format & lint

This commit is contained in:
2023-10-04 15:01:01 +02:00
parent 800fc35e63
commit 45c1d83512
125 changed files with 15006 additions and 13222 deletions
@@ -1,143 +1,150 @@
<template>
<section class="scenery-table-section">
<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">
<thead>
<th>{{ $t('scenery.dispatchers-history-hash') }}</th>
<th>{{ $t('scenery.dispatchers-history-dispatcher') }}</th>
<th>{{ $t('scenery.dispatchers-history-level') }}</th>
<th>{{ $t('scenery.dispatchers-history-rate') }}</th>
<th>{{ $t('scenery.dispatchers-history-date') }}</th>
</thead>
<tbody>
<tr v-for="historyItem in historyList">
<td>#{{ historyItem.stationHash }}</td>
<td>
<router-link :to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`">
<b>{{ historyItem.dispatcherName }}</b>
</router-link>
</td>
<td>
<b
v-if="historyItem.dispatcherLevel !== null"
class="level-badge dispatcher"
:style="calculateExpStyle(historyItem.dispatcherLevel, historyItem.dispatcherIsSupporter)"
>
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
</b>
</td>
<td class="text--primary">
<b>{{ historyItem.dispatcherRate }}</b>
</td>
<td style="min-width: 300px">
<div v-if="historyItem.timestampTo">
<b>{{ $d(historyItem.timestampFrom) }}</b>
{{ timestampToString(historyItem.timestampFrom) }}
- {{ timestampToString(historyItem.timestampTo) }} ({{ calculateDuration(historyItem.currentDuration) }})
</div>
<div class="dispatcher-online" v-else>
{{ $t('journal.online-since') }}
<b>{{ timestampToString(historyItem.timestampFrom) }}</b>
({{ calculateDuration(historyItem.currentDuration) }})
</div>
</td>
</tr>
</tbody>
</table>
</section>
<div class="bottom-info">
<button class="btn btn--option" v-if="historyList.length > 0" @click="navigateToHistory">
{{ $t('scenery.bottom-info') }}
</button>
</div>
</template>
<script lang="ts">
import axios from 'axios';
import { defineComponent, PropType } from 'vue';
import dateMixin from '../../mixins/dateMixin';
import { DataStatus } from '../../scripts/enums/DataStatus';
import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIData';
import Station from '../../scripts/interfaces/Station';
import { URLs } from '../../scripts/utils/apiURLs';
import Loading from '../Global/Loading.vue';
import styleMixin from '../../mixins/styleMixin';
import listObserverMixin from '../../mixins/listObserverMixin';
export default defineComponent({
name: 'SceneryDispatchersHistory',
mixins: [dateMixin, styleMixin, listObserverMixin],
props: {
station: {
type: Object as PropType<Station>,
required: true,
},
},
data() {
return {
historyList: [] as DispatcherHistory[],
dataStatus: DataStatus.Loading,
DataStatus,
};
},
async activated() {
// if (this.historyList.length == 0) {
const fetchedHistory = await this.fetchAPIData();
if (fetchedHistory) this.historyList = fetchedHistory;
// }
},
methods: {
async fetchAPIData(countFrom = 0, countLimit = 30): Promise<DispatcherHistory[] | null> {
try {
this.dataStatus = DataStatus.Loading;
const requestString = `${URLs.stacjownikAPI}/api/getDispatchers?stationName=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
const historyAPIData: DispatcherHistory[] = await (await axios.get(requestString)).data;
this.dataStatus = DataStatus.Loaded;
return historyAPIData;
} catch (error) {
this.dataStatus = DataStatus.Error;
console.error(error);
return null;
}
},
navigateToHistory() {
this.$router.push(`/journal/dispatchers?sceneryName=${this.station.name}`);
},
},
components: { Loading },
});
</script>
<style lang="scss" scoped>
@import '../../styles/responsive.scss';
@import '../../styles/sceneryViewTables.scss';
.level-badge {
margin: 0 auto;
}
.dispatcher-online {
color: springgreen;
}
@include smallScreen {
.history-list {
font-size: 1.1em;
}
.list-item {
align-items: center;
flex-direction: column;
}
}
</style>
<template>
<section class="scenery-table-section">
<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>
<thead>
<th>{{ $t('scenery.dispatchers-history-hash') }}</th>
<th>{{ $t('scenery.dispatchers-history-dispatcher') }}</th>
<th>{{ $t('scenery.dispatchers-history-level') }}</th>
<th>{{ $t('scenery.dispatchers-history-rate') }}</th>
<th>{{ $t('scenery.dispatchers-history-date') }}</th>
</thead>
<tbody>
<tr v-for="historyItem in historyList" :key="historyItem.id">
<td>#{{ historyItem.stationHash }}</td>
<td>
<router-link :to="`/journal/dispatchers?dispatcherName=${historyItem.dispatcherName}`">
<b>{{ historyItem.dispatcherName }}</b>
</router-link>
</td>
<td>
<b
v-if="historyItem.dispatcherLevel !== null"
class="level-badge dispatcher"
:style="
calculateExpStyle(historyItem.dispatcherLevel, historyItem.dispatcherIsSupporter)
"
>
{{ historyItem.dispatcherLevel >= 2 ? historyItem.dispatcherLevel : 'L' }}
</b>
</td>
<td class="text--primary">
<b>{{ historyItem.dispatcherRate }}</b>
</td>
<td style="min-width: 300px">
<div v-if="historyItem.timestampTo">
<b>{{ $d(historyItem.timestampFrom) }}</b>
{{ timestampToString(historyItem.timestampFrom) }}
- {{ timestampToString(historyItem.timestampTo) }} ({{
calculateDuration(historyItem.currentDuration)
}})
</div>
<div class="dispatcher-online" v-else>
{{ $t('journal.online-since') }}
<b>{{ timestampToString(historyItem.timestampFrom) }}</b>
({{ calculateDuration(historyItem.currentDuration) }})
</div>
</td>
</tr>
</tbody>
</table>
</section>
<div class="bottom-info">
<button class="btn btn--option" v-if="historyList.length > 0" @click="navigateToHistory">
{{ $t('scenery.bottom-info') }}
</button>
</div>
</template>
<script lang="ts">
import axios from 'axios';
import { defineComponent, PropType } from 'vue';
import dateMixin from '../../mixins/dateMixin';
import { DataStatus } from '../../scripts/enums/DataStatus';
import { DispatcherHistory } from '../../scripts/interfaces/api/DispatchersAPIData';
import Station from '../../scripts/interfaces/Station';
import { URLs } from '../../scripts/utils/apiURLs';
import Loading from '../Global/Loading.vue';
import styleMixin from '../../mixins/styleMixin';
import listObserverMixin from '../../mixins/listObserverMixin';
export default defineComponent({
name: 'SceneryDispatchersHistory',
mixins: [dateMixin, styleMixin, listObserverMixin],
components: { Loading },
props: {
station: {
type: Object as PropType<Station>,
required: true
}
},
data() {
return {
historyList: [] as DispatcherHistory[],
dataStatus: DataStatus.Loading,
DataStatus
};
},
async activated() {
// if (this.historyList.length == 0) {
const fetchedHistory = await this.fetchAPIData();
if (fetchedHistory) this.historyList = fetchedHistory;
// }
},
methods: {
async fetchAPIData(countFrom = 0, countLimit = 30): Promise<DispatcherHistory[] | null> {
try {
this.dataStatus = DataStatus.Loading;
const requestString = `${URLs.stacjownikAPI}/api/getDispatchers?stationName=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
const historyAPIData: DispatcherHistory[] = await (await axios.get(requestString)).data;
this.dataStatus = DataStatus.Loaded;
return historyAPIData;
} catch (error) {
this.dataStatus = DataStatus.Error;
console.error(error);
return null;
}
},
navigateToHistory() {
this.$router.push(`/journal/dispatchers?sceneryName=${this.station.name}`);
}
}
});
</script>
<style lang="scss" scoped>
@import '../../styles/responsive.scss';
@import '../../styles/sceneryViewTables.scss';
.level-badge {
margin: 0 auto;
}
.dispatcher-online {
color: springgreen;
}
@include smallScreen {
.history-list {
font-size: 1.1em;
}
.list-item {
align-items: center;
flex-direction: column;
}
}
</style>
+5 -6
View File
@@ -13,16 +13,16 @@
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { PropType, defineComponent } from 'vue';
import Station from '../../scripts/interfaces/Station';
export default defineComponent({
props: {
station: {
type: Object as () => Station,
default: {},
},
},
type: Object as PropType<Station>,
required: true
}
}
});
</script>
@@ -52,4 +52,3 @@ export default defineComponent({
font-size: 1.2em;
}
</style>
+162 -139
View File
@@ -1,139 +1,162 @@
<template>
<div class="scenery-info">
<section v-if="!timetableOnly">
<div class="scenery-info-general" v-if="station.generalInfo">
<SceneryInfoIcons :station="station" />
<div class="scenery-general-list">
<span>
<b>{{ $t('availability.title') }}:</b> {{ $t(`availability.${station.generalInfo.availability}`) }}
<span v-if="station.generalInfo.reqLevel > -1">
- {{ $t('scenery.req-level', { lvl: station.generalInfo.reqLevel }, station.generalInfo.reqLevel) }}
</span>
</span>
<span>
&bull; <b>{{ $t('controls.title') }}:</b> {{ $t(`controls.${station.generalInfo.controlType}`) }}
</span>
<span>
&bull; <b>{{ $t('signals.title') }}:</b> {{ $t(`signals.${station.generalInfo.signalType}`) }}
</span>
<span v-if="station.generalInfo.lines">
&bull; <b>{{ $t('scenery.lines-title') }}:</b> {{ station.generalInfo.lines }}
</span>
<span v-if="station.generalInfo.project">
&bull; <b>{{ $t('scenery.project-title') }}: </b>
<a style="color: salmon; text-decoration: underline; font-weight: bold" :href="station.generalInfo.projectUrl" target="_blank">
{{ station.generalInfo.project }}
</a>
</span>
</div>
<SceneryInfoRoutes :station="station" />
<div 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(', ') }}
</div>
</div>
<div style="margin: 2em 0; height: 2px; background-color: white"></div>
<!-- info dispatcher -->
<SceneryInfoDispatcher :station="station" :onlineFrom="onlineFrom" />
<div class="info-lists">
<!-- user list -->
<SceneryInfoUserList :station="station" />
<!-- spawn list -->
<SceneryInfoSpawnList :station="station" />
</div>
</section>
</div>
</template>
<script lang="ts">
import { defineComponent } from '@vue/runtime-core';
import SceneryInfoDispatcher from './SceneryInfo/SceneryInfoDispatcher.vue';
import SceneryInfoIcons from './SceneryInfo/SceneryInfoIcons.vue';
import SceneryInfoStats from './SceneryInfo/SceneryInfoStats.vue';
import SceneryInfoUserList from './SceneryInfo/SceneryInfoUserList.vue';
import SceneryInfoSpawnList from './SceneryInfo/SceneryInfoSpawnList.vue';
import SceneryInfoRoutes from './SceneryInfo/SceneryInfoRoutes.vue';
import Station from '../../scripts/interfaces/Station';
export default defineComponent({
components: {
SceneryInfoDispatcher,
SceneryInfoIcons,
SceneryInfoStats,
SceneryInfoUserList,
SceneryInfoSpawnList,
SceneryInfoRoutes,
},
props: {
station: {
type: Object as () => Station,
default: {},
},
timetableOnly: Boolean,
},
data: () => ({
onlineFrom: -1,
}),
});
</script>
<style lang="scss">
@import '../../styles/responsive.scss';
@import '../../styles/badge.scss';
h3.section-header {
margin: 0.5em 0;
padding: 0.3em;
display: flex;
justify-content: center;
align-items: center;
font-size: 1.2em;
img {
width: 1.1em;
margin-left: 0.5em;
}
}
.info-lists {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
margin-top: 1em;
}
.scenery-info-general {
margin-top: 1em;
}
.scenery-general-list {
display: flex;
justify-content: center;
flex-wrap: wrap;
span {
margin: 0 0.15em;
}
}
.scenery-topic a {
font-weight: bold;
}
</style>
<template>
<div class="scenery-info">
<section v-if="!timetableOnly">
<div class="scenery-info-general" v-if="station.generalInfo">
<SceneryInfoIcons :station="station" />
<div class="scenery-general-list">
<span>
<b>{{ $t('availability.title') }}:</b>
{{ $t(`availability.${station.generalInfo.availability}`) }}
<span v-if="station.generalInfo.reqLevel > -1">
-
{{
$t(
'scenery.req-level',
{ lvl: station.generalInfo.reqLevel },
station.generalInfo.reqLevel
)
}}
</span>
</span>
<span>
&bull; <b>{{ $t('controls.title') }}:</b>
{{ $t(`controls.${station.generalInfo.controlType}`) }}
</span>
<span>
&bull; <b>{{ $t('signals.title') }}:</b>
{{ $t(`signals.${station.generalInfo.signalType}`) }}
</span>
<span v-if="station.generalInfo.lines">
&bull; <b>{{ $t('scenery.lines-title') }}:</b> {{ station.generalInfo.lines }}
</span>
<span v-if="station.generalInfo.project">
&bull; <b>{{ $t('scenery.project-title') }}: </b>
<a
style="color: salmon; text-decoration: underline; font-weight: bold"
:href="station.generalInfo.projectUrl"
target="_blank"
>
{{ station.generalInfo.project }}
</a>
</span>
</div>
<SceneryInfoRoutes :station="station" />
<div
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(', ') }}
</div>
</div>
<div style="margin: 2em 0; height: 2px; background-color: white"></div>
<!-- info dispatcher -->
<SceneryInfoDispatcher :station="station" :onlineFrom="onlineFrom" />
<div class="info-lists">
<!-- user list -->
<SceneryInfoUserList :station="station" />
<!-- spawn list -->
<SceneryInfoSpawnList :station="station" />
</div>
</section>
</div>
</template>
<script lang="ts">
import { PropType, defineComponent } from 'vue';
import SceneryInfoDispatcher from './SceneryInfo/SceneryInfoDispatcher.vue';
import SceneryInfoIcons from './SceneryInfo/SceneryInfoIcons.vue';
import SceneryInfoUserList from './SceneryInfo/SceneryInfoUserList.vue';
import SceneryInfoSpawnList from './SceneryInfo/SceneryInfoSpawnList.vue';
import SceneryInfoRoutes from './SceneryInfo/SceneryInfoRoutes.vue';
import Station from '../../scripts/interfaces/Station';
export default defineComponent({
components: {
SceneryInfoDispatcher,
SceneryInfoIcons,
SceneryInfoUserList,
SceneryInfoSpawnList,
SceneryInfoRoutes
},
props: {
station: {
type: Object as PropType<Station>,
required: true
},
timetableOnly: Boolean
},
data: () => ({
onlineFrom: -1
})
});
</script>
<style lang="scss">
@import '../../styles/responsive.scss';
@import '../../styles/badge.scss';
h3.section-header {
margin: 0.5em 0;
padding: 0.3em;
display: flex;
justify-content: center;
align-items: center;
font-size: 1.2em;
img {
width: 1.1em;
margin-left: 0.5em;
}
}
.info-lists {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
margin-top: 1em;
}
.scenery-info-general {
margin-top: 1em;
}
.scenery-general-list {
display: flex;
justify-content: center;
flex-wrap: wrap;
span {
margin: 0 0.15em;
}
}
.scenery-topic a {
font-weight: bold;
}
</style>
@@ -3,7 +3,12 @@
<div class="dispatcher" v-if="station.onlineInfo">
<span
class="dispatcher_level"
:style="calculateExpStyle(station.onlineInfo.dispatcherExp, station.onlineInfo.dispatcherIsSupporter)"
:style="
calculateExpStyle(
station.onlineInfo.dispatcherExp,
station.onlineInfo.dispatcherIsSupporter
)
"
>
{{ station.onlineInfo.dispatcherExp > 1 ? station.onlineInfo.dispatcherExp : 'L' }}
</span>
@@ -30,7 +35,7 @@
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { PropType, defineComponent } from 'vue';
import dateMixin from '../../../mixins/dateMixin';
import imageMixin from '../../../mixins/imageMixin';
import routerMixin from '../../../mixins/routerMixin';
@@ -39,18 +44,18 @@ import Station from '../../../scripts/interfaces/Station';
import StationStatusBadge from '../../Global/StationStatusBadge.vue';
export default defineComponent({
mixins: [styleMixin, dateMixin, routerMixin, imageMixin],
props: {
station: {
type: Object as () => Station,
default: {},
},
onlineFrom: {
type: Number,
default: -1,
},
mixins: [styleMixin, dateMixin, routerMixin, imageMixin],
props: {
station: {
type: Object as PropType<Station>,
required: true
},
components: { StationStatusBadge }
onlineFrom: {
type: Number,
default: -1
}
},
components: { StationStatusBadge }
});
</script>
@@ -98,4 +103,3 @@ export default defineComponent({
}
}
</style>
@@ -76,7 +76,7 @@
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { PropType, defineComponent } from 'vue';
import imageMixin from '../../../mixins/imageMixin';
import stationInfoMixin from '../../../mixins/stationInfoMixin';
import styleMixin from '../../../mixins/styleMixin';
@@ -86,10 +86,10 @@ export default defineComponent({
mixins: [stationInfoMixin, styleMixin, imageMixin],
props: {
station: {
type: Object as () => Station,
default: {},
},
},
type: Object as PropType<Station>,
required: true
}
}
});
</script>
@@ -118,4 +118,3 @@ export default defineComponent({
}
}
</style>
@@ -1,129 +1,142 @@
<template>
<section class="info-routes" v-if="station.generalInfo">
<div class="routes one-way" v-if="station.generalInfo.routes.oneWay.length > 0">
<b>{{ $t('scenery.one-way-routes') }}</b>
<ul class="routes-list">
<li v-for="route in station.generalInfo.routes.oneWay" @click="setActiveShowLength(route.name)">
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }"> {{ route.name }}</span>
<span v-if="route.speed" class="speed">
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
</span>
<span v-if="route.SBL" class="sbl">SBL</span>
</li>
</ul>
</div>
<div class="routes two-way" v-if="station.generalInfo.routes.twoWay.length > 0">
<b>{{ $t('scenery.two-way-routes') }}</b>
<ul class="routes-list">
<li v-for="(route, i) in station.generalInfo.routes.twoWay" @click="setActiveShowLength(route.name)">
<span :class="{ 'no-catenary': !route.catenary, internal: route.isInternal }">{{ route.name }}</span>
<span v-if="route.speed" class="speed">
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
</span>
<span v-if="route.SBL" class="sbl">SBL</span>
</li>
</ul>
</div>
</section>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import Station from '../../../scripts/interfaces/Station';
export default defineComponent({
props: {
station: {
type: Object as () => Station,
default: {},
},
},
methods: {
setActiveShowLength(name: string) {
if (this.activeShowLength.includes(name)) this.activeShowLength.splice(this.activeShowLength.indexOf(name), 1);
else this.activeShowLength.push(name);
},
},
data() {
return {
activeShowLength: [] as string[],
};
},
});
</script>
<style lang="scss" scoped>
.info-routes {
display: flex;
justify-content: center;
flex-wrap: wrap;
margin: 1em 0;
}
.routes {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
padding: 0.25em;
}
ul.routes-list {
margin: 0.45em 0.25em;
display: flex;
justify-content: center;
flex-wrap: wrap;
li {
margin: 0.5em 0.25em;
cursor: pointer;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
span {
padding: 0.2em 0.25em;
background-color: #007599;
font-weight: bold;
&.no-catenary {
background-color: #686868;
}
&.internal {
text-decoration: underline;
}
&.speed {
background-color: #404040;
color: #cfcfcf;
}
&.sbl {
color: var(--clr-primary);
background-color: #404040;
}
&:last-child {
border-radius: 0 0.5em 0.5em 0;
}
&:first-child {
border-radius: 0.5em 0 0 0.5em;
}
&:only-child {
border-radius: 0.5em;
}
}
}
}
</style>
<template>
<section class="info-routes" v-if="station.generalInfo">
<div class="routes one-way" v-if="station.generalInfo.routes.oneWay.length > 0">
<b>{{ $t('scenery.one-way-routes') }}</b>
<ul class="routes-list">
<li
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">
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
</span>
<span v-if="route.SBL" class="sbl">SBL</span>
</li>
</ul>
</div>
<div class="routes two-way" v-if="station.generalInfo.routes.twoWay.length > 0">
<b>{{ $t('scenery.two-way-routes') }}</b>
<ul class="routes-list">
<li
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">
{{ activeShowLength.includes(route.name) ? route.length + 'm' : route.speed }}
</span>
<span v-if="route.SBL" class="sbl">SBL</span>
</li>
</ul>
</div>
</section>
</template>
<script lang="ts">
import { PropType, defineComponent } from 'vue';
import Station from '../../../scripts/interfaces/Station';
export default defineComponent({
props: {
station: {
type: Object as PropType<Station>,
required: true
}
},
methods: {
setActiveShowLength(name: string) {
if (this.activeShowLength.includes(name))
this.activeShowLength.splice(this.activeShowLength.indexOf(name), 1);
else this.activeShowLength.push(name);
}
},
data() {
return {
activeShowLength: [] as string[]
};
}
});
</script>
<style lang="scss" scoped>
.info-routes {
display: flex;
justify-content: center;
flex-wrap: wrap;
margin: 1em 0;
}
.routes {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
padding: 0.25em;
}
ul.routes-list {
margin: 0.45em 0.25em;
display: flex;
justify-content: center;
flex-wrap: wrap;
li {
margin: 0.5em 0.25em;
cursor: pointer;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
span {
padding: 0.2em 0.25em;
background-color: #007599;
font-weight: bold;
&.no-catenary {
background-color: #686868;
}
&.internal {
text-decoration: underline;
}
&.speed {
background-color: #404040;
color: #cfcfcf;
}
&.sbl {
color: var(--clr-primary);
background-color: #404040;
}
&:last-child {
border-radius: 0 0.5em 0.5em 0;
}
&:first-child {
border-radius: 0.5em 0 0 0.5em;
}
&:only-child {
border-radius: 0.5em;
}
}
}
}
</style>
@@ -1,65 +1,71 @@
<template>
<section class="info-spawn-list">
<h3 class="spawn-header section-header">
<img :src="getIcon('spawn')" alt="icon-spawn" />
&nbsp;{{ $t('scenery.spawns') }} &nbsp;
<span class="text--primary">{{ station.onlineInfo?.spawns.length || '0' }}</span>
</h3>
<span v-if="station.onlineInfo">
<span
class="badge spawn"
v-for="(spawn, i) in sortedSpawns"
:key="spawn.spawnName + station.onlineInfo?.dispatcherName + i"
:data-electrified="spawn.isElectrified"
>
<span class="spawn_name">{{ spawn.spawnName }}</span>
<span class="spawn_length">{{ spawn.spawnLength }}m</span>
</span>
</span>
<span class="badge spawn badge-none" v-if="!station.onlineInfo || station.onlineInfo.spawns.length == 0"
>{{ $t('scenery.no-spawns') }}
</span>
</section>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import imageMixin from '../../../mixins/imageMixin';
import Station from '../../../scripts/interfaces/Station';
export default defineComponent({
mixins: [imageMixin],
props: {
station: {
type: Object as () => Station,
default: {},
},
},
computed: {
sortedSpawns() {
return this.station.onlineInfo?.spawns.sort((s1, s2) => (s1.spawnLength < s2.spawnLength ? 1 : -1));
},
},
});
</script>
<style lang="scss" scoped>
@import '../../../styles/variables.scss';
.spawn {
color: white;
&_length {
background-color: #404040;
color: #cfcfcf;
}
&[data-electrified='true'] > &_name {
background-color: #007599;
}
}
</style>
<template>
<section class="info-spawn-list">
<h3 class="spawn-header section-header">
<img :src="getIcon('spawn')" alt="icon-spawn" />
&nbsp;{{ $t('scenery.spawns') }} &nbsp;
<span class="text--primary">{{ station.onlineInfo?.spawns.length || '0' }}</span>
</h3>
<span v-if="station.onlineInfo">
<span
class="badge spawn"
v-for="(spawn, i) in sortedSpawns"
:key="spawn.spawnName + station.onlineInfo?.dispatcherName + i"
:data-electrified="spawn.isElectrified"
>
<span class="spawn_name">{{ spawn.spawnName }}</span>
<span class="spawn_length">{{ spawn.spawnLength }}m</span>
</span>
</span>
<span
class="badge spawn badge-none"
v-if="!station.onlineInfo || station.onlineInfo.spawns.length == 0"
>{{ $t('scenery.no-spawns') }}
</span>
</section>
</template>
<script lang="ts">
import { PropType, defineComponent } from 'vue';
import imageMixin from '../../../mixins/imageMixin';
import Station from '../../../scripts/interfaces/Station';
export default defineComponent({
mixins: [imageMixin],
props: {
station: {
type: Object as PropType<Station>,
required: true
}
},
computed: {
sortedSpawns() {
if (!this.station.onlineInfo) return [];
return [...this.station.onlineInfo.spawns].sort((s1, s2) =>
s1.spawnLength < s2.spawnLength ? 1 : -1
);
}
}
});
</script>
<style lang="scss" scoped>
@import '../../../styles/variables.scss';
.spawn {
color: white;
&_length {
background-color: #404040;
color: #cfcfcf;
}
&[data-electrified='true'] > &_name {
background-color: #007599;
}
}
</style>
@@ -23,7 +23,10 @@
<span style="color: #eee">{{ station.onlineInfo?.scheduledTrains?.length || '0' }}</span>
/
<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>
@@ -31,7 +34,7 @@
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { PropType, defineComponent } from 'vue';
import imageMixin from '../../../mixins/imageMixin';
import Station from '../../../scripts/interfaces/Station';
@@ -39,10 +42,10 @@ export default defineComponent({
mixins: [imageMixin],
props: {
station: {
type: Object as () => Station,
default: {},
},
},
type: Object as PropType<Station>,
required: true
}
}
});
</script>
@@ -1,131 +1,136 @@
<template>
<section class="info-user-list">
<h3 class="user-header section-header">
<img :src="getIcon('user')" alt="icon-user" />
&nbsp;{{ $t('scenery.users') }} &nbsp;
<span class="text--primary">{{ station.onlineInfo?.currentUsers || '0' }}</span
>&nbsp;/&nbsp;<span class="text--primary">{{ station.onlineInfo?.maxUsers || '0' }}</span>
</h3>
<div
v-for="(train, i) in computedStationTrains"
class="badge user"
:class="train.stopStatus"
:key="train.trainId"
tabindex="0"
@click.prevent="selectModalTrain(train.trainId, $event.currentTarget)"
@keydown.enter="selectModalTrain(train.trainId, $event.currentTarget)"
>
<span class="user_train">{{ train.trainNo }}</span>
<span class="user_name">{{ train.driverName }}</span>
</div>
<div class="badge user badge-none" v-if="!computedStationTrains || computedStationTrains.length == 0">
{{ $t('scenery.no-users') }}
</div>
</section>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
import imageMixin from '../../../mixins/imageMixin';
import modalTrainMixin from '../../../mixins/modalTrainMixin';
import routerMixin from '../../../mixins/routerMixin';
import Station from '../../../scripts/interfaces/Station';
import { useStore } from '../../../store/store';
export default defineComponent({
mixins: [routerMixin, imageMixin, modalTrainMixin],
props: {
station: {
type: Object as () => Station,
default: {},
},
},
setup(props) {
const store = useStore();
const computedStationTrains = computed(() => {
if (!props.station) return [];
const station = props.station as Station;
if (!station.onlineInfo) return [];
if (!station.onlineInfo.stationTrains) return [];
return station.onlineInfo.stationTrains.map((train) => {
const scheduledTrainStatus = station.onlineInfo?.scheduledTrains?.find((st) => st.trainNo === train.trainNo);
return {
...train,
stopStatus: scheduledTrainStatus?.stopStatus || 'no-timetable',
};
});
});
return { computedStationTrains, store };
},
});
</script>
<style lang="scss" scoped>
$no-timetable: #aaa;
$departed: springgreen;
$stopped: #ffa600;
$online: gold;
$terminated: salmon;
$disconnected: slategray;
.info-user-list {
width: 100%;
ul {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
}
.user {
cursor: pointer;
&_train {
color: black;
background-color: $no-timetable;
transition: background-color 200ms;
-ms-transition: background-color 200ms;
-webkit-transition: background-color 200ms;
}
&.no-timetable .user_train {
background-color: $no-timetable;
}
&.departed > &_train {
background-color: $departed;
}
&.stopped > &_train {
background-color: $stopped;
}
&.online > &_train {
background-color: $online;
}
&.terminated > &_train {
background-color: $terminated;
}
&.disconnected > &_train {
background-color: $disconnected;
}
&.offline {
background: firebrick;
pointer-events: none;
}
}
</style>
<template>
<section class="info-user-list">
<h3 class="user-header section-header">
<img :src="getIcon('user')" alt="icon-user" />
&nbsp;{{ $t('scenery.users') }} &nbsp;
<span class="text--primary">{{ station.onlineInfo?.currentUsers || '0' }}</span
>&nbsp;/&nbsp;<span class="text--primary">{{ station.onlineInfo?.maxUsers || '0' }}</span>
</h3>
<div
v-for="train in computedStationTrains"
class="badge user"
:class="train.stopStatus"
:key="train.trainId"
tabindex="0"
@click.prevent="selectModalTrain(train.trainId, $event.currentTarget)"
@keydown.enter="selectModalTrain(train.trainId, $event.currentTarget)"
>
<span class="user_train">{{ train.trainNo }}</span>
<span class="user_name">{{ train.driverName }}</span>
</div>
<div
class="badge user badge-none"
v-if="!computedStationTrains || computedStationTrains.length == 0"
>
{{ $t('scenery.no-users') }}
</div>
</section>
</template>
<script lang="ts">
import { PropType, computed, defineComponent } from 'vue';
import imageMixin from '../../../mixins/imageMixin';
import modalTrainMixin from '../../../mixins/modalTrainMixin';
import routerMixin from '../../../mixins/routerMixin';
import Station from '../../../scripts/interfaces/Station';
import { useStore } from '../../../store/store';
export default defineComponent({
mixins: [routerMixin, imageMixin, modalTrainMixin],
props: {
station: {
type: Object as PropType<Station>,
required: true
}
},
setup(props) {
const store = useStore();
const computedStationTrains = computed(() => {
if (!props.station) return [];
const station = props.station as Station;
if (!station.onlineInfo) return [];
if (!station.onlineInfo.stationTrains) return [];
return station.onlineInfo.stationTrains.map((train) => {
const scheduledTrainStatus = station.onlineInfo?.scheduledTrains?.find(
(st) => st.trainNo === train.trainNo
);
return {
...train,
stopStatus: scheduledTrainStatus?.stopStatus || 'no-timetable'
};
});
});
return { computedStationTrains, store };
}
});
</script>
<style lang="scss" scoped>
$no-timetable: #aaa;
$departed: springgreen;
$stopped: #ffa600;
$online: gold;
$terminated: salmon;
$disconnected: slategray;
.info-user-list {
width: 100%;
ul {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
}
.user {
cursor: pointer;
&_train {
color: black;
background-color: $no-timetable;
transition: background-color 200ms;
-ms-transition: background-color 200ms;
-webkit-transition: background-color 200ms;
}
&.no-timetable .user_train {
background-color: $no-timetable;
}
&.departed > &_train {
background-color: $departed;
}
&.stopped > &_train {
background-color: $stopped;
}
&.online > &_train {
background-color: $online;
}
&.terminated > &_train {
background-color: $terminated;
}
&.disconnected > &_train {
background-color: $disconnected;
}
&.offline {
background: firebrick;
pointer-events: none;
}
}
</style>
+481 -457
View File
@@ -1,457 +1,481 @@
<template>
<section class="scenery-timetable">
<div class="timetable-header">
<h3>
<img :src="getIcon('timetable')" alt="icon-timetable" />
<span>{{ $t('scenery.timetables') }}</span>
<span>
<span class="text--primary">{{ station.onlineInfo?.scheduledTrains?.length || '0' }}</span>
<span> / </span>
<span class="text--grayed">
{{ station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed).length || '0' }}
</span>
</span>
<span class="header_links">
<a :href="`https://pragotron-td2.web.app/board?name=${station.name}`" target="_blank" :title="$t('scenery.pragotron-link')">
<img :src="getIcon('pragotron')" alt="icon-pragotron" />
</a>
<a :href="tabliceZbiorczeHref" target="_blank" :title="$t('scenery.tablice-link')">
<img :src="getIcon('tablice', 'ico')" alt="icon-tablice" />
</a>
</span>
</h3>
<div class="timetable-checkpoints" v-if="station?.generalInfo?.checkpoints">
<span v-for="(cp, i) in station.generalInfo.checkpoints" :key="i">
{{ (i > 0 && '&bull;') || '' }}
<button
:key="cp.checkpointName"
class="checkpoint_item"
:class="{ current: chosenCheckpoint === cp.checkpointName }"
@click="setCheckpoint(cp)"
>
{{ cp.checkpointName }}
</button>
</span>
</div>
</div>
<div class="timetable-list">
<div style="padding-bottom: 5em" v-if="store.dataStatuses.trains == 0 && computedScheduledTrains.length == 0">
<Loading />
</div>
<span class="timetable-item empty" v-else-if="computedScheduledTrains.length == 0 && !station.onlineInfo">
{{ $t('scenery.offline') }}
</span>
<span class="timetable-item empty" v-else-if="computedScheduledTrains.length == 0">
{{ $t('scenery.no-timetables') }}
</span>
<transition-group name="list-anim">
<div
class="timetable-item"
v-for="(scheduledTrain, i) in computedScheduledTrains"
:key="scheduledTrain.trainId"
tabindex="0"
@click.prevent.stop="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)"
@keydown.enter.prevent="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)"
>
<span class="timetable-general">
<span class="general-info">
<span class="info-number">
<strong>{{ scheduledTrain.category }}</strong>
{{ scheduledTrain.trainNo }}
<span class="g-tooltip" v-if="scheduledTrain.stopInfo.comments">
<img :src="getIcon('warning')" />
<span class="content" v-html="scheduledTrain.stopInfo.comments"> </span>
</span>
</span>
&nbsp;|&nbsp;
<span>
{{ scheduledTrain.driverName }}
</span>
<div class="info-route">
<strong>{{ scheduledTrain.beginsAt }} - {{ scheduledTrain.terminatesAt }}</strong>
</div>
<ScheduledTrainStatus :scheduledTrain="scheduledTrain" />
</span>
</span>
<span class="timetable-schedule">
<span class="schedule-arrival">
<span class="arrival-time begins" v-if="scheduledTrain.stopInfo.beginsHere">
{{ $t('timetables.begins') }}
</span>
<span class="arrival-time" v-else>
<div v-if="scheduledTrain.stopInfo.arrivalDelay == 0">
<span>{{ timestampToString(scheduledTrain.stopInfo.arrivalTimestamp) }}</span>
</div>
<div v-else>
<div>
<s style="margin-right: 0.2em" class="text--grayed">{{ timestampToString(scheduledTrain.stopInfo.arrivalTimestamp) }}</s>
</div>
<span>
{{ timestampToString(scheduledTrain.stopInfo.arrivalRealTimestamp) }}
({{ scheduledTrain.stopInfo.arrivalDelay > 0 ? '+' : '' }}{{ scheduledTrain.stopInfo.arrivalDelay }})
</span>
</div>
</span>
</span>
<span class="schedule-stop">
<span class="stop-connection">
{{ scheduledTrain.arrivingLine }}
</span>
<span class="stop-time">
{{ scheduledTrain.stopInfo.stopTime || '' }}
{{ scheduledTrain.stopInfo.stopTime ? scheduledTrain.stopInfo.stopType || 'pt' : '' }}
</span>
<span class="stop-connection">
{{ scheduledTrain.departureLine }}
</span>
</span>
<span class="schedule-departure">
<span class="departure-time terminates" v-if="scheduledTrain.stopInfo.terminatesHere">
{{ $t('timetables.terminates') }}
</span>
<span class="departure-time" v-else>
<div v-if="scheduledTrain.stopInfo.departureDelay == 0">
<span>{{ timestampToString(scheduledTrain.stopInfo.departureTimestamp) }}</span>
</div>
<div v-else>
<div>
<s style="margin-right: 0.2em" class="text--grayed">{{ timestampToString(scheduledTrain.stopInfo.departureTimestamp) }}</s>
</div>
<span>
{{ timestampToString(scheduledTrain.stopInfo.departureRealTimestamp) }}
({{ scheduledTrain.stopInfo.departureDelay > 0 ? '+' : '' }}{{ scheduledTrain.stopInfo.departureDelay }})
</span>
</div>
</span>
</span>
</span>
</div>
</transition-group>
</div>
</section>
</template>
<script lang="ts">
import SelectBox from '../Global/SelectBox.vue';
import { computed, defineComponent, PropType, ref } from '@vue/runtime-core';
import { useRoute } from 'vue-router';
import Loading from '../Global/Loading.vue';
import TrainModal from '../Global/TrainModal.vue';
import dateMixin from '../../mixins/dateMixin';
import routerMixin from '../../mixins/routerMixin';
import Station from '../../scripts/interfaces/Station';
import { useStore } from '../../store/store';
import imageMixin from '../../mixins/imageMixin';
import modalTrainMixin from '../../mixins/modalTrainMixin';
import ScheduledTrainStatus from './ScheduledTrainStatus.vue';
export default defineComponent({
name: 'SceneryTimetable',
components: { SelectBox, Loading, TrainModal, ScheduledTrainStatus },
mixins: [dateMixin, routerMixin, imageMixin, modalTrainMixin],
props: {
station: {
type: Object as PropType<Station>,
required: true,
},
timetableOnly: {
type: Boolean,
},
},
data: () => ({
listOpen: false,
}),
mounted() {
this.loadSelectedOption();
},
activated() {
this.loadSelectedOption();
},
setup(props) {
const route = useRoute();
const currentURL = computed(() => `${location.origin}${route.fullPath}`);
const store = useStore();
const chosenCheckpoint = ref(
props.station?.generalInfo?.checkpoints?.length == 0 ? '' : props.station?.generalInfo?.checkpoints[0].checkpointName || null
);
const computedScheduledTrains = computed(() => {
if (!props.station) return [];
const station = props.station as Station;
let scheduledTrains =
station.generalInfo?.checkpoints.find((cp) => cp.checkpointName === chosenCheckpoint.value)?.scheduledTrains ||
station.onlineInfo?.scheduledTrains ||
[];
if (!scheduledTrains) return [];
return (
scheduledTrains.sort((a, b) => {
if (a.stopStatusID > b.stopStatusID) return 1;
if (a.stopStatusID < b.stopStatusID) return -1;
if (a.stopInfo.arrivalTimestamp > b.stopInfo.arrivalTimestamp) return 1;
if (a.stopInfo.arrivalTimestamp < b.stopInfo.arrivalTimestamp) return -1;
return a.stopInfo.departureTimestamp > b.stopInfo.departureTimestamp ? 1 : -1;
}) || []
);
});
return {
currentURL,
chosenCheckpoint,
computedScheduledTrains,
store,
};
},
computed: {
tabliceZbiorczeHref() {
let url = `https://tablice-td2.web.app/?station=${this.station.name}`;
if (this.chosenCheckpoint) url += `&checkpoint=${this.chosenCheckpoint}`;
return url;
},
},
methods: {
loadSelectedOption() {
if (!this.station) return;
if (!this.station.generalInfo) return;
if (!this.station.generalInfo.checkpoints) return;
if (this.station.generalInfo.checkpoints.length == 0) return;
if (this.chosenCheckpoint != '') return;
this.chosenCheckpoint = this.station.generalInfo.checkpoints[0].checkpointName;
},
setCheckpoint(cp: { checkpointName: string }) {
this.chosenCheckpoint = cp.checkpointName;
},
showTimetableOnlyView() {
this.$router.push(`${this.$route.fullPath}&timetableOnly=1`);
},
},
});
</script>
<style lang="scss" scoped>
@import '../../styles/responsive.scss';
@import '../../styles/variables.scss';
@import '../../styles/animations.scss';
.scenery-timetable {
height: 100%;
overflow-y: scroll;
padding: 0 0.5em;
}
.timetable-header {
position: sticky;
top: 0;
z-index: 99;
background-color: #181818;
padding: 0.5em;
img {
width: 25px;
vertical-align: middle;
}
h3 {
display: flex;
justify-content: center;
flex-wrap: wrap;
align-items: center;
gap: 0.5em;
font-size: 1.3em;
}
}
.header_links {
display: flex;
gap: 0.5em;
margin-left: 0.5em;
}
.timetable {
&-count {
margin-left: 0.5em;
}
&-item {
margin: 0.5em auto;
padding: 0.5em;
max-width: 1100px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.2em 0.5em;
overflow: hidden;
background: #353535;
cursor: pointer;
z-index: 10;
&.empty {
padding: 1rem;
font-size: 1.2em;
color: #bbb;
}
}
&-general {
display: flex;
align-items: center;
justify-content: space-between;
text-align: left;
}
&-schedule {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.2em;
align-items: center;
width: 100%;
max-width: 400px;
margin: 0 auto;
}
}
.timetable-list {
position: relative;
}
.timetable-checkpoints {
display: flex;
justify-content: center;
flex-wrap: wrap;
font-size: 1.1em;
margin-top: 0.5em;
button.checkpoint_item {
color: #aaa;
display: inline;
}
.checkpoint_item.current {
font-weight: bold;
color: $accentCol;
}
}
.general-info {
display: flex;
flex-wrap: wrap;
.info-number {
color: $accentCol;
}
.info-route {
width: 100%;
}
.g-tooltip > .content {
z-index: 100;
color: white;
left: 110%;
}
img {
width: 1.1em;
}
}
.schedule {
&-arrival,
&-departure {
font-size: 1.15em;
}
&-stop {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.5em;
align-items: end;
.stop-connection {
font-size: 0.95em;
}
.stop-time {
position: relative;
inline-size: max-content;
align-self: center;
font-size: 0.9em;
color: $accentCol;
&::after {
content: '\027F6';
display: block;
font-size: 2.2em;
line-height: 0.65em;
}
}
}
}
.arrival-time.begins,
.departure-time.terminates {
font-size: 0.85em;
}
@include smallScreen {
.timetable-item {
grid-template-columns: 1fr;
}
}
</style>
<template>
<section class="scenery-timetable">
<div class="timetable-header">
<h3>
<img :src="getIcon('timetable')" alt="icon-timetable" />
<span>{{ $t('scenery.timetables') }}</span>
<span>
<span class="text--primary">{{
station.onlineInfo?.scheduledTrains?.length || '0'
}}</span>
<span> / </span>
<span class="text--grayed">
{{
station.onlineInfo?.scheduledTrains?.filter((train) => train.stopInfo.confirmed)
.length || '0'
}}
</span>
</span>
<span class="header_links">
<a
:href="`https://pragotron-td2.web.app/board?name=${station.name}`"
target="_blank"
:title="$t('scenery.pragotron-link')"
>
<img :src="getIcon('pragotron')" alt="icon-pragotron" />
</a>
<a :href="tabliceZbiorczeHref" target="_blank" :title="$t('scenery.tablice-link')">
<img :src="getIcon('tablice', 'ico')" alt="icon-tablice" />
</a>
</span>
</h3>
<div class="timetable-checkpoints" v-if="station?.generalInfo?.checkpoints">
<span v-for="(cp, i) in station.generalInfo.checkpoints" :key="i">
{{ (i > 0 && '&bull;') || '' }}
<button
:key="cp.checkpointName"
class="checkpoint_item"
:class="{ current: chosenCheckpoint === cp.checkpointName }"
@click="setCheckpoint(cp)"
>
{{ cp.checkpointName }}
</button>
</span>
</div>
</div>
<div class="timetable-list">
<div
style="padding-bottom: 5em"
v-if="store.dataStatuses.trains == 0 && computedScheduledTrains.length == 0"
>
<Loading />
</div>
<span
class="timetable-item empty"
v-else-if="computedScheduledTrains.length == 0 && !station.onlineInfo"
>
{{ $t('scenery.offline') }}
</span>
<span class="timetable-item empty" v-else-if="computedScheduledTrains.length == 0">
{{ $t('scenery.no-timetables') }}
</span>
<transition-group name="list-anim">
<div
class="timetable-item"
v-for="scheduledTrain in computedScheduledTrains"
:key="scheduledTrain.trainId"
tabindex="0"
@click.prevent.stop="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)"
@keydown.enter.prevent="selectModalTrain(scheduledTrain.trainId, $event.currentTarget)"
>
<span class="timetable-general">
<span class="general-info">
<span class="info-number">
<strong>{{ scheduledTrain.category }}</strong>
{{ scheduledTrain.trainNo }}
<span class="g-tooltip" v-if="scheduledTrain.stopInfo.comments">
<img :src="getIcon('warning')" />
<span class="content" v-html="scheduledTrain.stopInfo.comments"> </span>
</span>
</span>
&nbsp;|&nbsp;
<span>
{{ scheduledTrain.driverName }}
</span>
<div class="info-route">
<strong>{{ scheduledTrain.beginsAt }} - {{ scheduledTrain.terminatesAt }}</strong>
</div>
<ScheduledTrainStatus :scheduledTrain="scheduledTrain" />
</span>
</span>
<span class="timetable-schedule">
<span class="schedule-arrival">
<span class="arrival-time begins" v-if="scheduledTrain.stopInfo.beginsHere">
{{ $t('timetables.begins') }}
</span>
<span class="arrival-time" v-else>
<div v-if="scheduledTrain.stopInfo.arrivalDelay == 0">
<span>{{ timestampToString(scheduledTrain.stopInfo.arrivalTimestamp) }}</span>
</div>
<div v-else>
<div>
<s style="margin-right: 0.2em" class="text--grayed">{{
timestampToString(scheduledTrain.stopInfo.arrivalTimestamp)
}}</s>
</div>
<span>
{{ timestampToString(scheduledTrain.stopInfo.arrivalRealTimestamp) }}
({{ scheduledTrain.stopInfo.arrivalDelay > 0 ? '+' : ''
}}{{ scheduledTrain.stopInfo.arrivalDelay }})
</span>
</div>
</span>
</span>
<span class="schedule-stop">
<span class="stop-connection">
{{ scheduledTrain.arrivingLine }}
</span>
<span class="stop-time">
{{ scheduledTrain.stopInfo.stopTime || '' }}
{{
scheduledTrain.stopInfo.stopTime ? scheduledTrain.stopInfo.stopType || 'pt' : ''
}}
</span>
<span class="stop-connection">
{{ scheduledTrain.departureLine }}
</span>
</span>
<span class="schedule-departure">
<span class="departure-time terminates" v-if="scheduledTrain.stopInfo.terminatesHere">
{{ $t('timetables.terminates') }}
</span>
<span class="departure-time" v-else>
<div v-if="scheduledTrain.stopInfo.departureDelay == 0">
<span>{{ timestampToString(scheduledTrain.stopInfo.departureTimestamp) }}</span>
</div>
<div v-else>
<div>
<s style="margin-right: 0.2em" class="text--grayed">{{
timestampToString(scheduledTrain.stopInfo.departureTimestamp)
}}</s>
</div>
<span>
{{ timestampToString(scheduledTrain.stopInfo.departureRealTimestamp) }}
({{ scheduledTrain.stopInfo.departureDelay > 0 ? '+' : ''
}}{{ scheduledTrain.stopInfo.departureDelay }})
</span>
</div>
</span>
</span>
</span>
</div>
</transition-group>
</div>
</section>
</template>
<script lang="ts">
import { computed, defineComponent, PropType, ref } from 'vue';
import { useRoute } from 'vue-router';
import Loading from '../Global/Loading.vue';
import dateMixin from '../../mixins/dateMixin';
import routerMixin from '../../mixins/routerMixin';
import Station from '../../scripts/interfaces/Station';
import { useStore } from '../../store/store';
import imageMixin from '../../mixins/imageMixin';
import modalTrainMixin from '../../mixins/modalTrainMixin';
import ScheduledTrainStatus from './ScheduledTrainStatus.vue';
export default defineComponent({
name: 'SceneryTimetable',
components: { Loading, ScheduledTrainStatus },
mixins: [dateMixin, routerMixin, imageMixin, modalTrainMixin],
props: {
station: {
type: Object as PropType<Station>,
required: true
},
timetableOnly: {
type: Boolean
}
},
data: () => ({
listOpen: false
}),
mounted() {
this.loadSelectedOption();
},
activated() {
this.loadSelectedOption();
},
setup(props) {
const route = useRoute();
const currentURL = computed(() => `${location.origin}${route.fullPath}`);
const store = useStore();
const chosenCheckpoint = ref(
props.station?.generalInfo?.checkpoints?.length == 0
? ''
: props.station?.generalInfo?.checkpoints[0].checkpointName || null
);
const computedScheduledTrains = computed(() => {
if (!props.station) return [];
const station = props.station as Station;
let scheduledTrains =
station.generalInfo?.checkpoints.find((cp) => cp.checkpointName === chosenCheckpoint.value)
?.scheduledTrains ||
station.onlineInfo?.scheduledTrains ||
[];
if (!scheduledTrains) return [];
return (
scheduledTrains.sort((a, b) => {
if (a.stopStatusID > b.stopStatusID) return 1;
if (a.stopStatusID < b.stopStatusID) return -1;
if (a.stopInfo.arrivalTimestamp > b.stopInfo.arrivalTimestamp) return 1;
if (a.stopInfo.arrivalTimestamp < b.stopInfo.arrivalTimestamp) return -1;
return a.stopInfo.departureTimestamp > b.stopInfo.departureTimestamp ? 1 : -1;
}) || []
);
});
return {
currentURL,
chosenCheckpoint,
computedScheduledTrains,
store
};
},
computed: {
tabliceZbiorczeHref() {
let url = `https://tablice-td2.web.app/?station=${this.station.name}`;
if (this.chosenCheckpoint) url += `&checkpoint=${this.chosenCheckpoint}`;
return url;
}
},
methods: {
loadSelectedOption() {
if (!this.station) return;
if (!this.station.generalInfo) return;
if (!this.station.generalInfo.checkpoints) return;
if (this.station.generalInfo.checkpoints.length == 0) return;
if (this.chosenCheckpoint != '') return;
this.chosenCheckpoint = this.station.generalInfo.checkpoints[0].checkpointName;
},
setCheckpoint(cp: { checkpointName: string }) {
this.chosenCheckpoint = cp.checkpointName;
},
showTimetableOnlyView() {
this.$router.push(`${this.$route.fullPath}&timetableOnly=1`);
}
}
});
</script>
<style lang="scss" scoped>
@import '../../styles/responsive.scss';
@import '../../styles/variables.scss';
@import '../../styles/animations.scss';
.scenery-timetable {
height: 100%;
overflow-y: scroll;
padding: 0 0.5em;
}
.timetable-header {
position: sticky;
top: 0;
z-index: 99;
background-color: #181818;
padding: 0.5em;
img {
width: 25px;
vertical-align: middle;
}
h3 {
display: flex;
justify-content: center;
flex-wrap: wrap;
align-items: center;
gap: 0.5em;
font-size: 1.3em;
}
}
.header_links {
display: flex;
gap: 0.5em;
margin-left: 0.5em;
}
.timetable {
&-count {
margin-left: 0.5em;
}
&-item {
margin: 0.5em auto;
padding: 0.5em;
max-width: 1100px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.2em 0.5em;
overflow: hidden;
background: #353535;
cursor: pointer;
z-index: 10;
&.empty {
padding: 1rem;
font-size: 1.2em;
color: #bbb;
}
}
&-general {
display: flex;
align-items: center;
justify-content: space-between;
text-align: left;
}
&-schedule {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.2em;
align-items: center;
width: 100%;
max-width: 400px;
margin: 0 auto;
}
}
.timetable-list {
position: relative;
}
.timetable-checkpoints {
display: flex;
justify-content: center;
flex-wrap: wrap;
font-size: 1.1em;
margin-top: 0.5em;
button.checkpoint_item {
color: #aaa;
display: inline;
}
.checkpoint_item.current {
font-weight: bold;
color: $accentCol;
}
}
.general-info {
display: flex;
flex-wrap: wrap;
.info-number {
color: $accentCol;
}
.info-route {
width: 100%;
}
.g-tooltip > .content {
z-index: 100;
color: white;
left: 110%;
}
img {
width: 1.1em;
}
}
.schedule {
&-arrival,
&-departure {
font-size: 1.15em;
}
&-stop {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.5em;
align-items: end;
.stop-connection {
font-size: 0.95em;
}
.stop-time {
position: relative;
inline-size: max-content;
align-self: center;
font-size: 0.9em;
color: $accentCol;
&::after {
content: '\027F6';
display: block;
font-size: 2.2em;
line-height: 0.65em;
}
}
}
}
.arrival-time.begins,
.departure-time.terminates {
font-size: 0.85em;
}
@include smallScreen {
.timetable-item {
grid-template-columns: 1fr;
}
}
</style>
@@ -1,110 +1,117 @@
<template>
<section class="scenery-table-section">
<Loading v-if="dataStatus != DataStatus.Loaded" />
<div class="no-history" v-else-if="historyList.length == 0">{{ $t('scenery.history-list-empty') }}</div>
<table class="scenery-history-table" v-else>
<thead>
<th>{{ $t('scenery.timetables-history-id') }}</th>
<th>{{ $t('scenery.timetables-history-number') }}</th>
<th>{{ $t('scenery.timetables-history-route') }}</th>
<th>{{ $t('scenery.timetables-history-driver') }}</th>
<th>{{ $t('scenery.timetables-history-author') }}</th>
<th>{{ $t('scenery.timetables-history-date') }}</th>
</thead>
<tbody>
<tr v-for="historyItem in historyList">
<td>
<router-link :to="`/journal/timetables?timetableId=${historyItem.id}`">#{{ historyItem.id }}</router-link>
</td>
<td>
<b class="text--primary">{{ historyItem.trainCategoryCode }}</b> <br />
{{ historyItem.trainNo }}
</td>
<td>{{ historyItem.route.replace('|', ' -> ') }}</td>
<td>{{ historyItem.driverName }}</td>
<td>
<router-link
v-if="historyItem.authorName"
:to="`/journal/timetables?authorName=${historyItem.authorName}`"
>{{ historyItem.authorName }}
</router-link>
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
</td>
<td>
<b>{{ localeDay(historyItem.beginDate, $i18n.locale) }}</b>
{{ localeTime(historyItem.beginDate, $i18n.locale) }}
</td>
</tr>
</tbody>
</table>
</section>
<div class="bottom-info">
<button class="btn btn--option" v-if="historyList.length > 0" @click="navigateToHistory()">
{{ $t('scenery.bottom-info') }}
</button>
</div>
</template>
<script lang="ts">
import axios from 'axios';
import { defineComponent, PropType } from 'vue';
import dateMixin from '../../mixins/dateMixin';
import { DataStatus } from '../../scripts/enums/DataStatus';
import { TimetableHistory, SceneryTimetableHistory } from '../../scripts/interfaces/api/TimetablesAPIData';
import Station from '../../scripts/interfaces/Station';
import { URLs } from '../../scripts/utils/apiURLs';
import Loading from '../Global/Loading.vue';
import listObserverMixin from '../../mixins/listObserverMixin';
export default defineComponent({
name: 'SceneryTimetablesHistory',
mixins: [dateMixin, listObserverMixin],
props: {
station: {
type: Object as PropType<Station>,
required: true,
},
},
data() {
return {
historyList: [] as TimetableHistory[],
dataStatus: DataStatus.Loading,
DataStatus,
};
},
async activated() {
const fetchedHistory = await this.fetchAPIData();
if (fetchedHistory) this.historyList = fetchedHistory.timetables;
},
methods: {
async fetchAPIData(countFrom = 0, countLimit = 15): Promise<SceneryTimetableHistory | null> {
try {
const requestString = `${URLs.stacjownikAPI}/api/getIssuedTimetables?name=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
const historyAPIData: SceneryTimetableHistory = await (await axios.get(requestString)).data;
this.dataStatus = DataStatus.Loaded;
return historyAPIData;
} catch (error) {
console.error(error);
return null;
}
},
navigateToHistory() {
this.$router.push(`/journal/timetables?issuedFrom=${this.station.name}`);
},
},
components: { Loading },
});
</script>
<style lang="scss" scoped>
@import '../../styles/responsive.scss';
@import '../../styles/sceneryViewTables.scss';
</style>
<template>
<section class="scenery-table-section">
<Loading v-if="dataStatus != DataStatus.Loaded" />
<div class="no-history" v-else-if="historyList.length == 0">
{{ $t('scenery.history-list-empty') }}
</div>
<table class="scenery-history-table" v-else>
<thead>
<th>{{ $t('scenery.timetables-history-id') }}</th>
<th>{{ $t('scenery.timetables-history-number') }}</th>
<th>{{ $t('scenery.timetables-history-route') }}</th>
<th>{{ $t('scenery.timetables-history-driver') }}</th>
<th>{{ $t('scenery.timetables-history-author') }}</th>
<th>{{ $t('scenery.timetables-history-date') }}</th>
</thead>
<tbody>
<tr v-for="historyItem in historyList" :key="historyItem.id">
<td>
<router-link :to="`/journal/timetables?timetableId=${historyItem.id}`"
>#{{ historyItem.id }}</router-link
>
</td>
<td>
<b class="text--primary">{{ historyItem.trainCategoryCode }}</b> <br />
{{ historyItem.trainNo }}
</td>
<td>{{ historyItem.route.replace('|', ' -> ') }}</td>
<td>{{ historyItem.driverName }}</td>
<td>
<router-link
v-if="historyItem.authorName"
:to="`/journal/timetables?authorName=${historyItem.authorName}`"
>{{ historyItem.authorName }}
</router-link>
<i v-else>{{ $t('scenery.timetable-author-unknown') }}</i>
</td>
<td>
<b>{{ localeDay(historyItem.beginDate, $i18n.locale) }}</b>
{{ localeTime(historyItem.beginDate, $i18n.locale) }}
</td>
</tr>
</tbody>
</table>
</section>
<div class="bottom-info">
<button class="btn btn--option" v-if="historyList.length > 0" @click="navigateToHistory()">
{{ $t('scenery.bottom-info') }}
</button>
</div>
</template>
<script lang="ts">
import axios from 'axios';
import { defineComponent, PropType } from 'vue';
import dateMixin from '../../mixins/dateMixin';
import { DataStatus } from '../../scripts/enums/DataStatus';
import {
TimetableHistory,
SceneryTimetableHistory
} from '../../scripts/interfaces/api/TimetablesAPIData';
import Station from '../../scripts/interfaces/Station';
import { URLs } from '../../scripts/utils/apiURLs';
import Loading from '../Global/Loading.vue';
import listObserverMixin from '../../mixins/listObserverMixin';
export default defineComponent({
name: 'SceneryTimetablesHistory',
mixins: [dateMixin, listObserverMixin],
props: {
station: {
type: Object as PropType<Station>,
required: true
}
},
data() {
return {
historyList: [] as TimetableHistory[],
dataStatus: DataStatus.Loading,
DataStatus
};
},
async activated() {
const fetchedHistory = await this.fetchAPIData();
if (fetchedHistory) this.historyList = fetchedHistory.timetables;
},
methods: {
async fetchAPIData(countFrom = 0, countLimit = 15): Promise<SceneryTimetableHistory | null> {
try {
const requestString = `${URLs.stacjownikAPI}/api/getIssuedTimetables?name=${this.station.name}&countFrom=${countFrom}&countLimit=${countLimit}`;
const historyAPIData: SceneryTimetableHistory = await (await axios.get(requestString)).data;
this.dataStatus = DataStatus.Loaded;
return historyAPIData;
} catch (error) {
console.error(error);
return null;
}
},
navigateToHistory() {
this.$router.push(`/journal/timetables?issuedFrom=${this.station.name}`);
}
},
components: { Loading }
});
</script>
<style lang="scss" scoped>
@import '../../styles/responsive.scss';
@import '../../styles/sceneryViewTables.scss';
</style>
@@ -1,6 +1,9 @@
<template>
<div class="general-status">
<span :class="computedScheduledTrain.stopStatus" :title="computedScheduledTrain.stopStatusDescription">
<span
:class="computedScheduledTrain.stopStatus"
:title="computedScheduledTrain.stopStatusDescription"
>
{{ computedScheduledTrain.stopStatusIndicator }}
</span>
</div>
@@ -19,16 +22,21 @@ export default defineComponent({
props: {
scheduledTrain: {
type: Object as PropType<ScheduledTrain>,
required: true,
},
required: true
}
},
computed: {
computedScheduledTrain(): ScheduledTrainComp {
const { prevDepartureLine, prevStationName, stopStatus, nextArrivalLine, nextStationName } = this.scheduledTrain;
const { prevDepartureLine, prevStationName, stopStatus, nextArrivalLine, nextStationName } =
this.scheduledTrain;
const prevDepartureIndicator = prevDepartureLine ? `(${prevDepartureLine}) ${prevStationName}` : '---';
const nextArrivalIndicator = nextArrivalLine ? `(${nextArrivalLine}) ${nextStationName}` : '---';
const prevDepartureIndicator = prevDepartureLine
? `(${prevDepartureLine}) ${prevStationName}`
: '---';
const nextArrivalIndicator = nextArrivalLine
? `(${nextArrivalLine}) ${nextStationName}`
: '---';
let stopStatusDescription = '',
stopStatusIndicator = '';
@@ -36,7 +44,10 @@ export default defineComponent({
switch (stopStatus) {
case StopStatus.arriving:
stopStatusIndicator = `${this.$t('timetables.from')}: ${prevDepartureIndicator}`;
stopStatusDescription = this.$t('timetables.desc-arriving', { prevStationName, prevDepartureLine });
stopStatusDescription = this.$t('timetables.desc-arriving', {
prevStationName,
prevDepartureLine
});
break;
case StopStatus.online:
@@ -51,12 +62,18 @@ export default defineComponent({
case StopStatus.departed:
stopStatusIndicator = `${this.$t('timetables.to')}: ${nextArrivalIndicator}`;
stopStatusDescription = this.$t('timetables.desc-departed', { nextStationName, nextArrivalLine });
stopStatusDescription = this.$t('timetables.desc-departed', {
nextStationName,
nextArrivalLine
});
break;
case StopStatus['departed-away']:
stopStatusIndicator = `${this.$t('timetables.to')}: ${nextArrivalIndicator}`;
stopStatusDescription = this.$t('timetables.desc-departed-away', { nextStationName, nextArrivalLine });
stopStatusDescription = this.$t('timetables.desc-departed-away', {
nextStationName,
nextArrivalLine
});
break;
case StopStatus.terminated:
@@ -70,10 +87,10 @@ export default defineComponent({
return {
...this.scheduledTrain,
stopStatusDescription,
stopStatusIndicator,
stopStatusIndicator
};
},
},
}
}
});
</script>
@@ -110,4 +127,3 @@ export default defineComponent({
}
}
</style>