mirror of
https://github.com/Spythere/stacjownik.git
synced 2026-05-03 13:28:11 +00:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -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.1",
|
"version": "1.24.0",
|
||||||
"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": [
|
||||||
|
|||||||
+59
-66
@@ -1,6 +1,11 @@
|
|||||||
<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>
|
||||||
@@ -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>
|
||||||
@@ -36,19 +44,21 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } 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 popupMixin from './mixins/popupMixin';
|
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';
|
|
||||||
|
|
||||||
const STORAGE_VERSION_KEY = 'app_version';
|
const STORAGE_VERSION_KEY = 'app_version';
|
||||||
|
|
||||||
@@ -58,19 +68,22 @@ export default defineComponent({
|
|||||||
StatusIndicator,
|
StatusIndicator,
|
||||||
AppHeader,
|
AppHeader,
|
||||||
TrainModal,
|
TrainModal,
|
||||||
PopUp
|
UpdateModal,
|
||||||
|
Tooltip
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [popupMixin],
|
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
VERSION: version,
|
VERSION: version,
|
||||||
store: useMainStore(),
|
store: useMainStore(),
|
||||||
apiStore: useApiStore(),
|
apiStore: useApiStore(),
|
||||||
|
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() {
|
||||||
@@ -78,41 +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();
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('mousemove', (e: MouseEvent) => this.handlePopUpEvents(e));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
StorageManager.setStringValue(STORAGE_VERSION_KEY, version);
|
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);
|
||||||
},
|
},
|
||||||
|
|
||||||
setupOfflineHandling() {
|
setupOfflineHandling() {
|
||||||
@@ -137,27 +161,6 @@ export default defineComponent({
|
|||||||
this.apiStore.connectToAPI();
|
this.apiStore.connectToAPI();
|
||||||
},
|
},
|
||||||
|
|
||||||
handlePopUpEvents(e: MouseEvent) {
|
|
||||||
const targetEl = e
|
|
||||||
.composedPath()
|
|
||||||
.find((p) => p instanceof HTMLElement && p.getAttribute('data-popup-key'));
|
|
||||||
|
|
||||||
if (!targetEl || !(targetEl instanceof HTMLElement)) {
|
|
||||||
if (this.store.popUpData.key != null) this.hidePopUp();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const popupComponentKey = targetEl.getAttribute('data-popup-key');
|
|
||||||
const popupContent = targetEl.getAttribute('data-popup-content');
|
|
||||||
|
|
||||||
if (popupComponentKey && popupContent) this.showPopUp(e, popupComponentKey, popupContent);
|
|
||||||
else if (this.store.popUpData.key != null) this.hidePopUp();
|
|
||||||
|
|
||||||
this.store.mousePos.x = e.pageX;
|
|
||||||
this.store.mousePos.y = e.pageY;
|
|
||||||
},
|
|
||||||
|
|
||||||
changeLang(lang: string) {
|
changeLang(lang: string) {
|
||||||
this.$i18n.locale = lang;
|
this.$i18n.locale = lang;
|
||||||
this.currentLang = lang;
|
this.currentLang = lang;
|
||||||
@@ -165,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');
|
||||||
|
|
||||||
@@ -255,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,36 +1,20 @@
|
|||||||
<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">
|
||||||
<div>
|
<h1 style="margin-bottom: 0.5em">🚀 {{ $t('update.title') }}</h1>
|
||||||
<h1 style="margin-bottom: 0.5em">{{ $t('update.title') }}</h1>
|
|
||||||
<h2 class="text--primary">{{ $t('update.version', [version]) }}</h2>
|
|
||||||
<hr class="separator" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="features-list">
|
<div class="features-body" v-if="htmlChangelog != ''" v-html="htmlChangelog"></div>
|
||||||
<h2>Nowości i zmiany:</h2>
|
<div class="no-features" v-else>{{ $t('update.no-data') }}</div>
|
||||||
<ul>
|
|
||||||
<li v-for="content in localeChangesArray" :key="content">{{ content }}</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal_actions">
|
<button class="btn btn--action" ref="confirm-btn" @click="toggleModal(false)">
|
||||||
<button class="btn--action">Przyjąłem!</button>
|
{{ $t('update.confirm') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
<p>Ten changelog będzie zawsze dostępny po kliknięciu numeru wersji w stopce strony!</p>
|
<p class="bottom-info">
|
||||||
|
{{ $t('update.info-1') }}
|
||||||
<!-- <div class="actions-checkboxes">
|
<br />
|
||||||
<label>
|
<span v-html="$t('update.info-2')"></span>
|
||||||
<input type="checkbox" />
|
</p>
|
||||||
<span>nie pokazuj dla przyszłych aktualizacji</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" />
|
|
||||||
<span>nie pokazuj dla przyszłych aktualizacji</span>
|
|
||||||
</label>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</AnimatedModal>
|
</AnimatedModal>
|
||||||
</template>
|
</template>
|
||||||
@@ -39,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(),
|
||||||
@@ -51,9 +48,22 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
updateModalOpen(val: boolean) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (val) (this.$refs['confirm-btn'] as HTMLElement).focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
localeChangesArray() {
|
htmlChangelog() {
|
||||||
return this.$t('update.content').split('\n');
|
if (this.mainStore.appUpdate == null) return '';
|
||||||
|
|
||||||
|
const x = converter.makeHtml(this.mainStore.appUpdate.changelog);
|
||||||
|
console.log(x);
|
||||||
|
|
||||||
|
return x;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -66,68 +76,46 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.modal_content {
|
::v-deep(h1) {
|
||||||
font-size: 1.2em;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 1em;
|
}
|
||||||
height: 80vh;
|
|
||||||
min-height: 550px;
|
|
||||||
|
|
||||||
|
::v-deep(h2) {
|
||||||
|
padding: 0.25em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(ul) {
|
||||||
|
list-style: inside;
|
||||||
|
padding: 0.5em;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto 1fr auto;
|
grid-template-rows: auto 1fr auto;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
}
|
padding: 1em;
|
||||||
|
min-height: 700px;
|
||||||
hr.separator {
|
|
||||||
margin: 0.5em 0;
|
|
||||||
padding: 0;
|
|
||||||
height: 3px;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.features-list {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
text-align: justify;
|
||||||
ul {
|
|
||||||
text-align: left;
|
|
||||||
list-style: '\21D2 ';
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
margin: 0.5em 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal_actions {
|
.no-features {
|
||||||
display: flex;
|
text-align: center;
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.5em;
|
|
||||||
|
|
||||||
button {
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 0.35em;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions-checkboxes {
|
button {
|
||||||
display: flex;
|
margin: 0 auto;
|
||||||
flex-wrap: wrap;
|
padding: 0.5em 0.75em;
|
||||||
justify-content: center;
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
gap: 1em;
|
p.bottom-info {
|
||||||
|
text-align: center;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
label {
|
a {
|
||||||
font-size: 0.9em;
|
text-decoration: underline;
|
||||||
}
|
|
||||||
|
|
||||||
label > input {
|
|
||||||
margin-right: 0.5em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</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;
|
||||||
background-color: #1a1a1a;
|
overflow: auto;
|
||||||
box-shadow: 0 0 15px 10px #333333;
|
|
||||||
|
|
||||||
width: 95%;
|
|
||||||
max-width: 800px;
|
|
||||||
max-height: 95vh;
|
max-height: 95vh;
|
||||||
|
|
||||||
& > :slotted(div) {
|
& > :slotted(div) {
|
||||||
max-height: 95vh;
|
background-color: #1a1a1a;
|
||||||
}
|
box-shadow: 0 0 15px 10px #0e0e0e;
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen {
|
width: 95vw;
|
||||||
.wrapper {
|
max-width: 850px;
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,8 @@
|
|||||||
<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="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}${
|
||||||
/^EN/.test(stockName) ? 'rb' : ''
|
/^EN/.test(stockName) ? 'rb' : ''
|
||||||
}.png`"
|
}.png`"
|
||||||
@@ -41,8 +41,8 @@
|
|||||||
<!-- /// 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="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}s.png`"
|
||||||
@error="
|
@error="
|
||||||
@@ -53,8 +53,8 @@
|
|||||||
|
|
||||||
<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="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}s.png`"
|
||||||
@error="
|
@error="
|
||||||
@@ -65,8 +65,8 @@
|
|||||||
|
|
||||||
<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="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}ra.png`"
|
||||||
@error="
|
@error="
|
||||||
@@ -116,14 +116,10 @@ export default defineComponent({
|
|||||||
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>
|
||||||
|
|
||||||
|
|||||||
@@ -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')"
|
||||||
>
|
>
|
||||||
@@ -57,18 +57,18 @@
|
|||||||
!timetable.terminated
|
!timetable.terminated
|
||||||
? $t('journal.timetable-active')
|
? $t('journal.timetable-active')
|
||||||
: timetable.fulfilled
|
: timetable.fulfilled
|
||||||
? $t('journal.timetable-fulfilled')
|
? $t('journal.timetable-fulfilled')
|
||||||
: `${$t('journal.timetable-abandoned')} ${localeTime(timetable.endDate, $i18n.locale)}`
|
: `${$t('journal.timetable-abandoned')} ${localeTime(timetable.endDate, $i18n.locale)}`
|
||||||
}}
|
}}
|
||||||
</b>
|
</b>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
v-if="timetable.terminated == false"
|
v-if="timetable.terminated == false"
|
||||||
class="btn--image btn--action btn-timetable"
|
class="btn--action btn-timetable"
|
||||||
@click.stop="showTimetable(timetable, $event.currentTarget)"
|
@click.stop="showTimetable(timetable, $event.currentTarget)"
|
||||||
>
|
>
|
||||||
<img src="/images/icon-train.svg" alt="" />
|
<img src="/images/icon-train.svg" alt="train icon" />
|
||||||
{{ $t('journal.timetable-online-button') }}
|
<b>{{ $t('journal.timetable-online-button') }}</b>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -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,24 +163,12 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.general-train {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.25em;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
line-height: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="card_controls">
|
<div class="card_controls">
|
||||||
<button class="btn--filled btn--image" @click="toggleCard">
|
<button class="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]
|
[F] {{ $t('options.filters') }}
|
||||||
<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>
|
||||||
|
|
||||||
@@ -69,8 +69,8 @@
|
|||||||
minimumHours == 0
|
minimumHours == 0
|
||||||
? $t('filters.now')
|
? $t('filters.now')
|
||||||
: minimumHours < 8
|
: minimumHours < 8
|
||||||
? minimumHours + $t('filters.hour')
|
? minimumHours + $t('filters.hour')
|
||||||
: $t('filters.no-limit')
|
: $t('filters.no-limit')
|
||||||
}}</span>
|
}}</span>
|
||||||
<button class="btn--action" @click="addHour">+</button>
|
<button class="btn--action" @click="addHour">+</button>
|
||||||
</span>
|
</span>
|
||||||
@@ -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;
|
||||||
|
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
<template>
|
||||||
|
<div class="station-stats">
|
||||||
|
<div class="separator" />
|
||||||
|
<div>
|
||||||
|
{{ $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
|
||||||
|
>:
|
||||||
|
<b :style="calculateFactorStyle()">
|
||||||
|
{{ uFactor.toFixed(2) }}
|
||||||
|
</b>
|
||||||
|
| {{ $t('station-stats.avg-timetable-count') }}
|
||||||
|
<b>{{ avgTimetableCount.toFixed(2) }}</b>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ $t('station-stats.single-track-count') }}
|
||||||
|
<b>{{ trackCount.oneWayElectric }}</b> {{ $t('station-stats.electrified') }} /
|
||||||
|
<b>{{ trackCount.oneWayOther }}</b> {{ $t('station-stats.not-electrified') }} |
|
||||||
|
{{ $t('station-stats.double-track-count') }} <b>{{ trackCount.twoWayElectric }}</b>
|
||||||
|
{{ $t('station-stats.electrified') }} / <b>{{ trackCount.twoWayOther }}</b>
|
||||||
|
{{ $t('station-stats.not-electrified') }} | {{ $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>
|
||||||
|
</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;
|
||||||
|
},
|
||||||
|
|
||||||
|
avgTimetableCount() {
|
||||||
|
const scheduledTrainsTotal = this.mainStore.activeSceneryList.reduce<number>((acc, sc) => {
|
||||||
|
if (sc.region != this.mainStore.region.id) return acc;
|
||||||
|
|
||||||
|
acc += sc.scheduledTrainCount.all;
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
return this.mainStore.activeSceneryList.length != 0
|
||||||
|
? scheduledTrainsTotal / this.mainStore.activeSceneryList.length
|
||||||
|
: 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
trackCount() {
|
||||||
|
return this.mainStore.allStationInfo
|
||||||
|
.filter(
|
||||||
|
(st) =>
|
||||||
|
st.onlineInfo?.dispatcherId != -1 &&
|
||||||
|
st.onlineInfo?.region == this.mainStore.region.id &&
|
||||||
|
st.generalInfo?.routes
|
||||||
|
)
|
||||||
|
.reduce(
|
||||||
|
(acc, st) => {
|
||||||
|
[...st.generalInfo!.routes.single, ...st.generalInfo!.routes.double].forEach((r) => {
|
||||||
|
if (r.isInternal) return;
|
||||||
|
|
||||||
|
const keyName: keyof typeof acc = `${r.routeTracks == 2 ? 'twoWay' : 'oneWay'}${r.isElectric ? 'Electric' : 'Other'}`;
|
||||||
|
|
||||||
|
acc[keyName] += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ oneWayElectric: 0, oneWayOther: 0, twoWayElectric: 0, twoWayOther: 0 }
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
spawnCount() {
|
||||||
|
return this.mainStore.activeSceneryList.reduce(
|
||||||
|
(acc, scenery) => {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
[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,330 +1,330 @@
|
|||||||
<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"
|
||||||
<table>
|
/>
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th
|
|
||||||
v-for="headerName in headIds"
|
|
||||||
:key="headerName"
|
|
||||||
@click="changeSorter(headerName)"
|
|
||||||
class="header-text"
|
|
||||||
:class="headerName"
|
|
||||||
>
|
|
||||||
<span class="header_wrapper">
|
|
||||||
<div v-html="$t(`sceneries.headers.${headerName}`)"></div>
|
|
||||||
|
|
||||||
<img
|
<div class="table_wrapper" v-else-if="displayedStations.length > 0">
|
||||||
class="sort-icon"
|
<table>
|
||||||
v-if="sorterActive.headerName == headerName"
|
<thead>
|
||||||
:src="`/images/icon-arrow-${sorterActive.dir == 1 ? 'asc' : 'desc'}.svg`"
|
<tr>
|
||||||
alt="sort icon"
|
<th
|
||||||
/>
|
v-for="headerName in headIds"
|
||||||
</span>
|
:key="headerName"
|
||||||
</th>
|
@click="changeSorter(headerName)"
|
||||||
|
class="header-text"
|
||||||
<th
|
:class="headerName"
|
||||||
v-for="headerName in headIconsIds"
|
|
||||||
:key="headerName"
|
|
||||||
@click="changeSorter(headerName)"
|
|
||||||
class="header-image"
|
|
||||||
:class="headerName"
|
|
||||||
>
|
|
||||||
<span class="header_wrapper">
|
|
||||||
<img
|
|
||||||
:src="`/images/icon-${headerName}.svg`"
|
|
||||||
:alt="headerName"
|
|
||||||
:title="$t(`sceneries.headers.${headerName}`)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<img
|
|
||||||
class="sort-icon"
|
|
||||||
v-if="sorterActive.headerName == headerName"
|
|
||||||
:src="`/images/icon-arrow-${sorterActive.dir == 1 ? 'asc' : 'desc'}.svg`"
|
|
||||||
alt="sort icon"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
<tr
|
|
||||||
v-for="station in stations"
|
|
||||||
:class="{ 'last-selected': lastSelectedStationName == station.name }"
|
|
||||||
:key="station.name"
|
|
||||||
@click.left="setScenery(station.name)"
|
|
||||||
@click.right="openForumSite($event, station.generalInfo?.url)"
|
|
||||||
@keydown.enter="setScenery(station.name)"
|
|
||||||
@keydown.space="openForumSite($event, station.generalInfo?.url)"
|
|
||||||
tabindex="0"
|
|
||||||
>
|
>
|
||||||
<td class="station-name" :class="station.generalInfo?.availability">
|
<span class="header_wrapper">
|
||||||
<b v-if="station.generalInfo?.project" style="color: salmon">{{
|
<div v-html="$t(`sceneries.headers.${headerName}`)"></div>
|
||||||
station.generalInfo.project
|
|
||||||
}}</b>
|
|
||||||
{{ station.name }}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="station-level">
|
<img
|
||||||
<span v-if="station.generalInfo">
|
class="sort-icon"
|
||||||
<span
|
v-if="sorterActive.headerName == headerName"
|
||||||
v-if="
|
:src="`/images/icon-arrow-${sorterActive.dir == 1 ? 'asc' : 'desc'}.svg`"
|
||||||
station.generalInfo.reqLevel > -1 &&
|
alt="sort icon"
|
||||||
station.generalInfo.availability != 'nonPublic' &&
|
|
||||||
station.generalInfo.availability != 'unavailable'
|
|
||||||
"
|
|
||||||
:style="calculateExpStyle(station.generalInfo.reqLevel)"
|
|
||||||
>
|
|
||||||
{{ station.generalInfo.reqLevel >= 2 ? station.generalInfo.reqLevel : 'L' }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-else-if="station.generalInfo.availability == 'abandoned'">
|
|
||||||
<img
|
|
||||||
src="/images/icon-abandoned.svg"
|
|
||||||
alt="non-public"
|
|
||||||
:title="$t('sceneries.info.abandoned')"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-else-if="station.generalInfo.availability == 'nonPublic'">
|
|
||||||
<img
|
|
||||||
src="/images/icon-lock.svg"
|
|
||||||
alt="non-public"
|
|
||||||
:title="$t('sceneries.info.non-public')"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-else>
|
|
||||||
<img
|
|
||||||
src="/images/icon-unavailable.svg"
|
|
||||||
alt="unavailable"
|
|
||||||
:title="$t('sceneries.info.unavailable')"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span v-else> ? </span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="station-status">
|
|
||||||
<StationStatusBadge
|
|
||||||
:isOnline="station.onlineInfo ? true : false"
|
|
||||||
:dispatcherStatus="station.onlineInfo?.dispatcherStatus"
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</span>
|
||||||
|
</th>
|
||||||
|
|
||||||
<td class="station-dispatcher-name">
|
<th
|
||||||
<span v-if="station.onlineInfo?.dispatcherName">
|
v-for="headerName in headIconsIds"
|
||||||
<b
|
:key="headerName"
|
||||||
v-if="apiStore.donatorsData.includes(station.onlineInfo.dispatcherName)"
|
@click="changeSorter(headerName)"
|
||||||
@click.stop="openDonationModal"
|
class="header-image"
|
||||||
data-popup-key="DonatorPopUp"
|
:class="headerName"
|
||||||
:data-popup-content="$t('donations.dispatcher-message')"
|
>
|
||||||
>
|
<span class="header_wrapper">
|
||||||
<img src="/images/icon-diamond.svg" alt="" />
|
<img
|
||||||
{{ station.onlineInfo.dispatcherName }}
|
:src="`/images/icon-${headerName}.svg`"
|
||||||
</b>
|
:alt="headerName"
|
||||||
|
:title="$t(`sceneries.headers.${headerName}`)"
|
||||||
|
/>
|
||||||
|
|
||||||
<div v-else>
|
<img
|
||||||
{{ station.onlineInfo.dispatcherName }}
|
class="sort-icon"
|
||||||
</div>
|
v-if="sorterActive.headerName == headerName"
|
||||||
</span>
|
:src="`/images/icon-arrow-${sorterActive.dir == 1 ? 'asc' : 'desc'}.svg`"
|
||||||
</td>
|
alt="sort icon"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
<td class="station-dispatcher-exp">
|
<tbody>
|
||||||
|
<tr
|
||||||
|
v-for="station in displayedStations"
|
||||||
|
:class="{ 'last-selected': lastSelectedStationName == station.name }"
|
||||||
|
:key="station.name"
|
||||||
|
@click.left="setScenery(station.name)"
|
||||||
|
@click.right="openForumSite($event, station.generalInfo?.url)"
|
||||||
|
@keydown.enter="setScenery(station.name)"
|
||||||
|
@keydown.space="openForumSite($event, station.generalInfo?.url)"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<td class="station-name" :class="station.generalInfo?.availability">
|
||||||
|
<b v-if="station.generalInfo?.project" style="color: salmon">{{
|
||||||
|
station.generalInfo.project
|
||||||
|
}}</b>
|
||||||
|
{{ station.name }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="station-level">
|
||||||
|
<span v-if="station.generalInfo">
|
||||||
<span
|
<span
|
||||||
v-if="station.onlineInfo && station.onlineInfo?.dispatcherExp != -1"
|
v-if="
|
||||||
:style="
|
station.generalInfo.reqLevel > -1 &&
|
||||||
calculateExpStyle(
|
station.generalInfo.availability != 'nonPublic' &&
|
||||||
station.onlineInfo.dispatcherExp,
|
station.generalInfo.availability != 'unavailable'
|
||||||
station.onlineInfo.dispatcherIsSupporter
|
|
||||||
)
|
|
||||||
"
|
"
|
||||||
|
:style="calculateExpStyle(station.generalInfo.reqLevel)"
|
||||||
>
|
>
|
||||||
{{
|
{{ station.generalInfo.reqLevel >= 2 ? station.generalInfo.reqLevel : 'L' }}
|
||||||
station.onlineInfo.dispatcherExp < 2 ? 'L' : station.onlineInfo.dispatcherExp
|
|
||||||
}}
|
|
||||||
</span>
|
</span>
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="station-tracks">
|
<span v-else-if="station.generalInfo.availability == 'abandoned'">
|
||||||
<div v-if="station.generalInfo">
|
<img
|
||||||
<span
|
src="/images/icon-abandoned.svg"
|
||||||
v-if="station.generalInfo.routes.singleElectrifiedNames.length != 0"
|
alt="non-public"
|
||||||
class="track catenary"
|
:title="$t('sceneries.info.abandoned')"
|
||||||
:title="`${$t('sceneries.info.single-track-routes-catenary')}${
|
/>
|
||||||
station.generalInfo.routes.singleElectrifiedNames.length
|
</span>
|
||||||
}`"
|
|
||||||
>
|
|
||||||
{{ station.generalInfo.routes.singleElectrifiedNames.length }}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<span
|
<span v-else-if="station.generalInfo.availability == 'nonPublic'">
|
||||||
v-if="station.generalInfo.routes.singleOtherNames.length != 0"
|
<img
|
||||||
class="track no-catenary"
|
src="/images/icon-lock.svg"
|
||||||
:title="`${$t('sceneries.info.single-track-routes-other')}${
|
alt="non-public"
|
||||||
station.generalInfo.routes.singleOtherNames.length
|
:title="$t('sceneries.info.non-public')"
|
||||||
}`"
|
/>
|
||||||
>
|
</span>
|
||||||
{{ station.generalInfo.routes.singleOtherNames.length }}
|
|
||||||
</span>
|
<span v-else>
|
||||||
|
<img
|
||||||
|
src="/images/icon-unavailable.svg"
|
||||||
|
alt="unavailable"
|
||||||
|
:title="$t('sceneries.info.unavailable')"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span v-else> ? </span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="station-status">
|
||||||
|
<StationStatusBadge
|
||||||
|
:isOnline="station.onlineInfo ? true : false"
|
||||||
|
:dispatcherStatus="station.onlineInfo?.dispatcherStatus"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="station-dispatcher-name">
|
||||||
|
<span v-if="station.onlineInfo?.dispatcherName">
|
||||||
|
<b
|
||||||
|
v-if="apiStore.donatorsData.includes(station.onlineInfo.dispatcherName)"
|
||||||
|
@click.stop="openDonationModal"
|
||||||
|
data-tooltip-type="DonatorTooltip"
|
||||||
|
:data-tooltip-content="$t('donations.dispatcher-message')"
|
||||||
|
>
|
||||||
|
<img src="/images/icon-diamond.svg" alt="" />
|
||||||
|
{{ station.onlineInfo.dispatcherName }}
|
||||||
|
</b>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
{{ station.onlineInfo.dispatcherName }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
<td class="station-tracks">
|
<td class="station-dispatcher-exp">
|
||||||
<div v-if="station.generalInfo">
|
<span
|
||||||
<span
|
v-if="station.onlineInfo && station.onlineInfo?.dispatcherExp != -1"
|
||||||
v-if="station.generalInfo.routes.doubleElectrifiedNames.length != 0"
|
:style="
|
||||||
class="track catenary"
|
calculateExpStyle(
|
||||||
:title="`${$t('sceneries.info.double-track-routes-catenary')}${
|
station.onlineInfo.dispatcherExp,
|
||||||
station.generalInfo.routes.doubleElectrifiedNames.length
|
station.onlineInfo.dispatcherIsSupporter
|
||||||
}`"
|
)
|
||||||
>
|
"
|
||||||
{{ station.generalInfo.routes.doubleElectrifiedNames.length }}
|
>
|
||||||
</span>
|
{{ station.onlineInfo.dispatcherExp < 2 ? 'L' : station.onlineInfo.dispatcherExp }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
<span
|
<td class="station-tracks">
|
||||||
v-if="station.generalInfo.routes.doubleOtherNames.length != 0"
|
<div v-if="station.generalInfo">
|
||||||
class="track no-catenary"
|
|
||||||
:title="`${$t('sceneries.info.double-track-routes-other')}${
|
|
||||||
station.generalInfo.routes.doubleOtherNames.length
|
|
||||||
}`"
|
|
||||||
>
|
|
||||||
{{ station.generalInfo.routes.doubleOtherNames.length }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="station-info">
|
|
||||||
<span
|
<span
|
||||||
v-if="station.generalInfo?.signalType"
|
v-if="station.generalInfo.routes.singleElectrifiedNames.length != 0"
|
||||||
class="scenery-icon icon-info"
|
class="track catenary"
|
||||||
:class="station.generalInfo?.controlType.replace('+', '-')"
|
:title="`${$t('sceneries.info.single-track-routes-catenary')}${
|
||||||
:title="
|
station.generalInfo.routes.singleElectrifiedNames.length
|
||||||
$t('sceneries.info.control-type') +
|
}`"
|
||||||
$t(`controls.${station.generalInfo?.controlType}`)
|
|
||||||
"
|
|
||||||
v-html="getControlTypeAbbrev(station.generalInfo.controlType)"
|
|
||||||
>
|
>
|
||||||
|
{{ station.generalInfo.routes.singleElectrifiedNames.length }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<img
|
<span
|
||||||
v-if="station.generalInfo?.signalType"
|
v-if="station.generalInfo.routes.singleOtherNames.length != 0"
|
||||||
class="icon-info"
|
class="track no-catenary"
|
||||||
:src="`/images/icon-${station.generalInfo.signalType}.svg`"
|
:title="`${$t('sceneries.info.single-track-routes-other')}${
|
||||||
:alt="station.generalInfo.signalType"
|
station.generalInfo.routes.singleOtherNames.length
|
||||||
:title="
|
}`"
|
||||||
$t('sceneries.info.signals-type') +
|
>
|
||||||
$t(`signals.${station.generalInfo.signalType}`)
|
{{ station.generalInfo.routes.singleOtherNames.length }}
|
||||||
"
|
</span>
|
||||||
/>
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
<img
|
<td class="station-tracks">
|
||||||
v-if="station.generalInfo?.SUP"
|
<div v-if="station.generalInfo">
|
||||||
class="icon-info"
|
<span
|
||||||
src="/images/icon-SUP.svg"
|
v-if="station.generalInfo.routes.doubleElectrifiedNames.length != 0"
|
||||||
alt="SUP (RASP-UZK)"
|
class="track catenary"
|
||||||
:title="$t('sceneries.info.SUP')"
|
:title="`${$t('sceneries.info.double-track-routes-catenary')}${
|
||||||
/>
|
station.generalInfo.routes.doubleElectrifiedNames.length
|
||||||
|
}`"
|
||||||
|
>
|
||||||
|
{{ station.generalInfo.routes.doubleElectrifiedNames.length }}
|
||||||
|
</span>
|
||||||
|
|
||||||
<img
|
<span
|
||||||
v-if="station.generalInfo?.ASDEK"
|
v-if="station.generalInfo.routes.doubleOtherNames.length != 0"
|
||||||
class="icon-info"
|
class="track no-catenary"
|
||||||
src="/images/icon-ASDEK.svg"
|
:title="`${$t('sceneries.info.double-track-routes-other')}${
|
||||||
alt="dSAT ASDEK"
|
station.generalInfo.routes.doubleOtherNames.length
|
||||||
:title="$t('sceneries.info.ASDEK')"
|
}`"
|
||||||
/>
|
>
|
||||||
|
{{ station.generalInfo.routes.doubleOtherNames.length }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
<img
|
<td class="station-info">
|
||||||
v-if="!station.generalInfo"
|
<span
|
||||||
class="icon-info"
|
v-if="station.generalInfo?.signalType"
|
||||||
src="/images/icon-unknown.svg"
|
class="scenery-icon icon-info"
|
||||||
alt="icon-unknown"
|
:class="station.generalInfo?.controlType.replace('+', '-')"
|
||||||
:title="$t('sceneries.info.unknown')"
|
:title="
|
||||||
/>
|
$t('sceneries.info.control-type') +
|
||||||
</td>
|
$t(`controls.${station.generalInfo?.controlType}`)
|
||||||
|
"
|
||||||
<td class="station-users" :class="{ inactive: !station.onlineInfo }">
|
|
||||||
<span class="text--primary">{{ station.onlineInfo?.currentUsers ?? '-' }}</span>
|
|
||||||
/
|
|
||||||
<span class="text--primary">{{ station.onlineInfo?.maxUsers ?? '-' }}</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="station-likes" :class="{ inactive: !station.onlineInfo }">
|
|
||||||
<span>{{ station.onlineInfo?.dispatcherRate ?? '-' }}</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="station-spawns" :class="{ inactive: !station.onlineInfo }">
|
|
||||||
<span>{{ station.onlineInfo?.spawns.length ?? '-' }}</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td
|
|
||||||
class="station-schedules all"
|
|
||||||
style="width: 30px"
|
|
||||||
:class="{ inactive: !station.onlineInfo }"
|
|
||||||
>
|
>
|
||||||
{{ station.onlineInfo?.scheduledTrainCount.all ?? '-' }}
|
{{ $t(`controls.abbrevs.${station.generalInfo.controlType}`) }}
|
||||||
</td>
|
</span>
|
||||||
|
|
||||||
<td
|
<img
|
||||||
class="station-schedules unconfirmed"
|
v-if="station.generalInfo?.signalType"
|
||||||
style="width: 30px"
|
class="icon-info"
|
||||||
:class="{ inactive: !station.onlineInfo }"
|
:src="`/images/icon-${station.generalInfo.signalType}.svg`"
|
||||||
>
|
:alt="station.generalInfo.signalType"
|
||||||
{{ station.onlineInfo?.scheduledTrainCount.unconfirmed ?? '-' }}
|
:title="
|
||||||
</td>
|
$t('sceneries.info.signals-type') +
|
||||||
|
$t(`signals.${station.generalInfo.signalType}`)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
|
||||||
<td
|
<img
|
||||||
class="station-schedules confirmed"
|
v-if="station.generalInfo?.SUP"
|
||||||
style="width: 30px"
|
class="icon-info"
|
||||||
:class="{ inactive: !station.onlineInfo }"
|
src="/images/icon-SUP.svg"
|
||||||
>
|
alt="SUP (RASP-UZK)"
|
||||||
{{ station.onlineInfo?.scheduledTrainCount.confirmed ?? '-' }}
|
:title="$t('sceneries.info.SUP')"
|
||||||
</td>
|
/>
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<Loading
|
<img
|
||||||
v-if="apiStore.dataStatuses.connection == Status.Loading && stations.length == 0"
|
v-if="station.generalInfo?.ASDEK"
|
||||||
/>
|
class="icon-info"
|
||||||
|
src="/images/icon-ASDEK.svg"
|
||||||
|
alt="dSAT ASDEK"
|
||||||
|
:title="$t('sceneries.info.ASDEK')"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="no-stations" v-else-if="stations.length == 0">
|
<img
|
||||||
{{ $t('sceneries.no-stations') }}
|
v-if="!station.generalInfo"
|
||||||
</div>
|
class="icon-info"
|
||||||
</div>
|
src="/images/icon-unknown.svg"
|
||||||
</transition>
|
alt="icon-unknown"
|
||||||
|
:title="$t('sceneries.info.unknown')"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td
|
||||||
|
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>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="station-likes" :class="{ inactive: !station.onlineInfo }">
|
||||||
|
<span>{{ station.onlineInfo?.dispatcherRate ?? '-' }}</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td
|
||||||
|
class="station-schedules all"
|
||||||
|
style="width: 30px"
|
||||||
|
:class="{ inactive: !station.onlineInfo }"
|
||||||
|
>
|
||||||
|
{{ station.onlineInfo?.scheduledTrainCount.all ?? '-' }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td
|
||||||
|
class="station-schedules unconfirmed"
|
||||||
|
style="width: 30px"
|
||||||
|
:class="{ inactive: !station.onlineInfo }"
|
||||||
|
>
|
||||||
|
{{ station.onlineInfo?.scheduledTrainCount.unconfirmed ?? '-' }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td
|
||||||
|
class="station-schedules confirmed"
|
||||||
|
style="width: 30px"
|
||||||
|
:class="{ inactive: !station.onlineInfo }"
|
||||||
|
>
|
||||||
|
{{ station.onlineInfo?.scheduledTrainCount.confirmed ?? '-' }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="no-stations" v-else>
|
||||||
|
{{ $t('sceneries.no-stations') }} (region: <b>{{ mainStore.region.name }}</b
|
||||||
|
>)
|
||||||
|
</div>
|
||||||
</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 popupMixin from '../../mixins/popupMixin';
|
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, popupMixin],
|
mixins: [styleMixin, dateMixin],
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
headIconsIds,
|
headIconsIds,
|
||||||
@@ -335,12 +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 tooltipStore = useTooltipStore();
|
||||||
|
|
||||||
const stationFiltersStore = useStationFiltersStore();
|
const stationFiltersStore = useStationFiltersStore();
|
||||||
|
|
||||||
@@ -348,16 +353,19 @@ export default defineComponent({
|
|||||||
Status: Status.Data,
|
Status: Status.Data,
|
||||||
stationFiltersStore,
|
stationFiltersStore,
|
||||||
mainStore,
|
mainStore,
|
||||||
apiStore
|
apiStore,
|
||||||
|
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',
|
||||||
@@ -371,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.hidePopUp();
|
this.tooltipStore.hide();
|
||||||
},
|
},
|
||||||
|
|
||||||
openForumSite(e: Event, url: string | undefined) {
|
openForumSite(e: Event, url: string | undefined) {
|
||||||
@@ -396,23 +404,11 @@ export default defineComponent({
|
|||||||
|
|
||||||
$rowCol: #424242;
|
$rowCol: #424242;
|
||||||
|
|
||||||
.change-anim {
|
.station_table {
|
||||||
&-enter-active,
|
height: 80vh;
|
||||||
&-leave-active {
|
min-height: 550px;
|
||||||
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 {
|
||||||
@@ -420,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;
|
||||||
|
|||||||
+5
-5
@@ -1,24 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="popup-content">
|
<div class="tooltip-content">
|
||||||
<span>{{ store.popUpData.content }}</span>
|
<span>{{ tooltipStore.content }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { useMainStore } from '../../store/mainStore';
|
import { useTooltipStore } from '../../store/tooltipStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
store: useMainStore()
|
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>{{ store.popUpData.content }}</span>
|
<span>{{ tooltipStore.content }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { useMainStore } from '../../store/mainStore';
|
import { useTooltipStore } from '../../store/tooltipStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
store: useMainStore()
|
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>
|
||||||
@@ -1,55 +1,58 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="popup" v-show="store.popUpData.key" ref="preview">
|
<div class="tooltip" v-show="tooltipStore.type" ref="preview">
|
||||||
<component v-if="store.popUpData.key" :is="store.popUpData.key" />
|
<component v-if="tooltipStore.type" :is="tooltipStore.type" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import DonatorPopUp from './DonatorPopUp.vue';
|
import { useTooltipStore } from '../../store/tooltipStore';
|
||||||
import TrainCommentsPopUp from './TrainCommentsPopUp.vue';
|
import DonatorTooltip from './DonatorTooltip.vue';
|
||||||
import VehiclePreviewPopUp from './VehiclePreviewPopUp.vue';
|
import VehiclePreviewTooltip from './VehiclePreviewTooltip.vue';
|
||||||
import { useMainStore } from '../../store/mainStore';
|
import BaseTooltip from './BaseTooltip.vue';
|
||||||
|
import SpawnsTooltip from './SpawnsTooltip.vue';
|
||||||
|
import UsersTooltip from './UsersTooltip.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { DonatorPopUp, TrainCommentsPopUp, VehiclePreviewPopUp },
|
components: { DonatorTooltip, VehiclePreviewTooltip, BaseTooltip, SpawnsTooltip, UsersTooltip },
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
store: useMainStore()
|
tooltipStore: useTooltipStore()
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
'store.mousePos': {
|
'tooltipStore.mousePos': {
|
||||||
deep: true,
|
deep: true,
|
||||||
handler(val: typeof this.store.mousePos) {
|
// [x, y]
|
||||||
|
handler(val: [number, number]) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
const previewEl = this.$refs['preview'] as HTMLElement;
|
const previewEl = this.$refs['preview'] as HTMLElement;
|
||||||
const clientWidth = document.body.clientWidth;
|
const clientWidth = document.body.clientWidth;
|
||||||
const boxWidth = previewEl.getBoundingClientRect().width;
|
const boxWidth = previewEl.getBoundingClientRect().width;
|
||||||
|
|
||||||
let translateX = '0px',
|
let translateX = '0',
|
||||||
translateY = '30px';
|
translateY = '30px';
|
||||||
|
|
||||||
if (clientWidth < 500) {
|
if (clientWidth < 500) {
|
||||||
previewEl.style.left = '50%';
|
previewEl.style.left = '50%';
|
||||||
translateX = '-50%';
|
translateX = '-50%';
|
||||||
} else if (val.x <= boxWidth / 2) {
|
} else if (val[0] <= boxWidth / 2) {
|
||||||
previewEl.style.left = '0';
|
previewEl.style.left = '0';
|
||||||
translateX = '0px';
|
translateX = '0px';
|
||||||
} else if (val.x >= clientWidth - boxWidth / 2) {
|
} else if (val[0] >= clientWidth - boxWidth / 2) {
|
||||||
previewEl.style.left = '100%';
|
previewEl.style.left = '100%';
|
||||||
translateX = '-100%';
|
translateX = '-100%';
|
||||||
} else {
|
} else {
|
||||||
previewEl.style.left = `${val.x}px`;
|
previewEl.style.left = `${val[0]}px`;
|
||||||
translateX = '-50%';
|
translateX = '-50%';
|
||||||
}
|
}
|
||||||
|
|
||||||
previewEl.style.top = `${val.y}px`;
|
previewEl.style.top = `${val[1]}px`;
|
||||||
|
|
||||||
const isOutside =
|
const isOutside =
|
||||||
val.y + previewEl.getBoundingClientRect().height + 30 >=
|
val[1] + previewEl.getBoundingClientRect().height + 30 >=
|
||||||
window.innerHeight + window.scrollY;
|
window.innerHeight + window.scrollY;
|
||||||
|
|
||||||
if (isOutside) translateY = 'calc(-100% - 30px)';
|
if (isOutside) translateY = 'calc(-100% - 30px)';
|
||||||
@@ -62,7 +65,7 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.popup {
|
.tooltip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 250;
|
z-index: 250;
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
@@ -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
-10
@@ -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,29 +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="store.popUpData.key"
|
v-if="tooltipStore.type"
|
||||||
@load="onImageLoad"
|
@load="onImageLoad"
|
||||||
@error="onImageError"
|
@error="onImageError"
|
||||||
width="300"
|
width="300"
|
||||||
height="176"
|
height="176"
|
||||||
class="rounded-md w-full h-auto"
|
class="rounded-md w-full h-auto"
|
||||||
:src="`https://static.spythere.eu/images/${store.popUpData.content}--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>
|
||||||
{{ store.popUpData.content.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 { useMainStore } from '../../store/mainStore';
|
import { useTooltipStore } from '../../store/tooltipStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
store: useMainStore(),
|
tooltipStore: useTooltipStore(),
|
||||||
imageState: 'loading'
|
imageState: 'loading'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -38,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';
|
||||||
@@ -53,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;
|
||||||
@@ -82,4 +88,8 @@ img {
|
|||||||
color: #ccc;
|
color: #ccc;
|
||||||
text-wrap: wrap;
|
text-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error-placeholder {
|
||||||
|
height: 176px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -45,8 +45,8 @@
|
|||||||
<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" />
|
||||||
@@ -74,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
|
||||||
)})`"
|
)})`"
|
||||||
>
|
>
|
||||||
@@ -163,12 +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 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],
|
||||||
|
|||||||
@@ -1,7 +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">
|
||||||
<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>
|
||||||
@@ -13,7 +13,7 @@ import { defineComponent } from 'vue';
|
|||||||
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||||
import TrainInfo from './TrainInfo.vue';
|
import TrainInfo from './TrainInfo.vue';
|
||||||
import TrainSchedule from './TrainSchedule.vue';
|
import TrainSchedule from './TrainSchedule.vue';
|
||||||
import Train from '../../scripts/interfaces/Train';
|
import { Train } from '../../typings/common';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { TrainInfo, TrainSchedule },
|
components: { TrainInfo, TrainSchedule },
|
||||||
@@ -71,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;
|
||||||
@@ -83,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,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({
|
||||||
|
|||||||
@@ -265,6 +265,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 +274,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 +310,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 +319,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 +328,7 @@
|
|||||||
"name": "minTwoWay",
|
"name": "minTwoWay",
|
||||||
"minRange": 0,
|
"minRange": 0,
|
||||||
"maxRange": 5,
|
"maxRange": 5,
|
||||||
|
"step": 1,
|
||||||
"value": 0,
|
"value": 0,
|
||||||
"defaultValue": 0
|
"defaultValue": 0
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-8
@@ -27,11 +27,11 @@
|
|||||||
"SKR": "Train with exceeded gauge"
|
"SKR": "Train with exceeded gauge"
|
||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"title": "Stacjownik app update!",
|
"title": "Stacjownik update!",
|
||||||
"version": "Version {0}",
|
"confirm": "ROGER THAT!",
|
||||||
"confirm-button": "UPDATE NOW",
|
"no-data": "No data about the latest app update has been found",
|
||||||
"later-button": "LATER",
|
"info-1": "This changelog will be available to see once again after clicking the version number in the footer",
|
||||||
"content": "context tooltips when hovering over project sponsors and timetable comment warnings\nvehicle image preview when hovering over its thumbnail in the active timetable card view and timetable journal\nlink to the driver's timetable history in the active timetable card view\nlink to the driver's active timetable card view in the timetable journal (available for online trains only)\nnew update card with version changelog"
|
"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",
|
||||||
@@ -81,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 ",
|
||||||
@@ -169,7 +182,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",
|
||||||
@@ -222,11 +236,14 @@
|
|||||||
"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",
|
||||||
@@ -279,6 +296,16 @@
|
|||||||
"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)",
|
||||||
|
"avg-timetable-count": "Average timetable count for one dispatcher:",
|
||||||
|
"single-track-count": "Available single track routes:",
|
||||||
|
"double-track-count": "Available double track routes:",
|
||||||
|
"electrified": "(electrified)",
|
||||||
|
"not-electrified": "(not electr.)",
|
||||||
|
"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...",
|
||||||
@@ -367,7 +394,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",
|
||||||
|
|||||||
+34
-9
@@ -28,10 +28,10 @@
|
|||||||
},
|
},
|
||||||
"update": {
|
"update": {
|
||||||
"title": "Aktualizacja Stacjownika!",
|
"title": "Aktualizacja Stacjownika!",
|
||||||
"version": "Wersja {0}",
|
"confirm": "PRZYJĄŁEM!",
|
||||||
"confirm-button": "UPDATE NOW",
|
"no-data": "Nie znaleziono informacji o ostatnich zmianach w aplikacji",
|
||||||
"later-button": "LATER",
|
"info-1": "Ten changelog będzie zawsze dostępny po kliknięciu numeru wersji w stopce strony",
|
||||||
"content": "dymki kontekstowe po najechaniu kursorem na m.in. sponsorów projektu i uwagi eksploatacyjne\npodgląd pojazdu po najechaniu kursorem na jego miniaturkę w karcie aktywnego rozkładu jazdy oraz dzienniku RJ\nodnośnik do historii RJ maszynisty w widoku karty aktywnego rozkładu jazdy\nodnośnik do karty aktywnego rozkładu jazdy maszynisty w dzienniku (dostępny tylko dla pociągów online)\nnowa karta ze zmianami w aktualizacji"
|
"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",
|
||||||
@@ -77,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 ",
|
||||||
@@ -163,10 +176,11 @@
|
|||||||
"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",
|
||||||
@@ -209,7 +223,6 @@
|
|||||||
"semaphores": "KSZTAŁTOWA",
|
"semaphores": "KSZTAŁTOWA",
|
||||||
"mixed": "MIESZANA",
|
"mixed": "MIESZANA",
|
||||||
"historical": "HISTORYCZNA",
|
"historical": "HISTORYCZNA",
|
||||||
|
|
||||||
"free": "WOLNA",
|
"free": "WOLNA",
|
||||||
"occupied": "ZAJĘTA",
|
"occupied": "ZAJĘTA",
|
||||||
|
|
||||||
@@ -219,6 +232,8 @@
|
|||||||
"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)",
|
||||||
@@ -274,6 +289,16 @@
|
|||||||
"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)",
|
||||||
|
"avg-timetable-count": "Średnia liczba rozkładów jazdy na dyżurnego:",
|
||||||
|
"single-track-count": "Dostępne szlaki jednotorowe:",
|
||||||
|
"double-track-count": "Dostępne szlaki dwutorowe:",
|
||||||
|
"electrified": "(zelektr.)",
|
||||||
|
"not-electrified": "(niezelektr.)",
|
||||||
|
"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...",
|
||||||
@@ -352,7 +377,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.",
|
||||||
|
|||||||
@@ -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,10 +1,12 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { useMainStore } from '../store/mainStore';
|
import { useMainStore } from '../store/mainStore';
|
||||||
|
import { useTooltipStore } from '../store/tooltipStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
store: useMainStore()
|
store: useMainStore(),
|
||||||
|
tooltipStore: useTooltipStore()
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -17,7 +19,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
closeModal() {
|
closeModal() {
|
||||||
this.store.chosenModalTrainId = undefined;
|
this.store.chosenModalTrainId = undefined;
|
||||||
this.store.popUpData.key = null;
|
this.tooltipStore.hide();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
(this.store.modalLastClickedTarget as any)?.focus();
|
(this.store.modalLastClickedTarget as any)?.focus();
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import { defineComponent } from 'vue';
|
|
||||||
import { useMainStore } from '../store/mainStore';
|
|
||||||
import { PopUpType, popupKeys } from '../store/typings';
|
|
||||||
|
|
||||||
const isPopUp = (v: any): v is PopUpType => popupKeys.includes(v);
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
store: useMainStore()
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
showPopUp(e: MouseEvent, componentKey: string, value?: string) {
|
|
||||||
if (!isPopUp(componentKey)) return;
|
|
||||||
|
|
||||||
this.store.popUpData['key'] = componentKey;
|
|
||||||
this.store.popUpData['content'] = value ?? '';
|
|
||||||
},
|
|
||||||
|
|
||||||
hidePopUp() {
|
|
||||||
this.store.popUpData['key'] = null;
|
|
||||||
this.store.popUpData['content'] = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -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: () => ({
|
||||||
@@ -51,8 +50,8 @@ export default defineComponent({
|
|||||||
return diffMins < 1
|
return diffMins < 1
|
||||||
? this.$t('trains.last-seen-now')
|
? this.$t('trains.last-seen-now')
|
||||||
: diffMins < 2
|
: diffMins < 2
|
||||||
? this.$t('trains.last-seen-min')
|
? this.$t('trains.last-seen-min')
|
||||||
: this.$t('trains.last-seen-ago', { minutes: diffMins });
|
: this.$t('trains.last-seen-ago', { minutes: diffMins });
|
||||||
},
|
},
|
||||||
|
|
||||||
displayTrainPosition(train: Train) {
|
displayTrainPosition(train: Train) {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
+2
-14
@@ -54,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);
|
||||||
|
|||||||
+52
-17
@@ -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,11 +29,8 @@ export const useMainStore = defineStore('store', {
|
|||||||
|
|
||||||
chosenModalTrainId: undefined,
|
chosenModalTrainId: undefined,
|
||||||
|
|
||||||
modalLastClickedTarget: null,
|
modalLastClickedTarget: null
|
||||||
|
}) as MainStoreState,
|
||||||
mousePos: { x: 0, y: 0 },
|
|
||||||
popUpData: { key: null, content: '' }
|
|
||||||
}) as StoreState,
|
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
trainList(): Train[] {
|
trainList(): Train[] {
|
||||||
@@ -96,6 +96,7 @@ export const useMainStore = defineStore('store', {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// computed active sceneries
|
||||||
activeSceneryList(state): ActiveScenery[] {
|
activeSceneryList(state): ActiveScenery[] {
|
||||||
const apiStore = useApiStore();
|
const apiStore = useApiStore();
|
||||||
|
|
||||||
@@ -156,8 +157,8 @@ export const useMainStore = defineStore('store', {
|
|||||||
scenery.dispatcherStatus == Status.ActiveDispatcher.NO_LIMIT
|
scenery.dispatcherStatus == Status.ActiveDispatcher.NO_LIMIT
|
||||||
? Date.now() + 25500000
|
? Date.now() + 25500000
|
||||||
: scenery.dispatcherStatus > 5
|
: scenery.dispatcherStatus > 5
|
||||||
? scenery.dispatcherStatus
|
? scenery.dispatcherStatus
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
list.push({
|
list.push({
|
||||||
name: scenery.stationName,
|
name: scenery.stationName,
|
||||||
@@ -231,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();
|
||||||
|
|
||||||
@@ -248,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;
|
||||||
@@ -259,7 +268,9 @@ export const useMainStore = defineStore('store', {
|
|||||||
double: [],
|
double: [],
|
||||||
doubleElectrifiedNames: [],
|
doubleElectrifiedNames: [],
|
||||||
doubleOtherNames: [],
|
doubleOtherNames: [],
|
||||||
sblNames: []
|
sblNames: [],
|
||||||
|
minRouteSpeed: 0,
|
||||||
|
maxRouteSpeed: 0
|
||||||
} as StationRoutes
|
} as StationRoutes
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -274,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
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -52,9 +52,9 @@ const filterInitStates: Filter = {
|
|||||||
unsignedStatus: false,
|
unsignedStatus: false,
|
||||||
withActiveTimetables: false,
|
withActiveTimetables: false,
|
||||||
withoutActiveTimetables: false,
|
withoutActiveTimetables: false,
|
||||||
|
maxVmax: 200,
|
||||||
|
minVmax: 0,
|
||||||
authors: '',
|
authors: '',
|
||||||
|
|
||||||
onlineFromHours: 0
|
onlineFromHours: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -75,26 +75,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
-134
@@ -1,20 +1,10 @@
|
|||||||
import { API } from '../typings/api';
|
import { API } from '../typings/api';
|
||||||
import { Status } from '../typings/common';
|
import { Availability, StationRoutesInfo, Status } from '../typings/common';
|
||||||
|
|
||||||
export const popupKeys = ['DonatorPopUp', 'TrainCommentsPopUp', 'VehiclePreviewPopUp'] as const;
|
export interface MainStoreState {
|
||||||
export type Availability = 'default' | 'unavailable' | 'nonPublic' | 'abandoned' | 'nonDefault';
|
region: { id: string; value: string; name: string };
|
||||||
export type PopUpType = (typeof popupKeys)[number];
|
|
||||||
|
|
||||||
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;
|
||||||
@@ -22,19 +12,6 @@ export interface StoreState {
|
|||||||
driverStatsStatus: Status.Data;
|
driverStatsStatus: Status.Data;
|
||||||
chosenModalTrainId?: string;
|
chosenModalTrainId?: string;
|
||||||
modalLastClickedTarget: EventTarget | null;
|
modalLastClickedTarget: EventTarget | null;
|
||||||
mousePos: { x: number; y: number };
|
|
||||||
popUpData: { key: PopUpType | null; content: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
||||||
@@ -62,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
-5
@@ -1,6 +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,
|
||||||
|
ScheduledTrain,
|
||||||
|
Station,
|
||||||
|
StationTrain,
|
||||||
|
ScenerySpawn,
|
||||||
|
ScenerySpawnType
|
||||||
|
} from '../typings/common';
|
||||||
|
|
||||||
export function getLocoURL(locoType: string): string {
|
export function getLocoURL(locoType: string): string {
|
||||||
return `https://rj.td2.info.pl/dist/img/thumbnails/${
|
return `https://rj.td2.info.pl/dist/img/thumbnails/${
|
||||||
@@ -31,7 +38,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 +48,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 };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -213,6 +213,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;
|
||||||
@@ -298,3 +299,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;
|
||||||
|
}
|
||||||
|
|||||||
+10
-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;
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
+33
-28
@@ -12,46 +12,42 @@ 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: new RegExp('^https://stacjownik.spythere.eu/api/getSceneries', 'i'),
|
||||||
handler: 'NetworkFirst',
|
|
||||||
options: {
|
|
||||||
cacheName: 'sceneries-cache',
|
|
||||||
cacheableResponse: {
|
|
||||||
statuses: [0, 200]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
urlPattern: new RegExp('^https://raw.githubusercontent.com/Spythere/api/*', '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-sceneries-cache',
|
||||||
expiration: {
|
cacheableResponse: {
|
||||||
maxEntries: 100,
|
statuses: [0, 200]
|
||||||
maxAgeSeconds: 60 * 60 * 24 * 7 // <== 7 days
|
}
|
||||||
},
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urlPattern: new RegExp('^https://rj.td2.info.pl/dist/img/thumbnails/*', 'i'),
|
||||||
|
handler: 'CacheFirst',
|
||||||
|
options: {
|
||||||
|
cacheName: 'swdr-images-cache',
|
||||||
cacheableResponse: {
|
cacheableResponse: {
|
||||||
statuses: [0, 200, 404]
|
statuses: [0, 200, 404]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urlPattern: new RegExp('^https://static.spythere.eu/images/*', 'i'),
|
||||||
|
handler: 'CacheFirst',
|
||||||
|
options: {
|
||||||
|
cacheName: 'spythere-images-cache',
|
||||||
|
cacheableResponse: {
|
||||||
|
statuses: [0, 200]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -60,5 +56,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