Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a4304d65f | |||
| edad5306f2 | |||
| 5b775dfec9 | |||
| a485652ca5 | |||
| ed308246d7 | |||
| 621bb1ad55 | |||
| 154ae2ddac | |||
| d9da49a867 | |||
| 826d51f66c | |||
| 1d7fc2955f | |||
| c550e7598a | |||
| d5168ce59d | |||
| 380c97655c | |||
| e4ed24de80 | |||
| 8de03b9210 | |||
| 12ece46089 | |||
| 085238fada | |||
| 45c1d83512 |
@@ -0,0 +1,18 @@
|
|||||||
|
/* eslint-env node */
|
||||||
|
require('@rushstack/eslint-patch/modern-module-resolution')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: [
|
||||||
|
'plugin:vue/vue3-essential',
|
||||||
|
'eslint:recommended',
|
||||||
|
'@vue/eslint-config-typescript',
|
||||||
|
'@vue/eslint-config-prettier/skip-formatting'
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
'vue/multi-word-component-names': 'off'
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest'
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/prettierrc",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 100,
|
||||||
|
"trailingComma": "none"
|
||||||
|
}
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "stacjownik",
|
"name": "stacjownik",
|
||||||
"version": "1.17.1",
|
"version": "1.18.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vue-tsc --noEmit && vite build",
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
"deploy": "yarn build && firebase deploy --only hosting",
|
"deploy": "yarn build && firebase deploy --only hosting",
|
||||||
"preview": "yarn build && vite preview"
|
"preview": "yarn build && vite preview",
|
||||||
|
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
|
||||||
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||||
|
"format": "prettier --write src/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-js": "^3.32.2",
|
"core-js": "^3.32.2",
|
||||||
@@ -25,10 +28,17 @@
|
|||||||
"@vite-pwa/assets-generator": "^0.0.10",
|
"@vite-pwa/assets-generator": "^0.0.10",
|
||||||
"@vitejs/plugin-vue": "^4.3.4",
|
"@vitejs/plugin-vue": "^4.3.4",
|
||||||
"axios": "^1.5.0",
|
"axios": "^1.5.0",
|
||||||
|
"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.16.5",
|
||||||
"vue-tsc": "^1.8.11"
|
"vue-tsc": "^1.8.11",
|
||||||
|
"@vue/eslint-config-prettier": "^8.0.0",
|
||||||
|
"@vue/eslint-config-typescript": "^12.0.0",
|
||||||
|
"@vue/tsconfig": "^0.4.0",
|
||||||
|
"eslint": "^8.49.0",
|
||||||
|
"eslint-plugin-vue": "^9.17.0",
|
||||||
|
"@rushstack/eslint-patch": "^1.3.3"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"> 1%",
|
"> 1%",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 363 B After Width: | Height: | Size: 363 B |
|
Before Width: | Height: | Size: 537 B After Width: | Height: | Size: 537 B |
|
Before Width: | Height: | Size: 482 B After Width: | Height: | Size: 482 B |
|
Before Width: | Height: | Size: 271 B After Width: | Height: | Size: 271 B |
|
Before Width: | Height: | Size: 201 B After Width: | Height: | Size: 201 B |
|
Before Width: | Height: | Size: 212 B After Width: | Height: | Size: 212 B |
|
Before Width: | Height: | Size: 170 B After Width: | Height: | Size: 170 B |
|
Before Width: | Height: | Size: 350 B After Width: | Height: | Size: 350 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 264 B After Width: | Height: | Size: 264 B |
|
Before Width: | Height: | Size: 582 B After Width: | Height: | Size: 582 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 256 B After Width: | Height: | Size: 256 B |
|
Before Width: | Height: | Size: 201 B After Width: | Height: | Size: 201 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 632 B After Width: | Height: | Size: 632 B |
|
Before Width: | Height: | Size: 398 B After Width: | Height: | Size: 398 B |
|
Before Width: | Height: | Size: 938 B After Width: | Height: | Size: 938 B |
|
Before Width: | Height: | Size: 356 B After Width: | Height: | Size: 356 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 369 B After Width: | Height: | Size: 369 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 252 B After Width: | Height: | Size: 252 B |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 264 B After Width: | Height: | Size: 264 B |
|
Before Width: | Height: | Size: 199 B After Width: | Height: | Size: 199 B |
|
Before Width: | Height: | Size: 384 B After Width: | Height: | Size: 384 B |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 270 B After Width: | Height: | Size: 270 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 476 B After Width: | Height: | Size: 476 B |
|
Before Width: | Height: | Size: 863 B After Width: | Height: | Size: 863 B |
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 304 B |
|
Before Width: | Height: | Size: 546 B After Width: | Height: | Size: 546 B |
|
Before Width: | Height: | Size: 230 B After Width: | Height: | Size: 230 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 199 B After Width: | Height: | Size: 199 B |
|
Before Width: | Height: | Size: 522 B After Width: | Height: | Size: 522 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 268 B After Width: | Height: | Size: 268 B |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 478 B After Width: | Height: | Size: 478 B |
|
Before Width: | Height: | Size: 477 B After Width: | Height: | Size: 477 B |
|
Before Width: | Height: | Size: 477 B After Width: | Height: | Size: 477 B |
|
Before Width: | Height: | Size: 477 B After Width: | Height: | Size: 477 B |
|
Before Width: | Height: | Size: 457 B After Width: | Height: | Size: 457 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 326 B After Width: | Height: | Size: 326 B |
|
Before Width: | Height: | Size: 285 B After Width: | Height: | Size: 285 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
@@ -43,9 +43,10 @@
|
|||||||
#app {
|
#app {
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
@include smallScreen() {
|
@include smallScreen() {
|
||||||
font-size: calc(0.55rem + 1.1vw);
|
font-size: calc(0.65rem + 0.8vw);
|
||||||
}
|
}
|
||||||
|
|
||||||
@include screenLandscape() {
|
@include screenLandscape() {
|
||||||
|
|||||||
@@ -6,8 +6,6 @@
|
|||||||
</keep-alive>
|
</keep-alive>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
<UpdatePrompt />
|
|
||||||
|
|
||||||
<AppHeader :current-lang="currentLang" @change-lang="changeLang" />
|
<AppHeader :current-lang="currentLang" @change-lang="changeLang" />
|
||||||
|
|
||||||
<main class="app_main">
|
<main class="app_main">
|
||||||
@@ -24,7 +22,9 @@
|
|||||||
{{ new Date().getUTCFullYear() }} |
|
{{ new Date().getUTCFullYear() }} |
|
||||||
<a :href="releaseURL" target="_blank">v{{ VERSION }}{{ isOnProductionHost ? '' : 'dev' }}</a>
|
<a :href="releaseURL" target="_blank">v{{ VERSION }}{{ isOnProductionHost ? '' : 'dev' }}</a>
|
||||||
<br />
|
<br />
|
||||||
<a href="https://discord.gg/x2mpNN3svk"><img :src="getIcon('discord', 'png')" alt=""> <b>{{ $t('footer.discord') }}</b></a>
|
<a href="https://discord.gg/x2mpNN3svk">
|
||||||
|
<img src="/images/icon-discord.png" alt="" /> <b>{{ $t('footer.discord') }}</b>
|
||||||
|
</a>
|
||||||
|
|
||||||
<div style="display: none">∫ ukryta taktyczna całka do programowania w HTMLu</div>
|
<div style="display: none">∫ ukryta taktyczna całka do programowania w HTMLu</div>
|
||||||
</footer>
|
</footer>
|
||||||
@@ -32,68 +32,40 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, KeepAlive, provide, ref, watch } from 'vue';
|
import { defineComponent, watch } from 'vue';
|
||||||
|
|
||||||
import Clock from './components/App/Clock.vue';
|
import Clock from './components/App/Clock.vue';
|
||||||
|
|
||||||
import packageInfo from '.././package.json';
|
import packageInfo from '.././package.json';
|
||||||
|
import { regions } from './data/options.json';
|
||||||
|
|
||||||
import StatusIndicator from './components/App/StatusIndicator.vue';
|
|
||||||
import SelectBox from './components/Global/SelectBox.vue';
|
|
||||||
import { useStore } from './store/store';
|
import { useStore } from './store/store';
|
||||||
|
import StatusIndicator from './components/App/StatusIndicator.vue';
|
||||||
import TrainModal from './components/Global/TrainModal.vue';
|
import TrainModal from './components/Global/TrainModal.vue';
|
||||||
import StorageManager from './scripts/managers/storageManager';
|
import StorageManager from './scripts/managers/storageManager';
|
||||||
import imageMixin from './mixins/imageMixin';
|
|
||||||
import AppHeader from './components/App/AppHeader.vue';
|
import AppHeader from './components/App/AppHeader.vue';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import UpdatePrompt from './components/App/UpdatePrompt.vue';
|
|
||||||
import { VERSION } from 'vue-i18n';
|
|
||||||
import { RouterView } from 'vue-router';
|
|
||||||
import useCustomSW from './mixins/useCustomSW';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
Clock,
|
Clock,
|
||||||
StatusIndicator,
|
StatusIndicator,
|
||||||
SelectBox,
|
|
||||||
TrainModal,
|
TrainModal,
|
||||||
AppHeader,
|
AppHeader
|
||||||
UpdatePrompt,
|
|
||||||
},
|
|
||||||
|
|
||||||
mixins: [imageMixin],
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
const store = useStore();
|
|
||||||
store.connectToAPI();
|
|
||||||
|
|
||||||
const { offlineReady } = useCustomSW();
|
|
||||||
|
|
||||||
const isFilterCardVisible = ref(false);
|
|
||||||
|
|
||||||
provide('isFilterCardVisible', isFilterCardVisible);
|
|
||||||
|
|
||||||
return {
|
|
||||||
store,
|
|
||||||
isFilterCardVisible,
|
|
||||||
onlineDispatchers: computed(() =>
|
|
||||||
store.stationList.filter((station) => station.onlineInfo && station.onlineInfo.region == store.region.id)
|
|
||||||
),
|
|
||||||
|
|
||||||
dispatcherDataStatus: store.dataStatuses.dispatchers,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
VERSION: packageInfo.version,
|
VERSION: packageInfo.version,
|
||||||
|
store: useStore(),
|
||||||
|
|
||||||
currentLang: 'pl',
|
currentLang: 'pl',
|
||||||
releaseURL: '',
|
releaseURL: '',
|
||||||
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app',
|
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app'
|
||||||
}),
|
}),
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.loadLang();
|
this.loadLang();
|
||||||
|
this.store.connectToAPI();
|
||||||
|
|
||||||
this.store.isOffline = !window.navigator.onLine;
|
this.store.isOffline = !window.navigator.onLine;
|
||||||
|
|
||||||
@@ -104,10 +76,10 @@ export default defineComponent({
|
|||||||
stations: [],
|
stations: [],
|
||||||
dispatchers: [],
|
dispatchers: [],
|
||||||
trains: [],
|
trains: [],
|
||||||
connectedSocketCount: 0,
|
connectedSocketCount: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
this.store.setOnlineData();
|
this.store.setStatuses();
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('online', () => {
|
window.addEventListener('online', () => {
|
||||||
@@ -121,16 +93,28 @@ export default defineComponent({
|
|||||||
watch(
|
watch(
|
||||||
() => this.store.blockScroll,
|
() => this.store.blockScroll,
|
||||||
(value) => {
|
(value) => {
|
||||||
if (value) {
|
if (value) document.body.classList.add('no-scroll');
|
||||||
document.body.classList.add('no-scroll');
|
else document.body.classList.remove('no-scroll');
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.body.classList.remove('no-scroll');
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
'$route.query.region': {
|
||||||
|
immediate: true,
|
||||||
|
handler(regionQuery: string) {
|
||||||
|
if (regionQuery) {
|
||||||
|
this.store.region.id =
|
||||||
|
regions.find(
|
||||||
|
(reg) =>
|
||||||
|
reg.id == regionQuery.toLocaleLowerCase() ||
|
||||||
|
reg.value.toLocaleLowerCase() == regionQuery.toLocaleLowerCase()
|
||||||
|
)?.id || 'eu';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
changeLang(lang: string) {
|
changeLang(lang: string) {
|
||||||
this.$i18n.locale = lang;
|
this.$i18n.locale = lang;
|
||||||
@@ -170,8 +154,8 @@ export default defineComponent({
|
|||||||
this.changeLang('en');
|
this.changeLang('en');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.3 KiB |
@@ -3,8 +3,13 @@
|
|||||||
<div class="header_container">
|
<div class="header_container">
|
||||||
<div class="header_icons">
|
<div class="header_icons">
|
||||||
<span class="icons-top">
|
<span class="icons-top">
|
||||||
<img :src="getIcon('pl')" alt="icon-pl" @click="changeLang('en')" v-if="currentLang == 'pl'" />
|
<img
|
||||||
<img :src="getIcon('en', 'jpg')" alt="icon-en" @click="changeLang('pl')" v-else />
|
src="/images/icon-pl.svg"
|
||||||
|
alt="icon-pl"
|
||||||
|
@click="changeLang('en')"
|
||||||
|
v-if="currentLang == 'pl'"
|
||||||
|
/>
|
||||||
|
<img src="/images/icon-en.jpg" alt="icon-en" @click="changeLang('pl')" v-else />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -13,7 +18,7 @@
|
|||||||
|
|
||||||
<span class="header_brand">
|
<span class="header_brand">
|
||||||
<router-link to="/">
|
<router-link to="/">
|
||||||
<img :src="getImage('stacjownik-header-logo.svg')" alt="Stacjownik" />
|
<img src="/images/stacjownik-header-logo.svg" alt="Stacjownik" />
|
||||||
</router-link>
|
</router-link>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -21,7 +26,7 @@
|
|||||||
<Clock />
|
<Clock />
|
||||||
|
|
||||||
<div class="info_counter">
|
<div class="info_counter">
|
||||||
<img :src="getIcon('dispatcher')" 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">
|
<!-- <span class="g-tooltip">
|
||||||
@@ -31,12 +36,12 @@
|
|||||||
|
|
||||||
<span class="text--grayed"> / </span>
|
<span class="text--grayed"> / </span>
|
||||||
<span class="text--primary">{{ onlineTrainsCount }}</span>
|
<span class="text--primary">{{ onlineTrainsCount }}</span>
|
||||||
<img :src="getIcon('train')" alt="icon train" />
|
<img src="/images/icon-train.svg" alt="icon train" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="info_region">
|
<div class="info_region">
|
||||||
<SelectBox :itemList="computedRegions" :defaultItemIndex="0" @selected="changeRegion" />
|
<RegionDropdown />
|
||||||
</span>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="header_links">
|
<span class="header_links">
|
||||||
@@ -44,7 +49,9 @@
|
|||||||
{{ $t('app.sceneries') }}
|
{{ $t('app.sceneries') }}
|
||||||
</router-link>
|
</router-link>
|
||||||
/
|
/
|
||||||
<router-link class="route" active-class="route-active" to="/trains">{{ $t('app.trains') }}</router-link>
|
<router-link class="route" active-class="route-active" to="/trains">{{
|
||||||
|
$t('app.trains')
|
||||||
|
}}</router-link>
|
||||||
/
|
/
|
||||||
<router-link
|
<router-link
|
||||||
class="route"
|
class="route"
|
||||||
@@ -62,64 +69,47 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { useStore } from '../../store/store';
|
import { useStore } from '../../store/store';
|
||||||
import options from '../../data/options.json';
|
|
||||||
import imageMixin from '../../mixins/imageMixin';
|
|
||||||
import SelectBox from '../Global/SelectBox.vue';
|
|
||||||
import StatusIndicator from './StatusIndicator.vue';
|
import StatusIndicator from './StatusIndicator.vue';
|
||||||
import Clock from './Clock.vue';
|
import Clock from './Clock.vue';
|
||||||
|
import RegionDropdown from '../Global/RegionDropdown.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
emits: ['changeLang'],
|
emits: ['changeLang'],
|
||||||
mixins: [imageMixin],
|
|
||||||
props: {
|
props: {
|
||||||
currentLang: {
|
currentLang: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
return {
|
return {
|
||||||
store: useStore(),
|
store: useStore()
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
changeRegion(region: { id: string; value: string }) {
|
|
||||||
this.store.changeRegion(region);
|
|
||||||
},
|
|
||||||
changeLang(lang: string) {
|
changeLang(lang: string) {
|
||||||
this.$emit('changeLang', lang);
|
this.$emit('changeLang', lang);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
onlineTrainsCount() {
|
onlineTrainsCount() {
|
||||||
return this.store.trainList.filter((train) => train.online).length;
|
return this.store.trainList.filter((train) => train.online).length;
|
||||||
},
|
},
|
||||||
|
|
||||||
onlineDispatchersCount() {
|
onlineDispatchersCount() {
|
||||||
return this.store.stationList.filter(
|
return this.store.onlineSceneryList.length;
|
||||||
(station) => station.onlineInfo && station.onlineInfo.region == this.store.region.id
|
|
||||||
).length;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
factorU() {
|
factorU() {
|
||||||
return this.onlineDispatchersCount == 0 ? '-' : (this.onlineTrainsCount / this.onlineDispatchersCount).toFixed(2);
|
return this.onlineDispatchersCount == 0
|
||||||
|
? '-'
|
||||||
|
: (this.onlineTrainsCount / this.onlineDispatchersCount).toFixed(2);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
components: { StatusIndicator, Clock, RegionDropdown }
|
||||||
computedRegions() {
|
|
||||||
return options.regions.map((region) => {
|
|
||||||
const regionStationCount =
|
|
||||||
this.store.apiData.stations?.filter((station) => station.region == region.id && station.isOnline).length || 0;
|
|
||||||
const regionTrainCount =
|
|
||||||
this.store.apiData.trains?.filter((train) => train.region == region.id && train.online).length || 0;
|
|
||||||
return {
|
|
||||||
id: region.id,
|
|
||||||
value: `${region.value} <div class='text--grayed'>${regionStationCount} / ${regionTrainCount}</div>`,
|
|
||||||
selectedValue: region.value,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
components: { SelectBox, StatusIndicator, Clock },
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -215,23 +205,8 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// REGION SELECTION
|
|
||||||
.info_region {
|
.info_region {
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|
||||||
.select-box_content button {
|
|
||||||
background-color: transparent;
|
|
||||||
font-weight: bold;
|
|
||||||
padding: 0.1em 0.5em;
|
|
||||||
color: paleturquoise;
|
|
||||||
}
|
|
||||||
|
|
||||||
.options {
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,32 +2,33 @@
|
|||||||
<div class="clock">{{ computedDate }}</div>
|
<div class="clock">{{ computedDate }}</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, ref } from "vue";
|
import { computed, defineComponent, ref } from 'vue';
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "clock",
|
name: 'VueClock',
|
||||||
data: () => ({
|
data: () => ({
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now()
|
||||||
}),
|
}),
|
||||||
setup() {
|
setup() {
|
||||||
let timestamp = ref(Date.now());
|
let timestamp = ref(Date.now());
|
||||||
|
|
||||||
const computedDate = computed(() => new Date(timestamp.value).toLocaleString("pl-PL", {
|
const computedDate = computed(() =>
|
||||||
hour: "2-digit",
|
new Date(timestamp.value).toLocaleString('pl-PL', {
|
||||||
minute: "2-digit",
|
hour: '2-digit',
|
||||||
second: "2-digit",
|
minute: '2-digit',
|
||||||
}));
|
second: '2-digit'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
setInterval(() => (timestamp.value = Date.now()), 1000);
|
setInterval(() => (timestamp.value = Date.now()), 1000);
|
||||||
|
|
||||||
return { computedDate }
|
return { computedDate };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../styles/responsive.scss";
|
@import '../../styles/responsive.scss';
|
||||||
|
|
||||||
.clock {
|
.clock {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -43,7 +43,13 @@
|
|||||||
<g v-if="greenBlinkLight" filter="url(#filter0_d_843_28)">
|
<g v-if="greenBlinkLight" filter="url(#filter0_d_843_28)">
|
||||||
<circle cx="15" cy="17" r="7" fill="#00FF0A" />
|
<circle cx="15" cy="17" r="7" fill="#00FF0A" />
|
||||||
|
|
||||||
<animate attributeType="XML" attributeName="opacity" values="1;0;1" dur="1s" repeatCount="indefinite" />
|
<animate
|
||||||
|
attributeType="XML"
|
||||||
|
attributeName="opacity"
|
||||||
|
values="1;0;1"
|
||||||
|
dur="1s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<g v-if="redTopLight" filter="url(#filter1_d_843_28)">
|
<g v-if="redTopLight" filter="url(#filter1_d_843_28)">
|
||||||
@@ -56,7 +62,13 @@
|
|||||||
<g v-if="redBottomLight" filter="url(#filter3_d_843_28)">
|
<g v-if="redBottomLight" filter="url(#filter3_d_843_28)">
|
||||||
<circle cx="15" cy="74" r="7" fill="#F40000" />
|
<circle cx="15" cy="74" r="7" fill="#F40000" />
|
||||||
|
|
||||||
<animate attributeType="XML" attributeName="opacity" values="1;0;1" dur="1s" repeatCount="indefinite" />
|
<animate
|
||||||
|
attributeType="XML"
|
||||||
|
attributeName="opacity"
|
||||||
|
values="1;0;1"
|
||||||
|
dur="1s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
/>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
@@ -82,7 +94,12 @@
|
|||||||
<feComposite in2="hardAlpha" operator="out" />
|
<feComposite in2="hardAlpha" operator="out" />
|
||||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 1 0 0 0 0 0.04 0 0 0 1 0" />
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 1 0 0 0 0 0.04 0 0 0 1 0" />
|
||||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" />
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" />
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_843_28" result="shape" />
|
<feBlend
|
||||||
|
mode="normal"
|
||||||
|
in="SourceGraphic"
|
||||||
|
in2="effect1_dropShadow_843_28"
|
||||||
|
result="shape"
|
||||||
|
/>
|
||||||
</filter>
|
</filter>
|
||||||
<filter
|
<filter
|
||||||
id="filter1_d_843_28"
|
id="filter1_d_843_28"
|
||||||
@@ -104,7 +121,12 @@
|
|||||||
<feGaussianBlur stdDeviation="2.5" />
|
<feGaussianBlur stdDeviation="2.5" />
|
||||||
<feColorMatrix type="matrix" values="0 0 0 0 0.770833 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" />
|
<feColorMatrix type="matrix" values="0 0 0 0 0.770833 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" />
|
||||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" />
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" />
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_843_28" result="shape" />
|
<feBlend
|
||||||
|
mode="normal"
|
||||||
|
in="SourceGraphic"
|
||||||
|
in2="effect1_dropShadow_843_28"
|
||||||
|
result="shape"
|
||||||
|
/>
|
||||||
</filter>
|
</filter>
|
||||||
<filter
|
<filter
|
||||||
id="filter2_d_843_28"
|
id="filter2_d_843_28"
|
||||||
@@ -126,7 +148,12 @@
|
|||||||
<feGaussianBlur stdDeviation="2.5" />
|
<feGaussianBlur stdDeviation="2.5" />
|
||||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 0.72 0 0 0 0 0 0 0 0 1 0" />
|
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 0.72 0 0 0 0 0 0 0 0 1 0" />
|
||||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" />
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" />
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_843_28" result="shape" />
|
<feBlend
|
||||||
|
mode="normal"
|
||||||
|
in="SourceGraphic"
|
||||||
|
in2="effect1_dropShadow_843_28"
|
||||||
|
result="shape"
|
||||||
|
/>
|
||||||
</filter>
|
</filter>
|
||||||
<filter
|
<filter
|
||||||
id="filter3_d_843_28"
|
id="filter3_d_843_28"
|
||||||
@@ -148,7 +175,12 @@
|
|||||||
<feGaussianBlur stdDeviation="2.5" />
|
<feGaussianBlur stdDeviation="2.5" />
|
||||||
<feColorMatrix type="matrix" values="0 0 0 0 0.770833 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" />
|
<feColorMatrix type="matrix" values="0 0 0 0 0.770833 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" />
|
||||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" />
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_843_28" />
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_843_28" result="shape" />
|
<feBlend
|
||||||
|
mode="normal"
|
||||||
|
in="SourceGraphic"
|
||||||
|
in2="effect1_dropShadow_843_28"
|
||||||
|
result="shape"
|
||||||
|
/>
|
||||||
</filter>
|
</filter>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -173,14 +205,14 @@ export default defineComponent({
|
|||||||
indicator: {
|
indicator: {
|
||||||
offline: false,
|
offline: false,
|
||||||
status: DataStatus.Loading,
|
status: DataStatus.Loading,
|
||||||
message: 'data-status.S3',
|
message: 'data-status.S3'
|
||||||
},
|
},
|
||||||
|
|
||||||
greenLight: false,
|
greenLight: false,
|
||||||
greenBlinkLight: false,
|
greenBlinkLight: false,
|
||||||
redTopLight: false,
|
redTopLight: false,
|
||||||
orangeLight: false,
|
orangeLight: false,
|
||||||
redBottomLight: false,
|
redBottomLight: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -193,7 +225,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
dataStatus: store.dataStatuses,
|
dataStatus: store.dataStatuses,
|
||||||
store,
|
store
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -248,8 +280,8 @@ export default defineComponent({
|
|||||||
this.indicator.status = DataStatus.Loaded;
|
this.indicator.status = DataStatus.Loaded;
|
||||||
this.indicator.message = 'data-status.S2';
|
this.indicator.message = 'data-status.S2';
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -280,8 +312,8 @@ export default defineComponent({
|
|||||||
if (status == DataStatus.Loading) {
|
if (status == DataStatus.Loading) {
|
||||||
this.greenBlinkLight = true;
|
this.greenBlinkLight = true;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -375,4 +407,3 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,168 +0,0 @@
|
|||||||
<template>
|
|
||||||
<transition name="modal-anim">
|
|
||||||
<section class="update-modal card" v-if="releaseData && modalOpen">
|
|
||||||
<h2 class="modal_header text--primary">
|
|
||||||
<img :src="getImage('stacjownik-header-logo.svg')" alt="stacjownik logo" />
|
|
||||||
|
|
||||||
{{ releaseData.tag_name }}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<div class="horizontal"></div>
|
|
||||||
|
|
||||||
<div class="modal_content">
|
|
||||||
<h3>{{ $t('update.title') }}</h3>
|
|
||||||
<a :href="releaseData.html_url" target="_blank">{{ $t('update.release-link') }}</a>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<p>{{ $t('update.paragraph1') }}</p>
|
|
||||||
|
|
||||||
<!-- <div class="modal_changelog" v-html="markdownReleaseBody"></div> -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal_actions">
|
|
||||||
<button class="btn btn--option" @click="modalOpen = false">{{ $t('update.confirm-button') }}</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</transition>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import axios from 'axios';
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import packageInfo from '../../../package.json';
|
|
||||||
import imageMixin from '../../mixins/imageMixin';
|
|
||||||
import { ReleaseAPIData } from '../../scripts/interfaces/github_api/ReleaseAPIData';
|
|
||||||
import StorageManager from '../../scripts/managers/storageManager';
|
|
||||||
import { useStore } from '../../store/store';
|
|
||||||
|
|
||||||
|
|
||||||
const GH_LASTEST_RELEASE_URL = 'https://api.github.com/repos/Spythere/stacjownik/releases/latest';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
mixins: [imageMixin],
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.fetchReleases();
|
|
||||||
},
|
|
||||||
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
modalOpen: false,
|
|
||||||
|
|
||||||
releaseData: null as ReleaseAPIData | null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
store: useStore()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
async fetchReleases() {
|
|
||||||
const storedVersion = StorageManager.getStringValue('appVersion');
|
|
||||||
const appVersion = packageInfo.version;
|
|
||||||
|
|
||||||
// Zmiana
|
|
||||||
if (appVersion != storedVersion) {
|
|
||||||
StorageManager.setStringValue('appVersion', appVersion);
|
|
||||||
|
|
||||||
// Znajdź changelog na GitHubie, jeśli jest pokaż modal
|
|
||||||
try {
|
|
||||||
const releaseData: ReleaseAPIData = await (await axios.get(GH_LASTEST_RELEASE_URL)).data;
|
|
||||||
if (!releaseData) return;
|
|
||||||
|
|
||||||
const lastReleaseVersion = releaseData.tag_name.slice(1);
|
|
||||||
|
|
||||||
if (lastReleaseVersion == appVersion) {
|
|
||||||
this.releaseData = releaseData;
|
|
||||||
this.modalOpen = true;
|
|
||||||
|
|
||||||
StorageManager.setStringValue('releaseURL', releaseData.html_url);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../styles/card.scss';
|
|
||||||
@import '../../styles/responsive.scss';
|
|
||||||
|
|
||||||
|
|
||||||
.modal-anim {
|
|
||||||
&-enter-active,
|
|
||||||
&-leave-active {
|
|
||||||
transition: all $animDuration $animType;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-enter-from,
|
|
||||||
&-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translate(-50%, -50%) scale(0.45);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.update-modal {
|
|
||||||
text-align: center;
|
|
||||||
background-color: var(--clr-secondary);
|
|
||||||
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.horizontal {
|
|
||||||
margin: 1em 0;
|
|
||||||
|
|
||||||
height: 2px;
|
|
||||||
width: 100%;
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal_header {
|
|
||||||
font-size: 1.6em;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 50%;
|
|
||||||
vertical-align: text-top;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal_content {
|
|
||||||
font-size: 1.1em;
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal_actions {
|
|
||||||
margin-top: 2em;
|
|
||||||
|
|
||||||
button {
|
|
||||||
color: white;
|
|
||||||
padding: 0.5em;
|
|
||||||
font-size: 1.2em;
|
|
||||||
|
|
||||||
background-color: black;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal_changelog {
|
|
||||||
font-size: 0.8em;
|
|
||||||
margin-top: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include smallScreen {
|
|
||||||
.update-modal {
|
|
||||||
height: auto;
|
|
||||||
max-width: 95%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="update-prompt">
|
|
||||||
<transition name="prompt-anim">
|
|
||||||
<div class="prompt_content" v-if="!hidePrompt && needRefresh">
|
|
||||||
<div>{{ $t('update.title') }}</div>
|
|
||||||
|
|
||||||
<div class="prompt_actions">
|
|
||||||
<button class="btn btn--filled" @click="updateServiceWorker(true)">{{ $t('update.confirm-button') }}</button>
|
|
||||||
<button class="btn btn--filled" @click="hidePrompt = true">{{ $t('update.later-button') }}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import useCustomSW from '../../mixins/useCustomSW';
|
|
||||||
|
|
||||||
const hidePrompt = ref(false);
|
|
||||||
const { needRefresh, updateServiceWorker } = useCustomSW();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../styles/variables.scss';
|
|
||||||
|
|
||||||
.update-prompt {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt_content {
|
|
||||||
margin: 1em;
|
|
||||||
padding: 1em;
|
|
||||||
|
|
||||||
font-weight: bold;
|
|
||||||
background-color: black;
|
|
||||||
|
|
||||||
box-shadow: 0 0 10px 1px $accentCol;
|
|
||||||
border-radius: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt_actions {
|
|
||||||
display: flex;
|
|
||||||
margin-top: 1em;
|
|
||||||
gap: 0.5em;
|
|
||||||
|
|
||||||
button {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Animation
|
|
||||||
.prompt-anim {
|
|
||||||
&-enter-active,
|
|
||||||
&-leave-active {
|
|
||||||
transition: all 120ms ease-in;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
&-enter-from,
|
|
||||||
&-leave-to {
|
|
||||||
transform: translateY(100%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
@@ -7,14 +7,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({});
|
export default defineComponent({});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "../../styles/variables";
|
@import '../../styles/variables';
|
||||||
@import "../../styles/responsive";
|
@import '../../styles/responsive';
|
||||||
|
|
||||||
.button_content {
|
.button_content {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -15,18 +15,18 @@ export default defineComponent({
|
|||||||
props: {
|
props: {
|
||||||
scrollNoMoreData: {
|
scrollNoMoreData: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
|
|
||||||
scrollDataLoaded: {
|
scrollDataLoaded: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
|
|
||||||
list: {
|
list: {
|
||||||
type: Array as PropType<any[]>,
|
type: Array as PropType<any[]>,
|
||||||
required: true,
|
required: true
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
emits: ['addHistoryData'],
|
emits: ['addHistoryData'],
|
||||||
@@ -34,8 +34,8 @@ export default defineComponent({
|
|||||||
methods: {
|
methods: {
|
||||||
addHistoryData() {
|
addHistoryData() {
|
||||||
this.$emit('addHistoryData');
|
this.$emit('addHistoryData');
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { defineComponent } from 'vue';
|
|||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
return {};
|
return {};
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="progress-bar">
|
<div class="progress-bar">
|
||||||
<span class="bar-bg"></span>
|
<span class="bar-bg"></span>
|
||||||
<span class="bar-fg" :style="{ width: `${~~progressPercent}%`, backgroundColor: bgColor }"></span>
|
<span
|
||||||
|
class="bar-fg"
|
||||||
|
:style="{ width: `${~~progressPercent}%`, backgroundColor: bgColor }"
|
||||||
|
></span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -12,12 +15,12 @@ export default defineComponent({
|
|||||||
props: {
|
props: {
|
||||||
progressPercent: {
|
progressPercent: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
progressType: {
|
progressType: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
@@ -29,8 +32,8 @@ export default defineComponent({
|
|||||||
default:
|
default:
|
||||||
return 'springgreen';
|
return 'springgreen';
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="select-box">
|
<div class="region-dropdown" v-click-outside="clickedOutside">
|
||||||
<div class="select-box_content">
|
<div class="content">
|
||||||
<button class="selected" @click="toggleBox">
|
<button class="selected-region" @click="toggleBox">
|
||||||
<span>{{ computedSelectedItem.selectedValue || computedSelectedItem.value }}</span>
|
<span>{{ selectedItem.name }}</span>
|
||||||
|
|
||||||
<div class="arrow">
|
<img :src="`/images/icon-arrow-${listOpen ? 'asc' : 'desc'}.svg`" alt="Arrow icon" />
|
||||||
<img :src="listOpen ? getIcon('arrow-asc') : getIcon('arrow-desc')" alt="arrow-icon" />
|
|
||||||
</div>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<ul class="options" :ref="(el) => (listRef = el as Element)">
|
<ul class="options">
|
||||||
<li class="option" v-for="(item, i) in itemList" :key="item.id">
|
<li class="option" v-for="(item, i) in regionList" :key="item.id">
|
||||||
<transition
|
<transition
|
||||||
name="unfold"
|
name="unfold"
|
||||||
:style="`
|
:style="`
|
||||||
--delay-in: ${i * 55}ms;
|
--delay-in: ${i * 55}ms;
|
||||||
--delay-out: ${(itemList.length - 1 - i) * 55}ms`"
|
--delay-out: ${(regionList.length - 1 - i) * 55}ms`"
|
||||||
>
|
>
|
||||||
<label :for="item.id" v-if="listOpen">
|
<label :for="item.id" v-if="listOpen">
|
||||||
<input type="button" :id="item.id" name="select-box" @click="selectOption(item)" />
|
<input type="button" :id="item.id" name="select-box" @click="selectOption(item)" />
|
||||||
<span :style="computedSelectedItem.id == item.id ? 'color: gold;' : ''" v-html="item.value"> </span>
|
<span :style="selectedItem.id == item.id ? 'color: gold;' : ''" v-html="item.value">
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</transition>
|
</transition>
|
||||||
</li>
|
</li>
|
||||||
@@ -29,65 +28,69 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, Ref, ref, computed } from 'vue';
|
import { defineComponent, Ref, ref } from 'vue';
|
||||||
import imageMixin from '../../mixins/imageMixin';
|
import { useStore } from '../../store/store';
|
||||||
|
import { regions as regionsJSON } from '../../data/options.json';
|
||||||
|
|
||||||
interface Item {
|
interface Item {
|
||||||
id: string;
|
id: string;
|
||||||
value: string;
|
value: string;
|
||||||
selectedValue?: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
emits: ['selected'],
|
data() {
|
||||||
mixins: [imageMixin],
|
|
||||||
|
|
||||||
props: {
|
|
||||||
itemList: {
|
|
||||||
type: Array as () => Item[],
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
defaultItemIndex: {
|
|
||||||
type: Number,
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
|
|
||||||
prefix: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
setup(props) {
|
|
||||||
let listRef: Ref<Element | null> = ref(null);
|
|
||||||
let buttonRef: Ref<HTMLButtonElement | null> = ref(null);
|
|
||||||
|
|
||||||
let activeEl: Ref<Element | null> = ref(document.activeElement);
|
|
||||||
|
|
||||||
let listOpen = ref(false);
|
|
||||||
let selectedItem: Ref<Item> = ref(props.itemList[props.defaultItemIndex]);
|
|
||||||
|
|
||||||
const computedSelectedItem = computed(() => {
|
|
||||||
return props.itemList.find((item) => item.id === selectedItem.value.id) || props.itemList[props.defaultItemIndex];
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
computedSelectedItem,
|
store: useStore(),
|
||||||
listOpen,
|
selectedItemIndex: 0,
|
||||||
selectedItem,
|
listOpen: false
|
||||||
listRef,
|
|
||||||
buttonRef,
|
|
||||||
activeEl,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
setup() {
|
||||||
selectOption(item: Item) {
|
let buttonRef: Ref<HTMLButtonElement | null> = ref(null);
|
||||||
this.selectedItem = item;
|
|
||||||
this.listOpen = false;
|
|
||||||
|
|
||||||
this.$emit('selected', item);
|
return {
|
||||||
|
buttonRef
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
'store.region.id': {
|
||||||
|
handler(regionId) {
|
||||||
|
this.selectedItemIndex = this.regionList.findIndex((reg) => reg.id == regionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
selectedItem() {
|
||||||
|
return this.regionList[this.selectedItemIndex] || null;
|
||||||
|
},
|
||||||
|
regionList() {
|
||||||
|
return regionsJSON.map((region) => {
|
||||||
|
const regionStationCount =
|
||||||
|
this.store.apiData.stations?.filter(
|
||||||
|
(station) => station.region == region.id && station.isOnline
|
||||||
|
).length || 0;
|
||||||
|
|
||||||
|
const regionTrainCount =
|
||||||
|
this.store.apiData.trains?.filter((train) => train.region == region.id && train.online)
|
||||||
|
.length || 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: region.id,
|
||||||
|
value: `${region.value} <div class='text--grayed'>${regionStationCount} / ${regionTrainCount}</div>`,
|
||||||
|
name: region.name
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
selectOption(selectedRegion: Item) {
|
||||||
|
this.store.region = selectedRegion;
|
||||||
|
this.listOpen = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleBox(e: Event) {
|
toggleBox(e: Event) {
|
||||||
@@ -99,48 +102,28 @@ export default defineComponent({
|
|||||||
clickedOutside() {
|
clickedOutside() {
|
||||||
this.listOpen = false;
|
this.listOpen = false;
|
||||||
this.buttonRef?.blur();
|
this.buttonRef?.blur();
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/variables.scss';
|
@import '../../styles/variables.scss';
|
||||||
|
|
||||||
.unfold {
|
.region-dropdown {
|
||||||
&-enter-from,
|
|
||||||
&-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(-10px) scale(0.85);
|
|
||||||
}
|
|
||||||
|
|
||||||
&-enter-active,
|
|
||||||
&-leave-active {
|
|
||||||
transition: all 110ms ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-enter-active {
|
|
||||||
transition-delay: var(--delay-in);
|
|
||||||
}
|
|
||||||
|
|
||||||
&-leave-active {
|
|
||||||
transition-delay: var(--delay-out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.select-box {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrow {
|
button img {
|
||||||
img {
|
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
width: 1.35em;
|
width: 1.35em;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
button.selected {
|
button.selected-region {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
color: paleturquoise;
|
color: paleturquoise;
|
||||||
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@@ -149,11 +132,16 @@ button.selected {
|
|||||||
&:focus {
|
&:focus {
|
||||||
background-color: #262626;
|
background-color: #262626;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.select-box_content {
|
.content {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
@@ -214,4 +202,25 @@ li.option {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.unfold {
|
||||||
|
&-enter-from,
|
||||||
|
&-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px) scale(0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-enter-active,
|
||||||
|
&-leave-active {
|
||||||
|
transition: all 110ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-enter-active {
|
||||||
|
transition-delay: var(--delay-in);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-leave-active {
|
||||||
|
transition-delay: var(--delay-out);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -7,39 +7,31 @@
|
|||||||
@keypress="updateValue"
|
@keypress="updateValue"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<img
|
<img class="search-exit" src="/images/icon-exit.svg" alt="exit-icon" @click="clearSearchValue" />
|
||||||
class="search-exit"
|
|
||||||
:src="getIcon('exit')"
|
|
||||||
alt="exit-icon"
|
|
||||||
@click="clearValue"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref, watch } from "vue";
|
import { defineComponent, ref, watch } from 'vue';
|
||||||
import imageMixin from "../../mixins/imageMixin";
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [imageMixin],
|
emits: ['update:searchedValue', 'clearValue'],
|
||||||
|
|
||||||
emits: ["update:searchedValue", "clearValue"],
|
|
||||||
props: {
|
props: {
|
||||||
searchedValue: {
|
searchedValue: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
updateOnInput: {
|
updateOnInput: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true
|
||||||
},
|
},
|
||||||
titleToTranslate: {
|
titleToTranslate: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
clearValue: {
|
clearValue: {
|
||||||
type: Function,
|
type: Function
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
@@ -49,32 +41,32 @@ export default defineComponent({
|
|||||||
watch(
|
watch(
|
||||||
() => compSearchedValue.value,
|
() => compSearchedValue.value,
|
||||||
(value) => {
|
(value) => {
|
||||||
emit("update:searchedValue", value);
|
emit('update:searchedValue', value);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearValue = () => {
|
const clearSearchValue = () => {
|
||||||
compSearchedValue.value = "";
|
compSearchedValue.value = '';
|
||||||
emit("clearValue");
|
emit('clearValue');
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateValue = (e: any) => {
|
const updateValue = (e: any) => {
|
||||||
if (!props.updateOnInput && e.keyCode == 13)
|
if (!props.updateOnInput && e.keyCode == 13)
|
||||||
emit("update:searchedValue", compSearchedValue.value);
|
emit('update:searchedValue', compSearchedValue.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
compSearchedValue,
|
compSearchedValue,
|
||||||
updateValue,
|
updateValue,
|
||||||
clearValue,
|
clearSearchValue
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../styles/responsive";
|
@import '../../styles/responsive';
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
&-box {
|
&-box {
|
||||||
|
|||||||
@@ -16,16 +16,16 @@ import dateMixin from '../../mixins/dateMixin';
|
|||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
statusID: {
|
statusID: {
|
||||||
type: String,
|
type: String
|
||||||
},
|
},
|
||||||
statusTimestamp: {
|
statusTimestamp: {
|
||||||
type: Number,
|
type: Number
|
||||||
},
|
},
|
||||||
isOnline: {
|
isOnline: {
|
||||||
type: Boolean,
|
type: Boolean
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
mixins: [dateMixin]
|
||||||
mixins: [dateMixin],
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="stock-list">
|
<div class="stock-list">
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(stockName, i) in trainStockList">
|
<li v-for="(stockName, i) in trainStockList" :key="i">
|
||||||
<p>{{ stockName.split(':')[0].split('_').splice(0, 2).join(' ') }} {{ stockName.split(':')[1] }}</p>
|
<p>
|
||||||
|
{{ stockName.split(':')[0].split('_').splice(0, 2).join(' ') }}
|
||||||
|
{{ stockName.split(':')[1] }}
|
||||||
|
</p>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
<img
|
<img
|
||||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}${/^EN/.test(stockName) ? 'rb' : ''}.png`"
|
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stockName.split(':')[0]}${
|
||||||
|
/^EN/.test(stockName) ? 'rb' : ''
|
||||||
|
}.png`"
|
||||||
@error="onImageError($event, stockName)"
|
@error="onImageError($event, stockName)"
|
||||||
width="400"
|
width="400"
|
||||||
height="60"
|
height="60"
|
||||||
@@ -15,21 +20,27 @@
|
|||||||
<img
|
<img
|
||||||
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="(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')"
|
@error="
|
||||||
|
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
class="train-thumbnail"
|
class="train-thumbnail"
|
||||||
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="(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')"
|
@error="
|
||||||
|
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-s.png')
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
class="train-thumbnail"
|
class="train-thumbnail"
|
||||||
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="(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-ra.png')"
|
@error="
|
||||||
|
(event) => ((event.target as HTMLImageElement).src = '/images/icon-loco-ezt-ra.png')
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
@@ -41,21 +52,18 @@
|
|||||||
import { PropType, defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import { useStore } from '../../store/store';
|
import { useStore } from '../../store/store';
|
||||||
import { RollingStockInfo } from '../../scripts/interfaces/github_api/StockInfoGithubData';
|
import { RollingStockInfo } from '../../scripts/interfaces/github_api/StockInfoGithubData';
|
||||||
import imageMixin from '../../mixins/imageMixin';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [imageMixin],
|
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
trainStockList: {
|
trainStockList: {
|
||||||
type: Array as PropType<string[]>,
|
type: Array as PropType<string[]>,
|
||||||
required: true,
|
required: true
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
store: useStore(),
|
store: useStore()
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -63,12 +71,14 @@ export default defineComponent({
|
|||||||
onImageError(event: Event, stockName: string) {
|
onImageError(event: Event, stockName: string) {
|
||||||
const fallbackName =
|
const fallbackName =
|
||||||
Object.keys(this.store.rollingStockData!.info).find((type) => {
|
Object.keys(this.store.rollingStockData!.info).find((type) => {
|
||||||
return this.store.rollingStockData!.info[type as keyof RollingStockInfo].find((v) => v[0] === stockName.split(':')[0]);
|
return this.store.rollingStockData!.info[type as keyof RollingStockInfo].find(
|
||||||
|
(v) => v[0] === stockName.split(':')[0]
|
||||||
|
);
|
||||||
}) || 'vehicle-unknown';
|
}) || 'vehicle-unknown';
|
||||||
|
|
||||||
(event.target as HTMLImageElement).src = `/images/icon-${fallbackName}.png`;
|
(event.target as HTMLImageElement).src = `/images/icon-${fallbackName}.png`;
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
:class="{
|
:class="{
|
||||||
delayed: stop.arrivalDelay > 0 && (stop.confirmed || stop.stopped),
|
delayed: stop.arrivalDelay > 0 && (stop.confirmed || stop.stopped),
|
||||||
preponed: stop.arrivalDelay < 0 && (stop.confirmed || stop.stopped),
|
preponed: stop.arrivalDelay < 0 && (stop.confirmed || stop.stopped),
|
||||||
'on-time': stop.arrivalDelay == 0 && stop.confirmed,
|
'on-time': stop.arrivalDelay == 0 && stop.confirmed
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<span v-if="stop.arrivalDelay != 0 && (stop.confirmed || stop.stopped)">
|
<span v-if="stop.arrivalDelay != 0 && (stop.confirmed || stop.stopped)">
|
||||||
@@ -20,7 +20,11 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="date stop" v-if="stop.stopTime || stop.stopped" :class="stop.stopType.replace(', ', '-')">
|
<span
|
||||||
|
class="date stop"
|
||||||
|
v-if="stop.stopTime || stop.stopped"
|
||||||
|
:class="stop.stopType.replace(', ', '-')"
|
||||||
|
>
|
||||||
{{ stop.stopTime }} {{ stop.stopType == '' ? 'pt' : stop.stopType }}
|
{{ stop.stopTime }} {{ stop.stopType == '' ? 'pt' : stop.stopType }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -29,7 +33,7 @@
|
|||||||
v-if="!stop.terminatesHere && (stop.stopTime != 0 || stop.stopped)"
|
v-if="!stop.terminatesHere && (stop.stopTime != 0 || stop.stopped)"
|
||||||
:class="{
|
:class="{
|
||||||
delayed: stop.departureDelay > 0 && stop.confirmed,
|
delayed: stop.departureDelay > 0 && stop.confirmed,
|
||||||
preponed: stop.departureDelay < 0 && stop.confirmed,
|
preponed: stop.departureDelay < 0 && stop.confirmed
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<span v-if="stop.departureDelay != 0 && stop.confirmed">
|
<span v-if="stop.departureDelay != 0 && stop.confirmed">
|
||||||
@@ -57,13 +61,13 @@ export default defineComponent({
|
|||||||
props: {
|
props: {
|
||||||
stop: {
|
stop: {
|
||||||
type: Object as () => TrainStop,
|
type: Object as () => TrainStop,
|
||||||
required: true,
|
required: true
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
return {};
|
return {};
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="modal_background" @click="closeModal"></div>
|
<div class="modal_background" @click="closeModal"></div>
|
||||||
<div class="modal_content" ref="content" tabindex="0">
|
<div class="modal_content" ref="content" tabindex="0">
|
||||||
<button class="btn exit" @click="closeModal">
|
<button class="btn exit" @click="closeModal">
|
||||||
<img :src="getIcon('exit')" alt="close card" />
|
<img src="/images/icon-exit.svg" alt="close card" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<TrainInfo :train="chosenTrain" :extended="false" ref="trainInfo" />
|
<TrainInfo :train="chosenTrain" :extended="false" ref="trainInfo" />
|
||||||
@@ -14,7 +14,6 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import imageMixin from '../../mixins/imageMixin';
|
|
||||||
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
import modalTrainMixin from '../../mixins/modalTrainMixin';
|
||||||
import trainInfoMixin from '../../mixins/trainInfoMixin';
|
import trainInfoMixin from '../../mixins/trainInfoMixin';
|
||||||
import { useStore } from '../../store/store';
|
import { useStore } from '../../store/store';
|
||||||
@@ -23,11 +22,11 @@ import TrainSchedule from '../TrainsView/TrainSchedule.vue';
|
|||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { TrainInfo, TrainSchedule },
|
components: { TrainInfo, TrainSchedule },
|
||||||
mixins: [trainInfoMixin, modalTrainMixin, imageMixin],
|
mixins: [trainInfoMixin, modalTrainMixin],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isTopBarVisible: false,
|
isTopBarVisible: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -35,7 +34,7 @@ export default defineComponent({
|
|||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
store,
|
store
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -49,12 +48,14 @@ export default defineComponent({
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
handleContentScroll(e: Event) {
|
handleContentScroll(e: Event) {
|
||||||
const trainInfoCompHeight: number = (this.$refs['trainInfo'] as any).$el.getBoundingClientRect().height;
|
const trainInfoCompHeight: number = (
|
||||||
|
this.$refs['trainInfo'] as any
|
||||||
|
).$el.getBoundingClientRect().height;
|
||||||
|
|
||||||
const posTop = (e.target as HTMLElement).scrollTop;
|
const posTop = (e.target as HTMLElement).scrollTop;
|
||||||
this.isTopBarVisible = posTop > trainInfoCompHeight;
|
this.isTopBarVisible = posTop > trainInfoCompHeight;
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -144,7 +145,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
@include smallScreen {
|
@include smallScreen {
|
||||||
|
|
||||||
.modal_content {
|
.modal_content {
|
||||||
max-height: 85vh;
|
max-height: 85vh;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
<img
|
<img
|
||||||
class="train-thumbnail"
|
class="train-thumbnail"
|
||||||
v-else
|
v-else
|
||||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${name.split(':')[0]}${stockType == 'loco-ezt' ? 'rb' : ''}.png`"
|
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${name.split(':')[0]}${
|
||||||
|
stockType == 'loco-ezt' ? 'rb' : ''
|
||||||
|
}.png`"
|
||||||
@error="onImageError"
|
@error="onImageError"
|
||||||
@load="onImageLoad"
|
@load="onImageLoad"
|
||||||
width="220"
|
width="220"
|
||||||
@@ -14,7 +16,6 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import imageMixin from '../../mixins/imageMixin';
|
|
||||||
import { useStore } from '../../store/store';
|
import { useStore } from '../../store/store';
|
||||||
import { RollingStockInfo } from '../../scripts/interfaces/github_api/StockInfoGithubData';
|
import { RollingStockInfo } from '../../scripts/interfaces/github_api/StockInfoGithubData';
|
||||||
|
|
||||||
@@ -22,20 +23,20 @@ export default defineComponent({
|
|||||||
props: {
|
props: {
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
|
|
||||||
onlyFirstSegment: {
|
onlyFirstSegment: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
store: useStore(),
|
store: useStore(),
|
||||||
isNotFound: false,
|
isNotFound: false,
|
||||||
isLoaded: false,
|
isLoaded: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -53,10 +54,12 @@ export default defineComponent({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
Object.keys(this.store.rollingStockData.info).find((type) => {
|
Object.keys(this.store.rollingStockData.info).find((type) => {
|
||||||
return this.store.rollingStockData?.info[type as keyof RollingStockInfo].find((v) => v[0] === this.name.split(':')[0]);
|
return this.store.rollingStockData?.info[type as keyof RollingStockInfo].find(
|
||||||
|
(v) => v[0] === this.name.split(':')[0]
|
||||||
|
);
|
||||||
}) || 'vehicle-unknown'
|
}) || 'vehicle-unknown'
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -68,8 +71,8 @@ export default defineComponent({
|
|||||||
onImageLoad() {
|
onImageLoad() {
|
||||||
this.isNotFound = false;
|
this.isNotFound = false;
|
||||||
this.isLoaded = true;
|
this.isLoaded = true;
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -54,42 +54,42 @@
|
|||||||
</i18n-t>
|
</i18n-t>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="firstPlaceDispatchers.length == 1">
|
<div v-if="topDispatchers.length == 1">
|
||||||
•
|
•
|
||||||
<i18n-t keypath="journal.timetable-stats-most-active-dr">
|
<i18n-t keypath="journal.timetable-stats-most-active-dr">
|
||||||
<template #dispatcher>
|
<template #dispatcher>
|
||||||
<router-link :to="`/journal/dispatchers?dispatcherName=${firstPlaceDispatchers[0].name}`">
|
<router-link :to="`/journal/dispatchers?dispatcherName=${topDispatchers[0].name}`">
|
||||||
<b>{{ firstPlaceDispatchers[0].name }}</b>
|
<b>{{ topDispatchers[0].name }}</b>
|
||||||
</router-link>
|
</router-link>
|
||||||
</template>
|
</template>
|
||||||
<template #count>
|
<template #count>
|
||||||
<b class="text--primary">
|
<b class="text--primary">
|
||||||
{{ firstPlaceDispatchers[0].count }}
|
{{ topDispatchers[0].count }}
|
||||||
{{ $t('journal.timetable-count', firstPlaceDispatchers[0].count) }}
|
{{ $t('journal.timetable-count', topDispatchers[0].count) }}
|
||||||
</b>
|
</b>
|
||||||
</template>
|
</template>
|
||||||
</i18n-t>
|
</i18n-t>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="firstPlaceDispatchers.length > 1">
|
<div v-if="topDispatchers.length > 1">
|
||||||
•
|
•
|
||||||
<i18n-t keypath="journal.timetable-stats-most-active-dr-many">
|
<i18n-t keypath="journal.timetable-stats-most-active-dr-many">
|
||||||
<template #dispatchers>
|
<template #dispatchers>
|
||||||
<span v-for="(disp, i) in firstPlaceDispatchers">
|
<span v-for="(disp, i) in topDispatchers" :key="i">
|
||||||
<span v-if="i == firstPlaceDispatchers.length - 1"> {{ $t('general.and') }} </span>
|
<span v-if="i == topDispatchers.length - 1"> {{ $t('general.and') }} </span>
|
||||||
|
|
||||||
<router-link :to="`/journal/dispatchers?dispatcherName=${disp.name}`">
|
<router-link :to="`/journal/dispatchers?dispatcherName=${disp.name}`">
|
||||||
<b>{{ disp.name }}</b>
|
<b>{{ disp.name }}</b>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<span v-if="i < firstPlaceDispatchers.length - 2">, </span>
|
<span v-if="i < topDispatchers.length - 2">, </span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #count>
|
<template #count>
|
||||||
<b class="text--primary">
|
<b class="text--primary">
|
||||||
{{ firstPlaceDispatchers[0].count }}
|
{{ topDispatchers[0].count }}
|
||||||
{{ $t('journal.timetable-count', firstPlaceDispatchers[0].count) }}
|
{{ $t('journal.timetable-count', topDispatchers[0].count) }}
|
||||||
</b>
|
</b>
|
||||||
</template>
|
</template>
|
||||||
</i18n-t>
|
</i18n-t>
|
||||||
@@ -99,7 +99,9 @@
|
|||||||
•
|
•
|
||||||
<i18n-t keypath="journal.timetable-stats-longest-duties">
|
<i18n-t keypath="journal.timetable-stats-longest-duties">
|
||||||
<template #dispatcher>
|
<template #dispatcher>
|
||||||
<router-link :to="`/journal/dispatchers?dispatcherName=${stats.longestDuties[0].name}`">
|
<router-link
|
||||||
|
:to="`/journal/dispatchers?dispatcherName=${stats.longestDuties[0].name}`"
|
||||||
|
>
|
||||||
<b>{{ stats.longestDuties[0].name }}</b>
|
<b>{{ stats.longestDuties[0].name }}</b>
|
||||||
</router-link>
|
</router-link>
|
||||||
</template>
|
</template>
|
||||||
@@ -133,7 +135,10 @@ import axios from 'axios';
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import dateMixin from '../../mixins/dateMixin';
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
import { DataStatus } from '../../scripts/enums/DataStatus';
|
import { DataStatus } from '../../scripts/enums/DataStatus';
|
||||||
import { ITimetablesDailyStats, ITimetablesDailyStatsResponse } from '../../scripts/interfaces/api/StatsAPIData';
|
import {
|
||||||
|
ITimetablesDailyStats,
|
||||||
|
ITimetablesDailyStatsResponse
|
||||||
|
} from '../../scripts/interfaces/api/StatsAPIData';
|
||||||
import { URLs } from '../../scripts/utils/apiURLs';
|
import { URLs } from '../../scripts/utils/apiURLs';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@@ -156,8 +161,8 @@ export default defineComponent({
|
|||||||
timetableRouteDistance: 0,
|
timetableRouteDistance: 0,
|
||||||
longestDuties: [],
|
longestDuties: [],
|
||||||
mostActiveDrivers: [],
|
mostActiveDrivers: [],
|
||||||
mostActiveDispatchers: [],
|
mostActiveDispatchers: []
|
||||||
} as ITimetablesDailyStats,
|
} as ITimetablesDailyStats
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -171,12 +176,12 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
firstPlaceDispatchers() {
|
topDispatchers() {
|
||||||
if (this.stats.mostActiveDispatchers.length == 0) return [];
|
if (this.stats.mostActiveDispatchers.length == 0) return [];
|
||||||
const maxCount = this.stats.mostActiveDispatchers[0].count;
|
const maxCount = this.stats.mostActiveDispatchers[0].count;
|
||||||
|
|
||||||
return this.stats.mostActiveDispatchers.filter((disp) => disp.count === maxCount);
|
return this.stats.mostActiveDispatchers.filter((disp) => disp.count === maxCount);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -197,7 +202,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
mostActiveDispatchers: res.mostActiveDispatchers,
|
mostActiveDispatchers: res.mostActiveDispatchers,
|
||||||
mostActiveDrivers: res.mostActiveDrivers,
|
mostActiveDrivers: res.mostActiveDrivers,
|
||||||
longestDuties: res.longestDuties,
|
longestDuties: res.longestDuties
|
||||||
};
|
};
|
||||||
|
|
||||||
this.statsStatus = DataStatus.Loaded;
|
this.statsStatus = DataStatus.Loaded;
|
||||||
@@ -218,8 +223,8 @@ export default defineComponent({
|
|||||||
stopFetchingDailyStats() {
|
stopFetchingDailyStats() {
|
||||||
clearInterval(this.intervalId);
|
clearInterval(this.intervalId);
|
||||||
this.intervalId = -1;
|
this.intervalId = -1;
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -238,13 +243,8 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
@include smallScreen {
|
@include smallScreen {
|
||||||
.daily-stats {
|
|
||||||
text-align: justify;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -36,8 +36,9 @@
|
|||||||
|
|
||||||
<h3>OSTATNIE WYSTAWIONE ROZKŁADY</h3>
|
<h3>OSTATNIE WYSTAWIONE ROZKŁADY</h3>
|
||||||
<div class="last-timetables">
|
<div class="last-timetables">
|
||||||
<div class="timetable-row" v-for="timetable in timetables">
|
<div class="timetable-row" v-for="timetable in timetables" :key="timetable.id">
|
||||||
#{{ timetable.timetableId }} | <b>{{ timetable.trainCategoryCode }} {{ timetable.trainNo }}</b> |
|
#{{ timetable.timetableId }} |
|
||||||
|
<b>{{ timetable.trainCategoryCode }} {{ timetable.trainNo }}</b> |
|
||||||
{{ timetable.driverName }} ({{ timetable.routeDistance }}km)
|
{{ timetable.driverName }} ({{ timetable.routeDistance }}km)
|
||||||
<div>{{ timetable.route.replace('|', ' > ') }}</div>
|
<div>{{ timetable.route.replace('|', ' > ') }}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -49,9 +50,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { computed, defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { DispatcherStatsAPIData } from '../../scripts/interfaces/api/DispatcherStatsAPIData';
|
import { DispatcherStatsAPIData } from '../../scripts/interfaces/api/DispatcherStatsAPIData';
|
||||||
import { TimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData';
|
import { TimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData';
|
||||||
import { URLs } from '../../scripts/utils/apiURLs';
|
import { URLs } from '../../scripts/utils/apiURLs';
|
||||||
@@ -64,15 +64,8 @@ export default defineComponent({
|
|||||||
setup() {
|
setup() {
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
|
||||||
const statsData2 = computed(async () => {
|
|
||||||
return await (
|
|
||||||
await axios.get(`${URLs.stacjownikAPI}/api/getDispatcherInfo?name=${store.dispatcherStatsName}`)
|
|
||||||
).data;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
store,
|
store
|
||||||
statsData2,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -80,7 +73,7 @@ export default defineComponent({
|
|||||||
return {
|
return {
|
||||||
cardVisible: false,
|
cardVisible: false,
|
||||||
lastDispatcherName: '',
|
lastDispatcherName: '',
|
||||||
timetables: [] as TimetableHistory[],
|
timetables: [] as TimetableHistory[]
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -98,18 +91,22 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const statsData: DispatcherStatsAPIData = await (
|
const statsData: DispatcherStatsAPIData = await (
|
||||||
await axios.get(`${URLs.stacjownikAPI}/api/getDispatcherInfo?name=${this.store.dispatcherStatsName}`)
|
await axios.get(
|
||||||
|
`${URLs.stacjownikAPI}/api/getDispatcherInfo?name=${this.store.dispatcherStatsName}`
|
||||||
|
)
|
||||||
).data;
|
).data;
|
||||||
|
|
||||||
const timetables: TimetableHistory[] = await (
|
const timetables: TimetableHistory[] = await (
|
||||||
await axios.get(`${URLs.stacjownikAPI}/api/getTimetables?authorName=${this.store.dispatcherStatsName}`)
|
await axios.get(
|
||||||
|
`${URLs.stacjownikAPI}/api/getTimetables?authorName=${this.store.dispatcherStatsName}`
|
||||||
|
)
|
||||||
).data;
|
).data;
|
||||||
|
|
||||||
this.timetables = timetables;
|
this.timetables = timetables;
|
||||||
this.store.dispatcherStatsData = statsData;
|
this.store.dispatcherStatsData = statsData;
|
||||||
this.lastDispatcherName = this.store.dispatcherStatsName;
|
this.lastDispatcherName = this.store.dispatcherStatsName;
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -163,11 +160,7 @@ h3 {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.last-timetables {
|
.last-timetables {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -32,13 +32,17 @@
|
|||||||
<transition-group name="list-anim">
|
<transition-group name="list-anim">
|
||||||
<tr v-for="historyItem in dispatcherHistory" :key="historyItem.id">
|
<tr v-for="historyItem in dispatcherHistory" :key="historyItem.id">
|
||||||
<td>
|
<td>
|
||||||
<router-link :to="`/journal/dispatchers?sceneryName=${historyItem.stationName}`">
|
<router-link
|
||||||
|
:to="`/journal/dispatchers?sceneryName=${historyItem.stationName}`"
|
||||||
|
>
|
||||||
<b>{{ historyItem.stationName }}</b>
|
<b>{{ historyItem.stationName }}</b>
|
||||||
</router-link>
|
</router-link>
|
||||||
</td>
|
</td>
|
||||||
<td>#{{ historyItem.stationHash }}</td>
|
<td>#{{ historyItem.stationHash }}</td>
|
||||||
<td>
|
<td>
|
||||||
<router-link :to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`">
|
<router-link
|
||||||
|
:to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`"
|
||||||
|
>
|
||||||
<b>{{ historyItem.dispatcherName }}</b>
|
<b>{{ historyItem.dispatcherName }}</b>
|
||||||
</router-link>
|
</router-link>
|
||||||
</td>
|
</td>
|
||||||
@@ -46,7 +50,12 @@
|
|||||||
<b
|
<b
|
||||||
v-if="historyItem.dispatcherLevel !== null"
|
v-if="historyItem.dispatcherLevel !== null"
|
||||||
class="level-badge dispatcher"
|
class="level-badge dispatcher"
|
||||||
:style="calculateExpStyle(historyItem.dispatcherLevel, historyItem.dispatcherIsSupporter)"
|
:style="
|
||||||
|
calculateExpStyle(
|
||||||
|
historyItem.dispatcherLevel,
|
||||||
|
historyItem.dispatcherIsSupporter
|
||||||
|
)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
|
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
|
||||||
</b>
|
</b>
|
||||||
@@ -107,7 +116,6 @@ import { defineComponent, PropType } from 'vue';
|
|||||||
import dateMixin from '../../mixins/dateMixin';
|
import dateMixin from '../../mixins/dateMixin';
|
||||||
import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIData';
|
import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIData';
|
||||||
import styleMixin from '../../mixins/styleMixin';
|
import styleMixin from '../../mixins/styleMixin';
|
||||||
import imageMixin from '../../mixins/imageMixin';
|
|
||||||
import { DataStatus } from '../../scripts/enums/DataStatus';
|
import { DataStatus } from '../../scripts/enums/DataStatus';
|
||||||
import { useStore } from '../../store/store';
|
import { useStore } from '../../store/store';
|
||||||
import Loading from '../Global/Loading.vue';
|
import Loading from '../Global/Loading.vue';
|
||||||
@@ -117,32 +125,32 @@ import AddDataButton from '../Global/AddDataButton.vue';
|
|||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { Loading, AddDataButton },
|
components: { Loading, AddDataButton },
|
||||||
|
|
||||||
mixins: [dateMixin, styleMixin, imageMixin],
|
mixins: [dateMixin, styleMixin],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
dispatcherHistory: {
|
dispatcherHistory: {
|
||||||
type: Array as PropType<DispatcherHistory[]>,
|
type: Array as PropType<DispatcherHistory[]>,
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
scrollNoMoreData: {
|
scrollNoMoreData: {
|
||||||
type: Boolean,
|
type: Boolean
|
||||||
},
|
},
|
||||||
scrollDataLoaded: {
|
scrollDataLoaded: {
|
||||||
type: Boolean,
|
type: Boolean
|
||||||
},
|
},
|
||||||
addHistoryData: {
|
addHistoryData: {
|
||||||
type: Function as PropType<() => void>,
|
type: Function as PropType<() => void>
|
||||||
},
|
},
|
||||||
dataStatus: {
|
dataStatus: {
|
||||||
type: Number as PropType<DataStatus>,
|
type: Number as PropType<DataStatus>
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
DataStatus,
|
DataStatus,
|
||||||
store: useStore(),
|
store: useStore(),
|
||||||
regions,
|
regions
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -150,13 +158,17 @@ export default defineComponent({
|
|||||||
computedDispatcherHistory() {
|
computedDispatcherHistory() {
|
||||||
console.log(this.dispatcherHistory.length);
|
console.log(this.dispatcherHistory.length);
|
||||||
|
|
||||||
return this.dispatcherHistory.reduce((acc, historyItem, i) => {
|
return this.dispatcherHistory.reduce(
|
||||||
if (this.isAnotherDay(i - 1, i)) acc.push(new Date(historyItem.timestampFrom).toLocaleDateString('pl-PL'));
|
(acc, historyItem, i) => {
|
||||||
|
if (this.isAnotherDay(i - 1, i))
|
||||||
|
acc.push(new Date(historyItem.timestampFrom).toLocaleDateString('pl-PL'));
|
||||||
acc.push(historyItem);
|
acc.push(historyItem);
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, [] as (DispatcherHistory | string)[]);
|
|
||||||
},
|
},
|
||||||
|
[] as (DispatcherHistory | string)[]
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -173,8 +185,8 @@ export default defineComponent({
|
|||||||
new Date(this.dispatcherHistory[prevIndex].timestampFrom).getDate() !=
|
new Date(this.dispatcherHistory[prevIndex].timestampFrom).getDate() !=
|
||||||
new Date(this.dispatcherHistory[currIndex].timestampFrom).getDate()
|
new Date(this.dispatcherHistory[currIndex].timestampFrom).getDate()
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,17 @@
|
|||||||
<div class="journal-stats">
|
<div class="journal-stats">
|
||||||
<span v-if="store.driverStatsData">
|
<span v-if="store.driverStatsData">
|
||||||
<h3>
|
<h3>
|
||||||
{{ $t('journal.stats-title') }} <span class="text--primary">{{ store.driverStatsName.toUpperCase() }}</span>
|
{{ $t('journal.stats-title') }}
|
||||||
|
<span class="text--primary">{{ store.driverStatsName.toUpperCase() }}</span>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div class="info-stats">
|
<div class="info-stats">
|
||||||
<span class="stat-badge">
|
<span class="stat-badge">
|
||||||
<span>{{ $t('journal.stats-timetables') }}</span>
|
<span>{{ $t('journal.stats-timetables') }}</span>
|
||||||
<span>{{ store.driverStatsData._count.fulfilled }} / {{ store.driverStatsData._count._all }}</span>
|
<span
|
||||||
|
>{{ store.driverStatsData._count.fulfilled }} /
|
||||||
|
{{ store.driverStatsData._count._all }}</span
|
||||||
|
>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="stat-badge">
|
<span class="stat-badge">
|
||||||
@@ -39,7 +43,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<b v-else-if="store.driverStatsStatus == DataStatus.Loading">{{ $t('journal.stats-loading') }}</b>
|
<b v-else-if="store.driverStatsStatus == DataStatus.Loading">{{
|
||||||
|
$t('journal.stats-loading')
|
||||||
|
}}</b>
|
||||||
<b v-else-if="store.driverStatsStatus == DataStatus.Error">
|
<b v-else-if="store.driverStatsStatus == DataStatus.Error">
|
||||||
{{ $t('journal.stats-error ') }}
|
{{ $t('journal.stats-error ') }}
|
||||||
</b>
|
</b>
|
||||||
@@ -56,9 +62,9 @@ export default defineComponent({
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
store: useStore(),
|
store: useStore(),
|
||||||
DataStatus,
|
DataStatus
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -3,24 +3,28 @@
|
|||||||
<div class="bg" v-if="showOptions" @click="showOptions = false"></div>
|
<div class="bg" v-if="showOptions" @click="showOptions = false"></div>
|
||||||
|
|
||||||
<div class="actions-bar">
|
<div class="actions-bar">
|
||||||
<button class="filter-button btn--filled btn--image" @click="showOptions = !showOptions" ref="button">
|
<button
|
||||||
<img :src="getIcon('filter2')" alt="Open filters" />
|
class="filter-button btn--filled btn--image"
|
||||||
|
@click="showOptions = !showOptions"
|
||||||
|
ref="button"
|
||||||
|
>
|
||||||
|
<img src="/images/icon-filter2.svg" alt="Open filters" />
|
||||||
{{ $t('options.filters') }} [F]
|
{{ $t('options.filters') }} [F]
|
||||||
<span class="active-indicator" v-if="currentOptionsActive"></span>
|
<span class="active-indicator" v-if="currentOptionsActive"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="filter-button btn--filled btn--image" @click="refreshData">
|
<button class="filter-button btn--filled btn--image" @click="refreshData">
|
||||||
<img :src="getIcon('refresh')" alt="Refresh data" />
|
<img src="/images/icon-refresh.svg" alt="Refresh data" />
|
||||||
{{ $t('general.refresh') }}
|
{{ $t('general.refresh') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<datalist id="search-driver">
|
<datalist id="search-driver">
|
||||||
<option v-for="sugg in driverSuggestions" :value="sugg"></option>
|
<option v-for="(sugg, i) in driverSuggestions" :key="i" :value="sugg"></option>
|
||||||
</datalist>
|
</datalist>
|
||||||
|
|
||||||
<datalist id="search-dispatcher">
|
<datalist id="search-dispatcher">
|
||||||
<option v-for="sugg in dispatcherSuggestions" :value="sugg"></option>
|
<option v-for="(sugg, i) in dispatcherSuggestions" :key="i" :value="sugg"></option>
|
||||||
</datalist>
|
</datalist>
|
||||||
|
|
||||||
<transition name="options-anim">
|
<transition name="options-anim">
|
||||||
@@ -29,7 +33,9 @@
|
|||||||
<h1 class="option-title">{{ $t('options.search-title') }}</h1>
|
<h1 class="option-title">{{ $t('options.search-title') }}</h1>
|
||||||
<div class="search_content">
|
<div class="search_content">
|
||||||
<div class="search" v-for="(_, propName) in searchersValues" :key="propName">
|
<div class="search" v-for="(_, propName) in searchersValues" :key="propName">
|
||||||
<label v-if="propName == 'search-date'" for="date">{{ $t(`options.search-${optionsType}-date`) }}</label>
|
<label v-if="propName == 'search-date'" for="date">{{
|
||||||
|
$t(`options.search-${optionsType}-date`)
|
||||||
|
}}</label>
|
||||||
|
|
||||||
<div class="search-box">
|
<div class="search-box">
|
||||||
<input
|
<input
|
||||||
@@ -45,7 +51,11 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<button class="search-exit" v-if="propName != 'search-date'">
|
<button class="search-exit" v-if="propName != 'search-date'">
|
||||||
<img :src="getIcon('exit')" alt="exit-icon" @click="onInputClear(propName)" />
|
<img
|
||||||
|
src="/images/icon-exit.svg"
|
||||||
|
alt="exit-icon"
|
||||||
|
@click="onInputClear(propName)"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -53,7 +63,7 @@
|
|||||||
|
|
||||||
<h1 class="option-title">{{ $t('options.sort-title') }}</h1>
|
<h1 class="option-title">{{ $t('options.sort-title') }}</h1>
|
||||||
<div class="options_sorters">
|
<div class="options_sorters">
|
||||||
<div v-for="opt in translatedSorterOptions">
|
<div v-for="opt in translatedSorterOptions" :key="opt.id">
|
||||||
<button
|
<button
|
||||||
class="sort-option btn--option"
|
class="sort-option btn--option"
|
||||||
:data-selected="opt.id == sorterActive.id"
|
:data-selected="opt.id == sorterActive.id"
|
||||||
@@ -67,12 +77,13 @@
|
|||||||
<h1 class="option-title" v-if="filters.length != 0">{{ $t('options.filter-title') }}</h1>
|
<h1 class="option-title" v-if="filters.length != 0">{{ $t('options.filter-title') }}</h1>
|
||||||
|
|
||||||
<div class="options_filter-sections" v-if="filters.length != 0 && filterList">
|
<div class="options_filter-sections" v-if="filters.length != 0 && filterList">
|
||||||
<section class="filter-section" v-for="section in JournalFilterSection">
|
<section class="filter-section" v-for="section in JournalFilterSection" :key="section">
|
||||||
<p>{{ $t(`options.filter-section-${section}`) }}</p>
|
<p>{{ $t(`options.filter-section-${section}`) }}</p>
|
||||||
|
|
||||||
<div class="options_filters">
|
<div class="options_filters">
|
||||||
<button
|
<button
|
||||||
v-for="filter in filterList.filter((f) => f.filterSection == section)"
|
v-for="filter in filterList.filter((f) => f.filterSection == section)"
|
||||||
|
:key="filter.id"
|
||||||
class="filter-option btn--option"
|
class="filter-option btn--option"
|
||||||
:class="{ checked: filter.isActive }"
|
:class="{ checked: filter.isActive }"
|
||||||
:id="filter.id"
|
:id="filter.id"
|
||||||
@@ -101,47 +112,43 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { defineComponent, inject, PropType } from 'vue';
|
import { defineComponent, inject, PropType } from 'vue';
|
||||||
import imageMixin from '../../mixins/imageMixin';
|
|
||||||
import keyMixin from '../../mixins/keyMixin';
|
import keyMixin from '../../mixins/keyMixin';
|
||||||
import { DataStatus } from '../../scripts/enums/DataStatus';
|
import { DataStatus } from '../../scripts/enums/DataStatus';
|
||||||
import { DriverStatsAPIData } from '../../scripts/interfaces/api/DriverStatsAPIData';
|
import { DriverStatsAPIData } from '../../scripts/interfaces/api/DriverStatsAPIData';
|
||||||
import { URLs } from '../../scripts/utils/apiURLs';
|
import { URLs } from '../../scripts/utils/apiURLs';
|
||||||
import { useStore } from '../../store/store';
|
import { useStore } from '../../store/store';
|
||||||
import ActionButton from '../Global/ActionButton.vue';
|
|
||||||
import SelectBox from '../Global/SelectBox.vue';
|
|
||||||
import { JournalFilterSection } from '../../scripts/enums/JournalFilterType';
|
import { JournalFilterSection } from '../../scripts/enums/JournalFilterType';
|
||||||
import { JournalFilter } from '../../scripts/types/JournalTimetablesTypes';
|
import { JournalFilter } from '../../scripts/types/JournalTimetablesTypes';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { SelectBox, ActionButton },
|
|
||||||
emits: ['onSearchConfirm', 'onOptionsReset', 'onRefreshData'],
|
emits: ['onSearchConfirm', 'onOptionsReset', 'onRefreshData'],
|
||||||
mixins: [imageMixin, keyMixin],
|
mixins: [keyMixin],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
sorterOptionIds: {
|
sorterOptionIds: {
|
||||||
type: Array as PropType<Array<string>>,
|
type: Array as PropType<Array<string>>,
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
|
|
||||||
filters: {
|
filters: {
|
||||||
type: Array as PropType<JournalFilter[]>,
|
type: Array as PropType<JournalFilter[]>,
|
||||||
default: [],
|
default: () => []
|
||||||
},
|
},
|
||||||
|
|
||||||
dataStatus: {
|
dataStatus: {
|
||||||
type: Number as PropType<DataStatus>,
|
type: Number as PropType<DataStatus>,
|
||||||
default: DataStatus.Initialized,
|
default: DataStatus.Initialized
|
||||||
},
|
},
|
||||||
|
|
||||||
currentOptionsActive: {
|
currentOptionsActive: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false
|
||||||
},
|
},
|
||||||
|
|
||||||
optionsType: {
|
optionsType: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
@@ -155,7 +162,7 @@ export default defineComponent({
|
|||||||
searchTimeout: 0,
|
searchTimeout: 0,
|
||||||
store: useStore(),
|
store: useStore(),
|
||||||
|
|
||||||
DataStatus,
|
DataStatus
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -163,26 +170,21 @@ export default defineComponent({
|
|||||||
return {
|
return {
|
||||||
searchersValues: inject('searchersValues') as { [key: string]: string },
|
searchersValues: inject('searchersValues') as { [key: string]: string },
|
||||||
sorterActive: inject('sorterActive') as { id: string | number; dir: number },
|
sorterActive: inject('sorterActive') as { id: string | number; dir: number },
|
||||||
// journalFilterActive: inject('journalFilterActive') as JournalFilter,
|
filterList: inject('filterList') as JournalFilter[] | undefined
|
||||||
filterList: inject('filterList') as JournalFilter[] | undefined,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
driverStatsName() {
|
|
||||||
return this.store.driverStatsName;
|
|
||||||
},
|
|
||||||
|
|
||||||
translatedSorterOptions() {
|
translatedSorterOptions() {
|
||||||
return this.$props.sorterOptionIds.map((id) => ({
|
return this.$props.sorterOptionIds.map((id) => ({
|
||||||
id,
|
id,
|
||||||
value: this.$t(`options.sort-${id}`),
|
value: this.$t(`options.sort-${id}`)
|
||||||
}));
|
}));
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
async driverStatsName(value: string) {
|
async 'store.driverStatsName'() {
|
||||||
await this.fetchDriverStats();
|
await this.fetchDriverStats();
|
||||||
|
|
||||||
// if (value) this.store.currentStatsTab = 'driver';
|
// if (value) this.store.currentStatsTab = 'driver';
|
||||||
@@ -202,7 +204,7 @@ export default defineComponent({
|
|||||||
if (value.length < 3) return;
|
if (value.length < 3) return;
|
||||||
|
|
||||||
this.startSearchTimeout('dispatcher', value);
|
this.startSearchTimeout('dispatcher', value);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -218,7 +220,9 @@ export default defineComponent({
|
|||||||
this.store.driverStatsStatus = DataStatus.Loading;
|
this.store.driverStatsStatus = DataStatus.Loading;
|
||||||
|
|
||||||
const statsData: DriverStatsAPIData = await (
|
const statsData: DriverStatsAPIData = await (
|
||||||
await axios.get(`${URLs.stacjownikAPI}/api/getDriverInfo?name=${this.store.driverStatsName}`)
|
await axios.get(
|
||||||
|
`${URLs.stacjownikAPI}/api/getDriverInfo?name=${this.store.driverStatsName}`
|
||||||
|
)
|
||||||
).data;
|
).data;
|
||||||
|
|
||||||
this.store.driverStatsData = statsData;
|
this.store.driverStatsData = statsData;
|
||||||
@@ -268,7 +272,9 @@ export default defineComponent({
|
|||||||
|
|
||||||
onFilterChange(filter: JournalFilter) {
|
onFilterChange(filter: JournalFilter) {
|
||||||
// this.journalFilterActive = filter;
|
// this.journalFilterActive = filter;
|
||||||
this.filterList?.filter((f) => f.filterSection === filter.filterSection).forEach((f) => (f.isActive = false));
|
this.filterList
|
||||||
|
?.filter((f) => f.filterSection === filter.filterSection)
|
||||||
|
.forEach((f) => (f.isActive = false));
|
||||||
filter.isActive = true;
|
filter.isActive = true;
|
||||||
|
|
||||||
this.$emit('onSearchConfirm');
|
this.$emit('onSearchConfirm');
|
||||||
@@ -290,8 +296,8 @@ export default defineComponent({
|
|||||||
|
|
||||||
onResetButtonClick() {
|
onResetButtonClick() {
|
||||||
this.$emit('onOptionsReset');
|
this.$emit('onOptionsReset');
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
<button
|
<button
|
||||||
v-for="tab in data.tabs"
|
v-for="tab in data.tabs"
|
||||||
|
:key="tab.name"
|
||||||
class="btn--filled"
|
class="btn--filled"
|
||||||
:data-selected="tab.name == store.currentStatsTab && areStatsOpen"
|
:data-selected="tab.name == store.currentStatsTab && areStatsOpen"
|
||||||
:data-inactive="tab.inactive"
|
:data-inactive="tab.inactive"
|
||||||
@@ -16,7 +17,10 @@
|
|||||||
|
|
||||||
<div class="stats-tab" v-show="areStatsOpen">
|
<div class="stats-tab" v-show="areStatsOpen">
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
<JournalDailyStats v-if="store.currentStatsTab == 'daily'" @toggleStatsOpen="toggleStatsOpen" />
|
<JournalDailyStats
|
||||||
|
v-if="store.currentStatsTab == 'daily'"
|
||||||
|
@toggleStatsOpen="toggleStatsOpen"
|
||||||
|
/>
|
||||||
<JournalDriverStats v-else-if="store.currentStatsTab == 'driver'" />
|
<JournalDriverStats v-else-if="store.currentStatsTab == 'driver'" />
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
</div>
|
</div>
|
||||||
@@ -24,7 +28,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, KeepAlive, onMounted, reactive, Ref, ref, watch } from 'vue';
|
import { computed, onMounted, reactive, Ref, ref, watch } from 'vue';
|
||||||
import { useStore } from '../../store/store';
|
import { useStore } from '../../store/store';
|
||||||
import JournalDailyStats from './DailyStats.vue';
|
import JournalDailyStats from './DailyStats.vue';
|
||||||
import JournalDriverStats from './JournalDriverStats.vue';
|
import JournalDriverStats from './JournalDriverStats.vue';
|
||||||
@@ -44,19 +48,19 @@ let data = reactive({
|
|||||||
tabs: [
|
tabs: [
|
||||||
{
|
{
|
||||||
name: 'daily',
|
name: 'daily',
|
||||||
titlePath: 'journal.daily-stats-title',
|
titlePath: 'journal.daily-stats-title'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'driver',
|
name: 'driver',
|
||||||
titlePath: 'journal.driver-stats-title',
|
titlePath: 'journal.driver-stats-title'
|
||||||
// inactive: true,
|
// inactive: true,
|
||||||
},
|
}
|
||||||
] as { name: TStatTab; titlePath: string; inactive?: boolean }[],
|
] as { name: TStatTab; titlePath: string; inactive?: boolean }[]
|
||||||
});
|
});
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
function onTabButtonClick(tab: TStatTab) {
|
function onTabButtonClick(tab: TStatTab) {
|
||||||
if (lastClickedTab.value == tab || !areStatsOpen.value) areStatsOpen.value = !areStatsOpen.value;
|
if (lastClickedTab.value == tab || !lastClickedTab.value || !areStatsOpen.value) areStatsOpen.value = !areStatsOpen.value;
|
||||||
|
|
||||||
if (tab == 'daily') {
|
if (tab == 'daily') {
|
||||||
StorageManager.setBooleanValue('dailyStatsOpen', areStatsOpen.value);
|
StorageManager.setBooleanValue('dailyStatsOpen', areStatsOpen.value);
|
||||||
@@ -115,4 +119,3 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,9 @@
|
|||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
<div class="journal_warning" v-if="scrollNoMoreData">{{ $t('journal.no-further-data') }}</div>
|
<div class="journal_warning" v-if="scrollNoMoreData">{{ $t('journal.no-further-data') }}</div>
|
||||||
<div class="journal_warning" v-else-if="!scrollDataLoaded">{{ $t('journal.loading-further-data') }}</div>
|
<div class="journal_warning" v-else-if="!scrollDataLoaded">
|
||||||
|
{{ $t('journal.loading-further-data') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -41,38 +43,37 @@ import { TimetableHistory } from '../../../scripts/interfaces/api/TimetablesAPID
|
|||||||
import { useStore } from '../../../store/store';
|
import { useStore } from '../../../store/store';
|
||||||
|
|
||||||
import Loading from '../../Global/Loading.vue';
|
import Loading from '../../Global/Loading.vue';
|
||||||
import ProgressBar from '../../Global/ProgressBar.vue';
|
|
||||||
import AddDataButton from '../../Global/AddDataButton.vue';
|
import AddDataButton from '../../Global/AddDataButton.vue';
|
||||||
import TimetableHistoryList from './TimetableHistoryList.vue';
|
import TimetableHistoryList from './TimetableHistoryList.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { ProgressBar, Loading, AddDataButton, TimetableHistoryList },
|
components: { Loading, AddDataButton, TimetableHistoryList },
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
timetableHistory: {
|
timetableHistory: {
|
||||||
type: Array as PropType<TimetableHistory[]>,
|
type: Array as PropType<TimetableHistory[]>,
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
scrollNoMoreData: {
|
scrollNoMoreData: {
|
||||||
type: Boolean,
|
type: Boolean
|
||||||
},
|
},
|
||||||
scrollDataLoaded: {
|
scrollDataLoaded: {
|
||||||
type: Boolean,
|
type: Boolean
|
||||||
},
|
},
|
||||||
addHistoryData: {
|
addHistoryData: {
|
||||||
type: Function as PropType<() => void>,
|
type: Function as PropType<() => void>
|
||||||
},
|
},
|
||||||
dataStatus: {
|
dataStatus: {
|
||||||
type: Number as PropType<DataStatus>,
|
type: Number as PropType<DataStatus>
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
DataStatus,
|
DataStatus,
|
||||||
store: useStore(),
|
store: useStore()
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,11 @@
|
|||||||
<span class="badge">
|
<span class="badge">
|
||||||
<span>{{ $t('journal.stock-length') }}</span>
|
<span>{{ $t('journal.stock-length') }}</span>
|
||||||
<span>
|
<span>
|
||||||
{{ currentHistoryIndex == 0 ? timetable.stockLength : stockHistory[currentHistoryIndex].stockLength || timetable.stockLength }}m
|
{{
|
||||||
|
currentHistoryIndex == 0
|
||||||
|
? timetable.stockLength
|
||||||
|
: stockHistory[currentHistoryIndex].stockLength || timetable.stockLength
|
||||||
|
}}m
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
@@ -26,7 +30,11 @@
|
|||||||
<span>{{ $t('journal.stock-mass') }}</span>
|
<span>{{ $t('journal.stock-mass') }}</span>
|
||||||
<span>
|
<span>
|
||||||
{{
|
{{
|
||||||
Math.floor((currentHistoryIndex == 0 ? timetable.stockMass! : stockHistory[currentHistoryIndex].stockMass || timetable.stockMass) / 1000)
|
Math.floor(
|
||||||
|
(currentHistoryIndex == 0
|
||||||
|
? timetable.stockMass!
|
||||||
|
: stockHistory[currentHistoryIndex].stockMass || timetable.stockMass) / 1000
|
||||||
|
)
|
||||||
}}t
|
}}t
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -34,13 +42,26 @@
|
|||||||
|
|
||||||
<!-- Historia zmian w składzie -->
|
<!-- Historia zmian w składzie -->
|
||||||
<div class="stock-history" v-if="stockHistory.length > 1">
|
<div class="stock-history" v-if="stockHistory.length > 1">
|
||||||
<button class="btn--action" v-for="(sh, i) in stockHistory" :data-checked="i == currentHistoryIndex" @click.stop="currentHistoryIndex = i">
|
<button
|
||||||
|
v-for="(sh, i) in stockHistory"
|
||||||
|
:key="i"
|
||||||
|
class="btn--action"
|
||||||
|
:data-checked="i == currentHistoryIndex"
|
||||||
|
@click.stop="currentHistoryIndex = i"
|
||||||
|
>
|
||||||
{{ sh.updatedAt }}
|
{{ sh.updatedAt }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <StockList :trainStockList="currentHistoryIndex == 0 ? timetable.stockString : stockHistory[currentHistoryIndex].stockString).split(';')" /> -->
|
<!-- <StockList :trainStockList="currentHistoryIndex == 0 ? timetable.stockString : stockHistory[currentHistoryIndex].stockString).split(';')" /> -->
|
||||||
<StockList :trainStockList="(currentHistoryIndex == 0 ? timetable.stockString : stockHistory[currentHistoryIndex].stockString).split(';') " />
|
<StockList
|
||||||
|
:trainStockList="
|
||||||
|
(currentHistoryIndex == 0
|
||||||
|
? timetable.stockString
|
||||||
|
: stockHistory[currentHistoryIndex].stockString
|
||||||
|
).split(';')
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- <ul class="stock-list">
|
<!-- <ul class="stock-list">
|
||||||
<li
|
<li
|
||||||
@@ -57,25 +78,23 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PropType, defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import { TimetableHistory } from '../../../scripts/interfaces/api/TimetablesAPIData';
|
import { TimetableHistory } from '../../../scripts/interfaces/api/TimetablesAPIData';
|
||||||
import imageMixin from '../../../mixins/imageMixin';
|
|
||||||
import TrainThumbnail from '../../Global/TrainThumbnail.vue';
|
|
||||||
import StockList from '../../Global/StockList.vue';
|
import StockList from '../../Global/StockList.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [imageMixin],
|
components: { StockList },
|
||||||
props: {
|
props: {
|
||||||
showExtraInfo: {
|
showExtraInfo: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
timetable: {
|
timetable: {
|
||||||
type: Object as PropType<TimetableHistory>,
|
type: Object as PropType<TimetableHistory>,
|
||||||
required: true,
|
required: true
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
currentHistoryIndex: 0,
|
currentHistoryIndex: 0
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -88,22 +107,21 @@ export default defineComponent({
|
|||||||
return {
|
return {
|
||||||
updatedAt: new Date(Number(historyData[0])).toLocaleTimeString(this.$i18n.locale, {
|
updatedAt: new Date(Number(historyData[0])).toLocaleTimeString(this.$i18n.locale, {
|
||||||
hour: '2-digit',
|
hour: '2-digit',
|
||||||
minute: '2-digit',
|
minute: '2-digit'
|
||||||
}),
|
}),
|
||||||
stockString: historyData[1],
|
stockString: historyData[1],
|
||||||
stockMass: Number(historyData[2]) || undefined,
|
stockMass: Number(historyData[2]) || undefined,
|
||||||
stockLength: Number(historyData[3]) || undefined,
|
stockLength: Number(historyData[3]) || undefined
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onImageError(e: Event) {
|
onImageError(e: Event) {
|
||||||
const imageEl = e.target as HTMLImageElement;
|
const imageEl = e.target as HTMLImageElement;
|
||||||
imageEl.src = this.getImage('unknown.png');
|
imageEl.src = '/images/icon-unknown.png';
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
components: { TrainThumbnail, StockList },
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
:class="{
|
:class="{
|
||||||
fulfilled: timetable.fulfilled,
|
fulfilled: timetable.fulfilled,
|
||||||
terminated: timetable.terminated && !timetable.fulfilled,
|
terminated: timetable.terminated && !timetable.fulfilled,
|
||||||
active: !timetable.terminated,
|
active: !timetable.terminated
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
@@ -74,8 +74,8 @@ export default defineComponent({
|
|||||||
props: {
|
props: {
|
||||||
timetable: {
|
timetable: {
|
||||||
type: Object as PropType<TimetableHistory>,
|
type: Object as PropType<TimetableHistory>,
|
||||||
required: true,
|
required: true
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -83,8 +83,8 @@ export default defineComponent({
|
|||||||
if (timetable?.terminated) return;
|
if (timetable?.terminated) return;
|
||||||
|
|
||||||
this.selectModalTrain(timetable.driverName + timetable.trainNo.toString(), target);
|
this.selectModalTrain(timetable.driverName + timetable.trainNo.toString(), target);
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,10 @@
|
|||||||
|
|
||||||
<button class="btn--option btn--show">
|
<button class="btn--option btn--show">
|
||||||
{{ $t('journal.stock-info') }}
|
{{ $t('journal.stock-info') }}
|
||||||
<img :src="getIcon(`arrow-${showExtraInfo.value ? 'asc' : 'desc'}`)" alt="Arrow" />
|
<img
|
||||||
|
:src="`/images/icon-arrow-${showExtraInfo.value ? 'asc' : 'desc'}.svg`"
|
||||||
|
alt="Arrow icon"
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
<!-- Extra -->
|
<!-- Extra -->
|
||||||
<TimetableExtra :timetable="timetable" :showExtraInfo="showExtraInfo.value" />
|
<TimetableExtra :timetable="timetable" :showExtraInfo="showExtraInfo.value" />
|
||||||
@@ -35,7 +38,6 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { PropType, defineComponent, ref } from 'vue';
|
import { PropType, defineComponent, ref } from 'vue';
|
||||||
import imageMixin from '../../../mixins/imageMixin';
|
|
||||||
import { TimetableHistory } from '../../../scripts/interfaces/api/TimetablesAPIData';
|
import { TimetableHistory } from '../../../scripts/interfaces/api/TimetablesAPIData';
|
||||||
|
|
||||||
import TimetableGeneral from './TimetableGeneral.vue';
|
import TimetableGeneral from './TimetableGeneral.vue';
|
||||||
@@ -44,23 +46,22 @@ import TimetableStatus from './TimetableStatus.vue';
|
|||||||
import TimetableExtra from './TimetableExtra.vue';
|
import TimetableExtra from './TimetableExtra.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [imageMixin],
|
|
||||||
props: {
|
props: {
|
||||||
timetableHistory: {
|
timetableHistory: {
|
||||||
type: Array as PropType<TimetableHistory[]>,
|
type: Array as PropType<TimetableHistory[]>,
|
||||||
required: true,
|
required: true
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
computedTimetableHistory() {
|
computedTimetableHistory() {
|
||||||
return this.timetableHistory.map((timetable) => ({
|
return this.timetableHistory.map((timetable) => ({
|
||||||
timetable,
|
timetable,
|
||||||
showExtraInfo: ref(false),
|
showExtraInfo: ref(false)
|
||||||
}));
|
}));
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
methods: {},
|
methods: {},
|
||||||
components: { TimetableGeneral, TimetableStops, TimetableStatus, TimetableExtra },
|
components: { TimetableGeneral, TimetableStops, TimetableStatus, TimetableExtra }
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -6,13 +6,19 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
<span :style="{ color: timetable.fulfilled ? 'lightgreen' : timetable.terminated ? 'salmon' : '' }">
|
<span
|
||||||
|
:style="{
|
||||||
|
color: timetable.fulfilled ? 'lightgreen' : timetable.terminated ? 'salmon' : ''
|
||||||
|
}"
|
||||||
|
>
|
||||||
{{ timetable.currentDistance + ' km' }}
|
{{ timetable.currentDistance + ' km' }}
|
||||||
</span>
|
</span>
|
||||||
<span> / </span>
|
<span> / </span>
|
||||||
<span class="text--primary">{{ timetable.routeDistance }} km</span>
|
<span class="text--primary">{{ timetable.routeDistance }} km</span>
|
||||||
|
|
|
|
||||||
<span class="text--grayed">{{ timetable.confirmedStopsCount }}/{{ timetable.allStopsCount }}</span>
|
<span class="text--grayed"
|
||||||
|
>{{ timetable.confirmedStopsCount }}/{{ timetable.allStopsCount }}</span
|
||||||
|
>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="text--grayed" v-if="timetable.currentSceneryName">
|
<span class="text--grayed" v-if="timetable.currentSceneryName">
|
||||||
@@ -46,9 +52,9 @@ export default defineComponent({
|
|||||||
props: {
|
props: {
|
||||||
timetable: {
|
timetable: {
|
||||||
type: Object as PropType<TimetableHistory>,
|
type: Object as PropType<TimetableHistory>,
|
||||||
required: true,
|
required: true
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ export default defineComponent({
|
|||||||
|
|
||||||
timetable: {
|
timetable: {
|
||||||
type: Object as PropType<TimetableHistory>,
|
type: Object as PropType<TimetableHistory>,
|
||||||
required: true,
|
required: true
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
@@ -65,12 +65,18 @@ export default defineComponent({
|
|||||||
if (i == 0) return { stopName, html: beginDateHTML, confirmed };
|
if (i == 0) return { stopName, html: beginDateHTML, confirmed };
|
||||||
if (i == stopNames.length - 1) return { stopName, html: endDateHTML, confirmed };
|
if (i == stopNames.length - 1) return { stopName, html: endDateHTML, confirmed };
|
||||||
|
|
||||||
const departureDateScheduled = this.stringToDate(timetable.checkpointDeparturesScheduled?.at(i));
|
const departureDateScheduled = this.stringToDate(
|
||||||
|
timetable.checkpointDeparturesScheduled?.at(i)
|
||||||
|
);
|
||||||
const departureDateReal = this.stringToDate(timetable.checkpointDepartures?.at(i));
|
const departureDateReal = this.stringToDate(timetable.checkpointDepartures?.at(i));
|
||||||
const arrivalDateScheduled = this.stringToDate(timetable.checkpointArrivalsScheduled?.at(i));
|
const arrivalDateScheduled = this.stringToDate(
|
||||||
|
timetable.checkpointArrivalsScheduled?.at(i)
|
||||||
|
);
|
||||||
const arrivalDateReal = this.stringToDate(timetable.checkpointArrivals?.at(i));
|
const arrivalDateReal = this.stringToDate(timetable.checkpointArrivals?.at(i));
|
||||||
const arrivalHTML =
|
const arrivalHTML =
|
||||||
(arrivalDateReal && arrivalDateScheduled && arrivalDateReal?.getTime() != arrivalDateScheduled?.getTime()
|
(arrivalDateReal &&
|
||||||
|
arrivalDateScheduled &&
|
||||||
|
arrivalDateReal?.getTime() != arrivalDateScheduled?.getTime()
|
||||||
? `<s class="text--grayed">${this.parseDateToTimeString(arrivalDateScheduled)}</s> `
|
? `<s class="text--grayed">${this.parseDateToTimeString(arrivalDateScheduled)}</s> `
|
||||||
: '') + this.parseDateToTimeString(arrivalDateReal || arrivalDateScheduled);
|
: '') + this.parseDateToTimeString(arrivalDateReal || arrivalDateScheduled);
|
||||||
const departureHTML =
|
const departureHTML =
|
||||||
@@ -83,8 +89,8 @@ export default defineComponent({
|
|||||||
if (html) html = ` (${html})`;
|
if (html) html = ` (${html})`;
|
||||||
return { stopName, html, confirmed };
|
return { stopName, html, confirmed };
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="scenery-table-section">
|
<section class="scenery-table-section">
|
||||||
<Loading v-if="dataStatus != DataStatus.Loaded && historyList.length == 0" />
|
<Loading v-if="dataStatus != DataStatus.Loaded && historyList.length == 0" />
|
||||||
<div class="no-history" v-else-if="historyList.length == 0">{{ $t('scenery.history-list-empty') }}</div>
|
|
||||||
|
|
||||||
<table class="scenery-history-table" v-else="historyList.length">
|
<div class="no-history" v-else-if="historyList.length == 0">
|
||||||
|
{{ $t('scenery.history-list-empty') }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="scenery-history-table" v-else>
|
||||||
<thead>
|
<thead>
|
||||||
<th>{{ $t('scenery.dispatchers-history-hash') }}</th>
|
<th>{{ $t('scenery.dispatchers-history-hash') }}</th>
|
||||||
<th>{{ $t('scenery.dispatchers-history-dispatcher') }}</th>
|
<th>{{ $t('scenery.dispatchers-history-dispatcher') }}</th>
|
||||||
@@ -13,7 +16,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="historyItem in historyList">
|
<tr v-for="historyItem in historyList" :key="historyItem.id">
|
||||||
<td>#{{ historyItem.stationHash }}</td>
|
<td>#{{ historyItem.stationHash }}</td>
|
||||||
<td>
|
<td>
|
||||||
<router-link :to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`">
|
<router-link :to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`">
|
||||||
@@ -24,7 +27,9 @@
|
|||||||
<b
|
<b
|
||||||
v-if="historyItem.dispatcherLevel !== null"
|
v-if="historyItem.dispatcherLevel !== null"
|
||||||
class="level-badge dispatcher"
|
class="level-badge dispatcher"
|
||||||
:style="calculateExpStyle(historyItem.dispatcherLevel, historyItem.dispatcherIsSupporter)"
|
:style="
|
||||||
|
calculateExpStyle(historyItem.dispatcherLevel, historyItem.dispatcherIsSupporter)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
|
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
|
||||||
</b>
|
</b>
|
||||||
@@ -37,7 +42,9 @@
|
|||||||
<b>{{ $d(historyItem.timestampFrom) }}</b>
|
<b>{{ $d(historyItem.timestampFrom) }}</b>
|
||||||
|
|
||||||
{{ timestampToString(historyItem.timestampFrom) }}
|
{{ timestampToString(historyItem.timestampFrom) }}
|
||||||
- {{ timestampToString(historyItem.timestampTo) }} ({{ calculateDuration(historyItem.currentDuration) }})
|
- {{ timestampToString(historyItem.timestampTo) }} ({{
|
||||||
|
calculateDuration(historyItem.currentDuration)
|
||||||
|
}})
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dispatcher-online" v-else>
|
<div class="dispatcher-online" v-else>
|
||||||
@@ -69,22 +76,28 @@ import { URLs } from '../../scripts/utils/apiURLs';
|
|||||||
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 listObserverMixin from '../../mixins/listObserverMixin';
|
||||||
|
import { OnlineScenery } from '../../scripts/interfaces/store/storeTypes';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SceneryDispatchersHistory',
|
name: 'SceneryDispatchersHistory',
|
||||||
mixins: [dateMixin, styleMixin, listObserverMixin],
|
mixins: [dateMixin, styleMixin, listObserverMixin],
|
||||||
|
components: { Loading },
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
type: Object as PropType<Station>,
|
type: Object as PropType<Station>,
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
|
onlineScenery: {
|
||||||
|
type: Object as PropType<OnlineScenery>,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
historyList: [] as DispatcherHistory[],
|
historyList: [] as DispatcherHistory[],
|
||||||
dataStatus: DataStatus.Loading,
|
dataStatus: DataStatus.Loading,
|
||||||
DataStatus,
|
DataStatus
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -113,9 +126,8 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
navigateToHistory() {
|
navigateToHistory() {
|
||||||
this.$router.push(`/journal/dispatchers?sceneryName=${this.station.name}`);
|
this.$router.push(`/journal/dispatchers?sceneryName=${this.station.name}`);
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
components: { Loading },
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -8,21 +8,27 @@
|
|||||||
{{ $t('scenery.abbrev') }} <b>{{ station.generalInfo?.abbr }}</b>
|
{{ $t('scenery.abbrev') }} <b>{{ station.generalInfo?.abbr }}</b>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="scenery-hash" v-if="station.onlineInfo?.hash">#{{ station.onlineInfo.hash }}</div>
|
<div class="scenery-hash" v-if="onlineScenery?.hash">#{{ onlineScenery.hash }}</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import Station from '../../scripts/interfaces/Station';
|
import Station from '../../scripts/interfaces/Station';
|
||||||
|
import { OnlineScenery } from '../../scripts/interfaces/store/storeTypes';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
type: Object as () => Station,
|
type: Object as PropType<Station>,
|
||||||
default: {},
|
required: true
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onlineScenery: {
|
||||||
|
type: Object as PropType<OnlineScenery>,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -52,4 +58,3 @@ export default defineComponent({
|
|||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,34 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="scenery-info">
|
<div class="scenery-info">
|
||||||
<section v-if="!timetableOnly">
|
<section>
|
||||||
<div class="scenery-info-general" v-if="station.generalInfo">
|
<div class="scenery-info-general" v-if="station.generalInfo">
|
||||||
<SceneryInfoIcons :station="station" />
|
<SceneryInfoIcons :station="station" />
|
||||||
|
|
||||||
<div class="scenery-general-list">
|
<div class="scenery-general-list">
|
||||||
<span>
|
<span>
|
||||||
<b>{{ $t('availability.title') }}:</b> {{ $t(`availability.${station.generalInfo.availability}`) }}
|
<b>{{ $t('availability.title') }}:</b>
|
||||||
|
{{ $t(`availability.${station.generalInfo.availability}`) }}
|
||||||
|
|
||||||
<span v-if="station.generalInfo.reqLevel > -1">
|
<span v-if="station.generalInfo.reqLevel > -1">
|
||||||
- {{ $t('scenery.req-level', { lvl: station.generalInfo.reqLevel }, station.generalInfo.reqLevel) }}
|
-
|
||||||
|
{{
|
||||||
|
$t(
|
||||||
|
'scenery.req-level',
|
||||||
|
{ lvl: station.generalInfo.reqLevel },
|
||||||
|
station.generalInfo.reqLevel
|
||||||
|
)
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
• <b>{{ $t('controls.title') }}:</b> {{ $t(`controls.${station.generalInfo.controlType}`) }}
|
• <b>{{ $t('controls.title') }}:</b>
|
||||||
|
{{ $t(`controls.${station.generalInfo.controlType}`) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
• <b>{{ $t('signals.title') }}:</b> {{ $t(`signals.${station.generalInfo.signalType}`) }}
|
• <b>{{ $t('signals.title') }}:</b>
|
||||||
|
{{ $t(`signals.${station.generalInfo.signalType}`) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-if="station.generalInfo.lines">
|
<span v-if="station.generalInfo.lines">
|
||||||
@@ -26,7 +36,11 @@
|
|||||||
</span>
|
</span>
|
||||||
<span v-if="station.generalInfo.project">
|
<span v-if="station.generalInfo.project">
|
||||||
• <b>{{ $t('scenery.project-title') }}: </b>
|
• <b>{{ $t('scenery.project-title') }}: </b>
|
||||||
<a style="color: salmon; text-decoration: underline; font-weight: bold" :href="station.generalInfo.projectUrl" target="_blank">
|
<a
|
||||||
|
style="color: salmon; text-decoration: underline; font-weight: bold"
|
||||||
|
:href="station.generalInfo.projectUrl"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
{{ station.generalInfo.project }}
|
{{ station.generalInfo.project }}
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
@@ -34,8 +48,19 @@
|
|||||||
|
|
||||||
<SceneryInfoRoutes :station="station" />
|
<SceneryInfoRoutes :station="station" />
|
||||||
|
|
||||||
<div class="scenery-authors" v-if="station.generalInfo.authors && station.generalInfo.authors.length > 0">
|
<div
|
||||||
<b> {{ $t('scenery.authors-title', { authors: station.generalInfo.authors.length }, station.generalInfo.authors.length) }}: </b>
|
class="scenery-authors"
|
||||||
|
v-if="station.generalInfo.authors && station.generalInfo.authors.length > 0"
|
||||||
|
>
|
||||||
|
<b>
|
||||||
|
{{
|
||||||
|
$t(
|
||||||
|
'scenery.authors-title',
|
||||||
|
{ authors: station.generalInfo.authors.length },
|
||||||
|
station.generalInfo.authors.length
|
||||||
|
)
|
||||||
|
}}:
|
||||||
|
</b>
|
||||||
{{ station.generalInfo.authors.join(', ') }}
|
{{ station.generalInfo.authors.join(', ') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -43,51 +68,49 @@
|
|||||||
<div style="margin: 2em 0; height: 2px; background-color: white"></div>
|
<div style="margin: 2em 0; height: 2px; background-color: white"></div>
|
||||||
|
|
||||||
<!-- info dispatcher -->
|
<!-- info dispatcher -->
|
||||||
<SceneryInfoDispatcher :station="station" :onlineFrom="onlineFrom" />
|
<SceneryInfoDispatcher :onlineScenery="onlineScenery" />
|
||||||
|
|
||||||
<div class="info-lists">
|
<div class="info-lists">
|
||||||
<!-- user list -->
|
<!-- user list -->
|
||||||
<SceneryInfoUserList :station="station" />
|
<SceneryInfoUserList :onlineScenery="onlineScenery" />
|
||||||
|
|
||||||
<!-- spawn list -->
|
<!-- spawn list -->
|
||||||
<SceneryInfoSpawnList :station="station" />
|
<SceneryInfoSpawnList :onlineScenery="onlineScenery" />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from '@vue/runtime-core';
|
import { PropType, defineComponent } from 'vue';
|
||||||
|
|
||||||
import SceneryInfoDispatcher from './SceneryInfo/SceneryInfoDispatcher.vue';
|
import SceneryInfoDispatcher from './SceneryInfo/SceneryInfoDispatcher.vue';
|
||||||
import SceneryInfoIcons from './SceneryInfo/SceneryInfoIcons.vue';
|
import SceneryInfoIcons from './SceneryInfo/SceneryInfoIcons.vue';
|
||||||
import SceneryInfoStats from './SceneryInfo/SceneryInfoStats.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 Station from '../../scripts/interfaces/Station';
|
||||||
|
import { OnlineScenery } from '../../scripts/interfaces/store/storeTypes';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
SceneryInfoDispatcher,
|
SceneryInfoDispatcher,
|
||||||
SceneryInfoIcons,
|
SceneryInfoIcons,
|
||||||
SceneryInfoStats,
|
|
||||||
SceneryInfoUserList,
|
SceneryInfoUserList,
|
||||||
SceneryInfoSpawnList,
|
SceneryInfoSpawnList,
|
||||||
SceneryInfoRoutes,
|
SceneryInfoRoutes
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
type: Object as () => Station,
|
type: Object as PropType<Station>,
|
||||||
default: {},
|
required: true
|
||||||
},
|
},
|
||||||
|
|
||||||
timetableOnly: Boolean,
|
onlineScenery: {
|
||||||
|
type: Object as PropType<OnlineScenery>,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
|
||||||
onlineFrom: -1,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,54 +1,49 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="info-dispatcher">
|
<section class="info-dispatcher">
|
||||||
<div class="dispatcher" v-if="station.onlineInfo">
|
<div class="dispatcher" v-if="onlineScenery">
|
||||||
<span
|
<span
|
||||||
class="dispatcher_level"
|
class="dispatcher_level"
|
||||||
:style="calculateExpStyle(station.onlineInfo.dispatcherExp, station.onlineInfo.dispatcherIsSupporter)"
|
:style="calculateExpStyle(onlineScenery.dispatcherExp, onlineScenery.dispatcherIsSupporter)"
|
||||||
>
|
>
|
||||||
{{ station.onlineInfo.dispatcherExp > 1 ? station.onlineInfo.dispatcherExp : 'L' }}
|
{{ onlineScenery.dispatcherExp > 1 ? onlineScenery.dispatcherExp : 'L' }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<router-link
|
<router-link
|
||||||
class="dispatcher_name"
|
class="dispatcher_name"
|
||||||
:to="`/journal/dispatchers?dispatcherName=${station.onlineInfo.dispatcherName}`"
|
:to="`/journal/dispatchers?dispatcherName=${onlineScenery.dispatcherName}`"
|
||||||
>
|
>
|
||||||
{{ station.onlineInfo.dispatcherName }}
|
{{ onlineScenery.dispatcherName }}
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<span class="dispatcher_likes text--primary">
|
<span class="dispatcher_likes text--primary">
|
||||||
<img :src="getIcon('like')" alt="icon-like" />
|
<img src="/images/icon-like.svg" alt="Likes count icon" />
|
||||||
<span>{{ station.onlineInfo?.dispatcherRate || '0' }}</span>
|
<span>{{ onlineScenery?.dispatcherRate || '0' }}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<StationStatusBadge
|
<StationStatusBadge
|
||||||
:statusID="station.onlineInfo?.statusID"
|
:statusID="onlineScenery?.statusID"
|
||||||
:isOnline="station.onlineInfo ? true : false"
|
:isOnline="onlineScenery ? true : false"
|
||||||
:statusTimestamp="station.onlineInfo?.statusTimestamp"
|
:statusTimestamp="onlineScenery?.statusTimestamp"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import dateMixin from '../../../mixins/dateMixin';
|
import dateMixin from '../../../mixins/dateMixin';
|
||||||
import imageMixin from '../../../mixins/imageMixin';
|
|
||||||
import routerMixin from '../../../mixins/routerMixin';
|
import routerMixin from '../../../mixins/routerMixin';
|
||||||
import styleMixin from '../../../mixins/styleMixin';
|
import styleMixin from '../../../mixins/styleMixin';
|
||||||
import Station from '../../../scripts/interfaces/Station';
|
|
||||||
import StationStatusBadge from '../../Global/StationStatusBadge.vue';
|
import StationStatusBadge from '../../Global/StationStatusBadge.vue';
|
||||||
|
import { OnlineScenery } from '../../../scripts/interfaces/store/storeTypes';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [styleMixin, dateMixin, routerMixin, imageMixin],
|
mixins: [styleMixin, dateMixin, routerMixin],
|
||||||
props: {
|
props: {
|
||||||
station: {
|
onlineScenery: {
|
||||||
type: Object as () => Station,
|
type: Object as PropType<OnlineScenery>,
|
||||||
default: {},
|
required: false
|
||||||
},
|
}
|
||||||
onlineFrom: {
|
|
||||||
type: Number,
|
|
||||||
default: -1,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
components: { StationStatusBadge }
|
components: { StationStatusBadge }
|
||||||
});
|
});
|
||||||
@@ -98,4 +93,3 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<img
|
<img
|
||||||
v-if="station.generalInfo?.SUP"
|
v-if="station.generalInfo?.SUP"
|
||||||
class="icon-info"
|
class="icon-info"
|
||||||
:src="getIcon('SUP')"
|
src="/images/icon-SUP.svg"
|
||||||
alt="SUP (RASP-UZK)"
|
alt="SUP (RASP-UZK)"
|
||||||
:title="$t('desc.SUP')"
|
:title="$t('desc.SUP')"
|
||||||
/>
|
/>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<img
|
<img
|
||||||
v-if="station.generalInfo?.signalType"
|
v-if="station.generalInfo?.signalType"
|
||||||
class="icon-info"
|
class="icon-info"
|
||||||
:src="getIcon(station.generalInfo.signalType)"
|
:src="`/images/icon-${station.generalInfo.signalType}.svg`"
|
||||||
:alt="station.generalInfo.signalType"
|
:alt="station.generalInfo.signalType"
|
||||||
:title="$t('desc.signals-type') + $t(`signals.${station.generalInfo.signalType}`)"
|
:title="$t('desc.signals-type') + $t(`signals.${station.generalInfo.signalType}`)"
|
||||||
/>
|
/>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
<img
|
<img
|
||||||
v-if="station.generalInfo?.availability == 'nonPublic'"
|
v-if="station.generalInfo?.availability == 'nonPublic'"
|
||||||
class="icon-info"
|
class="icon-info"
|
||||||
:src="getIcon('lock')"
|
src="/images/icon-lock.svg"
|
||||||
alt="Non-public scenery"
|
alt="Non-public scenery"
|
||||||
:title="$t('desc.non-public')"
|
:title="$t('desc.non-public')"
|
||||||
/>
|
/>
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
<img
|
<img
|
||||||
v-if="station.generalInfo?.availability == 'unavailable'"
|
v-if="station.generalInfo?.availability == 'unavailable'"
|
||||||
class="icon-info"
|
class="icon-info"
|
||||||
:src="getIcon('unavailable')"
|
src="/images/icon-unavailable.svg"
|
||||||
alt="Unavailable scenery"
|
alt="Unavailable scenery"
|
||||||
:title="$t('desc.unavailable')"
|
:title="$t('desc.unavailable')"
|
||||||
/>
|
/>
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
<img
|
<img
|
||||||
v-if="station.generalInfo?.availability == 'abandoned'"
|
v-if="station.generalInfo?.availability == 'abandoned'"
|
||||||
class="icon-info"
|
class="icon-info"
|
||||||
:src="getIcon('abandoned')"
|
src="/images/icon-abandoned.svg"
|
||||||
alt="Abandoned scenery"
|
alt="Abandoned scenery"
|
||||||
:title="$t('desc.abandoned')"
|
:title="$t('desc.abandoned')"
|
||||||
/>
|
/>
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
<img
|
<img
|
||||||
v-if="station.generalInfo?.lines"
|
v-if="station.generalInfo?.lines"
|
||||||
class="icon-info"
|
class="icon-info"
|
||||||
:src="getIcon('real')"
|
src="/images/icon-real.svg"
|
||||||
alt="real scenery"
|
alt="real scenery"
|
||||||
:title="`${$t('desc.real')} ${station.generalInfo.lines}`"
|
:title="`${$t('desc.real')} ${station.generalInfo.lines}`"
|
||||||
/>
|
/>
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
<img
|
<img
|
||||||
v-if="!station.generalInfo"
|
v-if="!station.generalInfo"
|
||||||
class="icon-info"
|
class="icon-info"
|
||||||
:src="getIcon('unknown')"
|
src="/images/icon-unknown.svg"
|
||||||
alt="icon-unknown"
|
alt="icon-unknown"
|
||||||
:title="$t('desc.unknown')"
|
:title="$t('desc.unknown')"
|
||||||
/>
|
/>
|
||||||
@@ -76,20 +76,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import imageMixin from '../../../mixins/imageMixin';
|
|
||||||
import stationInfoMixin from '../../../mixins/stationInfoMixin';
|
import stationInfoMixin from '../../../mixins/stationInfoMixin';
|
||||||
import styleMixin from '../../../mixins/styleMixin';
|
import styleMixin from '../../../mixins/styleMixin';
|
||||||
import Station from '../../../scripts/interfaces/Station';
|
import Station from '../../../scripts/interfaces/Station';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [stationInfoMixin, styleMixin, imageMixin],
|
mixins: [stationInfoMixin, styleMixin],
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
type: Object as () => Station,
|
type: Object as PropType<Station>,
|
||||||
default: {},
|
required: true
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -118,4 +117,3 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,14 @@
|
|||||||
<b>{{ $t('scenery.one-way-routes') }}</b>
|
<b>{{ $t('scenery.one-way-routes') }}</b>
|
||||||
|
|
||||||
<ul class="routes-list">
|
<ul class="routes-list">
|
||||||
<li v-for="route in station.generalInfo.routes.oneWay" @click="setActiveShowLength(route.name)">
|
<li
|
||||||
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"> {{ route.name }}</span>
|
v-for="route in station.generalInfo.routes.oneWay"
|
||||||
|
:key="route.name"
|
||||||
|
@click="setActiveShowLength(route.name)"
|
||||||
|
>
|
||||||
|
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }">
|
||||||
|
{{ route.name }}</span
|
||||||
|
>
|
||||||
<span v-if="route.speed" class="speed">
|
<span v-if="route.speed" class="speed">
|
||||||
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
|
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
|
||||||
</span>
|
</span>
|
||||||
@@ -18,8 +24,14 @@
|
|||||||
<b>{{ $t('scenery.two-way-routes') }}</b>
|
<b>{{ $t('scenery.two-way-routes') }}</b>
|
||||||
|
|
||||||
<ul class="routes-list">
|
<ul class="routes-list">
|
||||||
<li v-for="(route, i) in station.generalInfo.routes.twoWay" @click="setActiveShowLength(route.name)">
|
<li
|
||||||
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }">{{ route.name }}</span>
|
v-for="route in station.generalInfo.routes.twoWay"
|
||||||
|
:key="route.name"
|
||||||
|
@click="setActiveShowLength(route.name)"
|
||||||
|
>
|
||||||
|
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }">{{
|
||||||
|
route.name
|
||||||
|
}}</span>
|
||||||
<span v-if="route.speed" class="speed">
|
<span v-if="route.speed" class="speed">
|
||||||
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
|
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
|
||||||
</span>
|
</span>
|
||||||
@@ -31,29 +43,30 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import Station from '../../../scripts/interfaces/Station';
|
import Station from '../../../scripts/interfaces/Station';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
type: Object as () => Station,
|
type: Object as PropType<Station>,
|
||||||
default: {},
|
required: true
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
setActiveShowLength(name: string) {
|
setActiveShowLength(name: string) {
|
||||||
if (this.activeShowLength.includes(name)) this.activeShowLength.splice(this.activeShowLength.indexOf(name), 1);
|
if (this.activeShowLength.includes(name))
|
||||||
|
this.activeShowLength.splice(this.activeShowLength.indexOf(name), 1);
|
||||||
else this.activeShowLength.push(name);
|
else this.activeShowLength.push(name);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
activeShowLength: [] as string[],
|
activeShowLength: [] as string[]
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,55 +1,60 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="info-spawn-list">
|
<section class="info-spawn-list">
|
||||||
<h3 class="spawn-header section-header">
|
<h3 class="spawn-header section-header">
|
||||||
<img :src="getIcon('spawn')" alt="icon-spawn" />
|
<img src="/images/icon-spawn.svg" alt="Open spawns icon" />
|
||||||
{{ $t('scenery.spawns') }}
|
{{ $t('scenery.spawns') }}
|
||||||
<span class="text--primary">{{ station.onlineInfo?.spawns.length || '0' }}</span>
|
<span class="text--primary">{{ onlineScenery?.spawns.length || '0' }}</span>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<span v-if="station.onlineInfo">
|
<transition-group name="spawns-anim" tag="ul">
|
||||||
<span
|
<li class="badge spawn badge-none" v-if="!onlineScenery || onlineScenery.spawns.length == 0" key="no-spawns">
|
||||||
|
{{ $t('scenery.no-spawns') }}
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li
|
||||||
class="badge spawn"
|
class="badge spawn"
|
||||||
v-for="(spawn, i) in sortedSpawns"
|
v-for="(spawn, i) in sortedSpawns"
|
||||||
:key="spawn.spawnName + station.onlineInfo?.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="spawn_name">{{ spawn.spawnName }}</span>
|
||||||
<span class="spawn_length">{{ spawn.spawnLength }}m</span>
|
<span class="spawn_length">{{ spawn.spawnLength }}m</span>
|
||||||
</span>
|
</li>
|
||||||
</span>
|
</transition-group>
|
||||||
|
|
||||||
<span class="badge spawn badge-none" v-if="!station.onlineInfo || station.onlineInfo.spawns.length == 0"
|
|
||||||
>{{ $t('scenery.no-spawns') }}
|
|
||||||
</span>
|
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import imageMixin from '../../../mixins/imageMixin';
|
import { OnlineScenery } from '../../../scripts/interfaces/store/storeTypes';
|
||||||
import Station from '../../../scripts/interfaces/Station';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [imageMixin],
|
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
station: {
|
onlineScenery: {
|
||||||
type: Object as () => Station,
|
type: Object as PropType<OnlineScenery>,
|
||||||
default: {},
|
required: false
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
sortedSpawns() {
|
sortedSpawns() {
|
||||||
return this.station.onlineInfo?.spawns.sort((s1, s2) => (s1.spawnLength < s2.spawnLength ? 1 : -1));
|
if (!this.onlineScenery) return [];
|
||||||
},
|
|
||||||
},
|
return [...this.onlineScenery.spawns].sort((s1, s2) =>
|
||||||
|
s1.spawnLength < s2.spawnLength ? 1 : -1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../../styles/variables.scss';
|
@import '../../../styles/variables.scss';
|
||||||
|
|
||||||
|
ul {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.spawn {
|
.spawn {
|
||||||
color: white;
|
color: white;
|
||||||
|
|
||||||
@@ -62,4 +67,22 @@ export default defineComponent({
|
|||||||
background-color: #007599;
|
background-color: #007599;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spawns-anim {
|
||||||
|
&-move,
|
||||||
|
&-enter-active,
|
||||||
|
&-leave-active {
|
||||||
|
transition: all 250ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-enter-from,
|
||||||
|
&-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-leave-active {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,29 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="info-stats" :class="!station.onlineInfo ? 'no-stats' : ''">
|
<section class="info-stats" :class="!station.onlineInfo ? 'no-stats' : ''">
|
||||||
<span class="likes">
|
<span class="likes">
|
||||||
<img :src="getIcon('like')" alt="icon-like" />
|
<img src="/images/icon-like" alt="Likes count icon" />
|
||||||
<span>{{ station.onlineInfo?.dispatcherRate || '0' }}</span>
|
<span>{{ station.onlineInfo?.dispatcherRate || '0' }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="users">
|
<span class="users">
|
||||||
<img :src="getIcon('user')" alt="icon-user" />
|
<img src="/images/icon-user" alt="Users count icon" />
|
||||||
<span>{{ station.onlineInfo?.currentUsers || '0' }}</span>
|
<span>{{ station.onlineInfo?.currentUsers || '0' }}</span>
|
||||||
/
|
/
|
||||||
<span>{{ station.onlineInfo?.maxUsers || '0' }}</span>
|
<span>{{ station.onlineInfo?.maxUsers || '0' }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="spawns">
|
<span class="spawns">
|
||||||
<img :src="getIcon('spawn')" alt="icon-spawn" />
|
<img src="/images/icon-spawn" alt="Spawns count icon" />
|
||||||
<span>{{ station.onlineInfo?.spawns.length || '0' }}</span>
|
<span>{{ station.onlineInfo?.spawns.length || '0' }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="schedules">
|
<span class="schedules">
|
||||||
<img :src="getIcon('timetable')" alt="icon-timetable" />
|
<img src="/images/icon-timetable" alt="Timetables count icon" />
|
||||||
<span>
|
<span>
|
||||||
<span style="color: #eee">{{ station.onlineInfo?.scheduledTrains?.length || '0' }}</span>
|
<span style="color: #eee">{{ station.onlineInfo?.scheduledTrains?.length || '0' }}</span>
|
||||||
/
|
/
|
||||||
<span style="color: #bbb"
|
<span style="color: #bbb"
|
||||||
>{{ station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed).length || '0' }}
|
>{{
|
||||||
|
station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed)
|
||||||
|
.length || '0'
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -31,18 +34,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { PropType, defineComponent } from 'vue';
|
||||||
import imageMixin from '../../../mixins/imageMixin';
|
|
||||||
import Station from '../../../scripts/interfaces/Station';
|
import Station from '../../../scripts/interfaces/Station';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [imageMixin],
|
|
||||||
props: {
|
props: {
|
||||||
station: {
|
station: {
|
||||||
type: Object as () => Station,
|
type: Object as PropType<Station>,
|
||||||
default: {},
|
required: true
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||