Compare commits

..

27 Commits

Author SHA1 Message Date
Spythere 916f6070ac Merge pull request #103 from Spythere/development
chore: added back merge gh workflow
2024-08-10 14:18:45 +02:00
Spythere a74ab6eb2a chore: added back merge gh workflow 2024-08-10 14:18:00 +02:00
Spythere 985c699ced Merge pull request #102 from Spythere/development
v1.26.1
2024-08-10 14:16:06 +02:00
Spythere 7e0e9146a5 fix: vehicle thumbnail cargo info 2024-08-10 14:08:32 +02:00
Spythere 30a0f05922 feat: journal dispatchers filtering by sc. hash 2024-08-10 14:00:25 +02:00
Spythere a30e04ca96 bump: v1.26.1 2024-08-09 15:24:48 +02:00
Spythere 1852d3e234 feat: category codes explanation tooltips 2024-08-09 15:24:26 +02:00
Spythere a17bf6c03f Merge branch 'development' 2024-08-09 14:12:07 +02:00
Spythere 766b08bc15 Merge branch 'master' of github.com:Spythere/stacjownik 2024-08-09 14:11:34 +02:00
Spythere cd1a4fa734 hotfix: checkpoint trains filtering 2024-08-06 14:18:26 +02:00
Spythere 619ce97b52 Merge pull request #101 from Spythere/development
v1.26.0
2024-08-05 16:11:07 +02:00
Spythere acbe761068 Merge branch 'development' 2024-08-05 16:02:20 +02:00
Spythere 47d35f335f chore: deleted merge workflow 2024-08-05 16:01:54 +02:00
Spythere 8fda8fa0df chore: package scripts 2024-08-05 15:59:54 +02:00
Spythere 71d697eda5 chore: changed workflow npm to yarn 2024-08-05 15:55:15 +02:00
Spythere f2b1fc5369 chore: disabled workflow on master push 2024-08-05 15:54:02 +02:00
Spythere 4a9b142e16 hotfix: lock files 2024-08-03 01:56:58 +02:00
Spythere 08d8bf3c57 bump: v1.26.0 2024-08-03 01:55:12 +02:00
Spythere 0ee90357aa chore: code structure 2024-08-03 01:53:36 +02:00
Spythere c8964dc20f chore: dispatcher history revamp & statuses 2024-08-02 02:00:44 +02:00
Spythere 6a62276d95 fix: vehicle preview loading 2024-08-01 19:26:25 +02:00
Spythere b8550eed9a chore: cleanup 2024-08-01 19:22:54 +02:00
Spythere 27b23ccc95 chore: lazy thumbnail loading & animations 2024-08-01 19:22:43 +02:00
Spythere b49517aded chore: packages upgrade 2024-07-24 18:55:41 +02:00
Spythere ed2b8be4dc chore: offline & fetching fixes 2024-07-24 17:52:20 +02:00
Spythere 54c1dbbf15 fix: stop labels statuses 2024-07-16 21:39:54 +02:00
Spythere 44f6cf4232 Merge branch 'development' 2024-05-12 15:22:28 +02:00
31 changed files with 1551 additions and 3077 deletions
+2 -5
View File
@@ -1,6 +1,3 @@
# This file was auto-generated by the Firebase CLI
# https://github.com/firebase/firebase-tools
name: Deploy to Firebase Hosting on merge
'on':
push:
@@ -11,10 +8,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm ci && npm run build
- run: yarn && yarn build
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: '${{ secrets.GITHUB_TOKEN }}'
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_STACJOWNIK_TD2 }}'
channelId: live
projectId: stacjownik-td2
projectId: stacjownik-td2
@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm ci && npm run build
- run: yarn && yarn build
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: '${{ secrets.GITHUB_TOKEN }}'
-1
View File
@@ -19,7 +19,6 @@
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
<meta name="msapplication-TileColor" content="#da532c" />
<meta name="theme-color" content="#222222" />
<link rel="icon" href="favicon.ico" />
+457 -1761
View File
File diff suppressed because it is too large Load Diff
+13 -16
View File
@@ -1,11 +1,13 @@
{
"name": "stacjownik",
"version": "1.25.2",
"version": "1.26.1",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"deploy": "yarn build && firebase deploy --only hosting",
"deploy:prod": "yarn build && firebase deploy --only hosting",
"deploy:dev": "yarn build && firebase hosting:channel:deploy dev --expires 7d",
"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",
@@ -19,25 +21,20 @@
"showdown": "^2.1.0",
"vue": "^3.3.4",
"vue-i18n": "^9.4.1",
"vue-router": "^4.2.4"
"vue-router": "^4.4.0"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.3.3",
"@types/node": "^20.6.2",
"@types/node": "^20.14.12",
"@types/showdown": "^2.0.6",
"@vite-pwa/assets-generator": "^0.2.4",
"@vitejs/plugin-vue": "^4.3.4",
"@vue/eslint-config-prettier": "^8.0.0",
"@vue/eslint-config-typescript": "^12.0.0",
"@vue/tsconfig": "^0.4.0",
"axios": "^1.5.0",
"eslint": "^8.49.0",
"eslint-plugin-vue": "^9.17.0",
"prettier": "^3.0.3",
"typescript": "^5.2.2",
"vite": "^4.4.9",
"@vitejs/plugin-vue": "^5.1.0",
"@vue/tsconfig": "^0.5.1",
"axios": "^1.7.2",
"prettier": "^3.3.3",
"typescript": "^5.5.4",
"vite": "^5.3.4",
"vite-plugin-pwa": "^0.20.0",
"vue-tsc": "^1.8.11"
"vue-tsc": "^2.0.28"
},
"browserslist": [
"> 1%",
+5 -15
View File
@@ -81,9 +81,7 @@ export default defineComponent({
isUpdateCardOpen: false,
currentLang: 'pl',
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app',
nextUpdateTime: 0
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app'
}),
created() {
@@ -96,22 +94,13 @@ export default defineComponent({
methods: {
init() {
if (!this.isOnProductionHost) document.title = 'Stacjownik Dev';
this.loadLang();
this.setupOfflineHandling();
this.checkAppVersion();
this.apiStore.setupAPIData();
window.requestAnimationFrame(this.update);
if (!this.isOnProductionHost) document.title = 'Stacjownik Dev';
},
update(t: number) {
if (t >= this.nextUpdateTime) {
this.apiStore.fetchActiveData();
this.nextUpdateTime = t + 20000;
}
window.requestAnimationFrame(this.update);
},
async checkAppVersion() {
@@ -131,7 +120,7 @@ export default defineComponent({
};
this.isUpdateCardOpen =
(storageVersion != '' && storageVersion != version) ||
(storageVersion != '' && storageVersion != version && this.isOnProductionHost) ||
import.meta.env.VITE_UPDATE_TEST === 'test';
} catch (error) {
console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`);
@@ -158,6 +147,7 @@ export default defineComponent({
handleOnlineMode() {
this.store.isOffline = false;
this.apiStore.dataStatuses.connection = Status.Data.Loading;
this.apiStore.connectToAPI();
},
+5 -4
View File
@@ -88,8 +88,9 @@ $unknown: #b93c3c;
.status-badge {
border-radius: 1em;
font-weight: 500;
text-wrap: nowrap;
padding: 0.2em 0.55em;
padding: 0.2rem 0.55rem;
background-color: $online;
@@ -106,13 +107,13 @@ $unknown: #b93c3c;
&.no-limit {
background-color: $no-limit;
font-size: 0.85em;
font-size: 0.9em;
}
&.not-signed,
&.unavailable {
background-color: $unav;
font-size: 0.85em;
font-size: 0.9em;
}
&.afk {
@@ -125,7 +126,7 @@ $unknown: #b93c3c;
background-color: $no-space;
border: 1px solid white;
color: white;
font-size: 0.85em;
font-size: 0.9em;
}
&.unknown,
+14 -12
View File
@@ -2,7 +2,9 @@
<div class="stock-list">
<ul>
<li
v-for="({ vehicleName, vehicleCargo, images, imagesFallbacks }, i) in thumbnailNames"
v-for="(
{ vehicleName, vehicleCargo, images, imagesFallbacks, vehicleString }, i
) in thumbnailNames"
:key="i"
>
<div class="stock-text">
@@ -11,15 +13,11 @@
</div>
<span>
<img
<VehicleThumbnail
v-for="(thumbnailImage, imageIndex) in images"
:data-mouseover="vehicleName"
data-tooltip-type="VehiclePreviewTooltip"
:data-tooltip-content="vehicleName"
:src="`https://static.spythere.eu/thumbnails/v2/${thumbnailImage}.png`"
@error="onImageError($event, imagesFallbacks[imageIndex])"
@click.stop="() => {}"
height="60"
:vehicle-name="vehicleString"
:img-name="thumbnailImage"
:fallback-name="imagesFallbacks[imageIndex]"
/>
</span>
</li>
@@ -30,8 +28,11 @@
<script lang="ts">
import { PropType, defineComponent } from 'vue';
import { useApiStore } from '../../store/apiStore';
import VehicleThumbnail from './VehicleThumbnail.vue';
export default defineComponent({
components: { VehicleThumbnail },
props: {
trainStockList: {
type: Array as PropType<string[]>,
@@ -64,7 +65,8 @@ export default defineComponent({
images: [] as string[],
imagesFallbacks: [] as string[],
vehicleName,
vehicleCargo
vehicleCargo,
vehicleString
};
// Generowanie członów EN57
@@ -143,7 +145,7 @@ export default defineComponent({
else {
let fallbackVehicleImage = 'unknown_cargo';
if (/^(EP|EU)/.test(vehicleName)) fallbackVehicleImage = 'unknown_train';
if (/^(EP|EU|ET|201E)/.test(vehicleName)) fallbackVehicleImage = 'unknown_train';
else if (/^(SM42)/.test(vehicleName)) fallbackVehicleImage = 'unknown_SM42';
else if (/(\d{3}a|(Bau|Gor)\d{2}|304C)_/.test(vehicleName))
fallbackVehicleImage = 'unknown_passenger';
@@ -160,7 +162,7 @@ export default defineComponent({
},
methods: {
onImageError(event: Event, fallbackImage: string) {
onImageError(event: Event, fallbackImage: string) {
(event.target as HTMLImageElement).src = `/images/${fallbackImage}.png`;
}
}
@@ -0,0 +1,61 @@
<template>
<div class="vehicle-thumbnail">
<img
ref="imgRef"
:src="`https://static.spythere.eu/thumbnails/v2/${imgName}.png`"
height="60"
loading="lazy"
data-tooltip-type="VehiclePreviewTooltip"
:data-tooltip-content="vehicleName"
:data-load-status="imgStatus"
@error="onImageError"
@load="onImageLoad"
/>
</div>
</template>
<script setup lang="ts">
import { onMounted, Ref, ref } from 'vue';
const props = defineProps({
vehicleName: { type: String, required: true },
imgName: { type: String, required: true },
fallbackName: { type: String, required: true },
placeholderName: String
});
const imgRef = ref(null) as Ref<HTMLElement | null>;
const imgStatus = ref('loading');
function onImageError(event: Event) {
console.log('error');
(event.target as HTMLImageElement).src = `/images/${props.fallbackName}.png`;
imgStatus.value = 'error';
}
function onImageLoad() {
if (imgStatus.value != 'error') {
imgStatus.value = 'loaded';
}
imgRef.value!.style.opacity = '1';
}
</script>
<style lang="scss" scoped>
.vehicle-thumbnail {
position: relative;
}
img {
opacity: 0;
transition: opacity 100ms ease-in-out;
&[data-load-status='loading'] {
min-height: 60px;
min-width: 150px;
}
}
</style>
@@ -0,0 +1,217 @@
<template>
<li class="dispatcher-history-entry">
<div class="entry-info">
<span>
<span>
<router-link :to="`/journal/dispatchers?search-station=${entry.stationName}`">
<b>{{ entry.stationName }}</b>
</router-link>
<b class="text--grayed"> #{{ entry.stationHash }}</b>
</span>
&bull;
<b
v-if="entry.dispatcherLevel !== null"
class="level-badge dispatcher"
:style="calculateExpStyle(entry.dispatcherLevel, entry.dispatcherIsSupporter)"
>
{{ entry.dispatcherLevel >= 2 ? entry.dispatcherLevel : 'L' }}
</b>
<b style="margin-left: 5px">
<span
v-if="apiStore.donatorsData.includes(entry.dispatcherName)"
data-tooltip-type="DonatorTooltip"
:data-tooltip-content="$t('donations.dispatcher-message')"
>
<router-link
class="text--donator"
:to="`/journal/dispatchers?search-dispatcher=${entry.dispatcherName}`"
>
{{ entry.dispatcherName }}
</router-link>
</span>
<router-link
v-else
:to="`/journal/dispatchers?search-dispatcher=${entry.dispatcherName}`"
>
{{ entry.dispatcherName }}
</router-link>
</b>
<div>
<span v-if="entry.timestampTo">
<b>{{ $d(entry.timestampFrom) }}</b>
{{ timestampToString(entry.timestampFrom) }}
-
<b
v-if="
new Date(entry.timestampFrom).getDate() != new Date(entry.timestampTo).getDate()
"
>
{{ $d(entry.timestampTo) }}
</b>
{{ timestampToString(entry.timestampTo) }} ({{
calculateDuration(entry.currentDuration)
}})
</span>
<router-link
:to="`/scenery?station=${entry.stationName}`"
class="dispatcher-online"
v-else
>
{{ $t('journal.online-since') }}
<b>
{{
new Date().getDate() != new Date(entry.timestampFrom).getDate()
? $d(entry.timestampFrom)
: ''
}}
{{ timestampToString(entry.timestampFrom) }}
</b>
({{ calculateDuration(entry.currentDuration) }})
</router-link>
</div>
</span>
<span class="entry-info-right">
<div>
<span>
{{ $t('scenery.dispatcher-rate') }}
<b class="text--primary"> {{ entry.dispatcherRate }}</b>
</span>
<button class="btn btn--option" @click="toggleExtraInfo">
{{ $t('scenery.dispatcher-status-changes') }}
<b class="text--primary">{{ entry.statusHistory.length }}</b>
</button>
</div>
<b class="region-badge" :aria-describedby="entry.region">
REGION: {{ regions.find((r) => r.id == entry.region)?.name }}
</b>
</span>
</div>
<div class="entry-extra" v-if="showExtraInfo">
<ul class="status-list">
<li v-for="statusItem in entry.statusHistory">
<b style="margin-right: 0.5em">{{
timestampToString(parseInt(statusItem.split('@')[0]))
}}</b>
<StationStatusBadge
:dispatcher-status="Number(statusItem.split('@')[1])"
:is-online="true"
/>
</li>
</ul>
</div>
</li>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
import { regions } from '../../../data/options.json';
import { API } from '../../../typings/api';
import dateMixin from '../../../mixins/dateMixin';
import styleMixin from '../../../mixins/styleMixin';
import { useApiStore } from '../../../store/apiStore';
import StationStatusBadge from '../../Global/StationStatusBadge.vue';
export default defineComponent({
props: {
entry: {
type: Object as PropType<API.DispatcherHistory.Data>,
required: true
},
showExtraInfo: {
type: Boolean,
required: true
}
},
components: { StationStatusBadge },
mixins: [dateMixin, styleMixin],
emits: ['toggleShowExtraInfo'],
data() {
return {
regions,
apiStore: useApiStore()
};
},
methods: {
toggleExtraInfo() {
this.$emit('toggleShowExtraInfo', this.entry.id);
}
}
});
</script>
<style lang="scss" scoped>
@import '../../../styles/responsive.scss';
@import '../../../styles/badge.scss';
.region-badge {
padding: 0 0.25em;
}
.level-badge {
text-align: center;
display: inline-block;
line-height: 1.6em;
}
.dispatcher-online {
color: springgreen;
}
.dispatcher-history-entry {
background-color: #1a1a1a;
padding: 1em;
}
.entry-info {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
line-height: 1.75em;
gap: 0.5em;
}
.entry-info-right {
display: flex;
flex-wrap: wrap;
align-items: center;
text-align: center;
gap: 1em;
}
.entry-extra {
margin-top: 1em;
}
.status-list {
display: flex;
overflow: auto;
gap: 0.5em;
}
.status-list > li {
background-color: #313131;
padding: 0.2rem 0 0.2rem 0.5em;
margin: 0.5em 0;
border-radius: 1em;
}
@include smallScreen {
.entry-info {
flex-direction: column;
justify-content: center;
text-align: center;
}
}
</style>
@@ -15,90 +15,16 @@
{{ $t('app.no-result') }}
</div>
<div v-else>
<table class="dispatchers-table">
<thead>
<th>{{ $t('journal.history-name') }}</th>
<th>{{ $t('journal.history-hash') }}</th>
<th>{{ $t('journal.history-dispatcher') }}</th>
<th>{{ $t('journal.history-level') }}</th>
<th>{{ $t('journal.history-rate') }}</th>
<th>{{ $t('journal.history-region') }}</th>
<th>{{ $t('journal.history-date') }}</th>
</thead>
<tbody>
<transition-group name="list-anim">
<tr v-for="historyItem in dispatcherHistory" :key="historyItem.id">
<td>
<router-link
:to="`/journal/dispatchers?search-station=${historyItem.stationName}`"
>
<b>{{ historyItem.stationName }}</b>
</router-link>
</td>
<td>#{{ historyItem.stationHash }}</td>
<td>
<router-link
:to="`/journal/dispatchers?search-dispatcher=${historyItem.dispatcherName}`"
>
<b
v-if="apiStore.donatorsData.includes(historyItem.dispatcherName)"
class="text--donator"
:title="$t('donations.dispatcher-message')"
>
{{ historyItem.dispatcherName }}
</b>
<b v-else>
{{ 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>
<b class="region-badge" :aria-describedby="historyItem.region">{{
regions.find((r) => r.id == historyItem.region)?.value || '???'
}}</b>
</td>
<td style="min-width: 200px" class="time">
<span v-if="historyItem.timestampTo" class="text--offline">
<b>{{ $d(historyItem.timestampFrom) }}</b>
{{ timestampToString(historyItem.timestampFrom) }}
- {{ timestampToString(historyItem.timestampTo) }} ({{
calculateDuration(historyItem.currentDuration)
}})
</span>
<span class="dispatcher-online" v-else>
<b class="text--online">
<router-link :to="`/scenery?station=${historyItem.stationName}`">{{
$t('journal.online-since')
}}</router-link>
{{ timestampToString(historyItem.timestampFrom) }}
</b>
({{ calculateDuration(historyItem.currentDuration) }})
</span>
</td>
</tr>
</transition-group>
</tbody>
</table>
<ul v-else class="journal-list">
<transition-group name="list-anim">
<JournalDispatcherEntry
v-for="entry in dispatcherHistory"
:key="entry.id"
:entry="entry"
:onToggleShowExtraInfo="toggleExtraInfo"
:showExtraInfo="extraInfoIndexes.includes(entry.id)"
/>
</transition-group>
<AddDataButton
:list="dispatcherHistory"
@@ -106,7 +32,7 @@
:scrollNoMoreData="scrollNoMoreData"
@addHistoryData="addHistoryData"
/>
</div>
</ul>
<div class="journal_warning" v-if="scrollNoMoreData">
{{ $t('journal.no-further-data') }}
@@ -121,20 +47,15 @@
<script lang="ts">
import { defineComponent, PropType } from 'vue';
import { regions } from '../../../data/options.json';
import { useMainStore } from '../../../store/mainStore';
import { API } from '../../../typings/api';
import { Status } from '../../../typings/common';
import Loading from '../../Global/Loading.vue';
import AddDataButton from '../../Global/AddDataButton.vue';
import dateMixin from '../../../mixins/dateMixin';
import styleMixin from '../../../mixins/styleMixin';
import { useApiStore } from '../../../store/apiStore';
import JournalDispatcherEntry from './JournalDispatcherEntry.vue';
export default defineComponent({
components: { Loading, AddDataButton },
mixins: [dateMixin, styleMixin],
components: { Loading, AddDataButton, JournalDispatcherEntry },
props: {
dispatcherHistory: {
@@ -159,99 +80,30 @@ export default defineComponent({
return {
Status,
store: useMainStore(),
apiStore: useApiStore(),
regions
extraInfoIndexes: [] as number[]
};
},
computed: {
computedDispatcherHistory() {
return this.dispatcherHistory.reduce(
(acc, historyItem, i) => {
if (this.isAnotherDay(i - 1, i))
acc.push(new Date(historyItem.timestampFrom).toLocaleDateString('pl-PL'));
acc.push(historyItem);
return acc;
},
[] as (API.DispatcherHistory.Data | string)[]
);
}
},
methods: {
navigateToScenery(name: string, isOnline: boolean) {
if (!isOnline) return;
toggleExtraInfo(id: number) {
const existingIdx = this.extraInfoIndexes.indexOf(id);
this.$router.push(`/scenery?station=${name.trim().replace(/ /g, '_')}`);
},
isAnotherDay(prevIndex: number, currIndex: number) {
if (currIndex == 0) return true;
return (
new Date(this.dispatcherHistory[prevIndex].timestampFrom).getDate() !=
new Date(this.dispatcherHistory[currIndex].timestampFrom).getDate()
);
if (existingIdx != -1) this.extraInfoIndexes.splice(existingIdx, 1);
else this.extraInfoIndexes.push(id);
}
}
});
</script>
<style lang="scss" scoped>
@import '../../../styles/animations.scss';
@import '../../../styles/responsive.scss';
@import '../../../styles/badge.scss';
@import '../../../styles/variables.scss';
@import '../../../styles/JournalSection.scss';
table.dispatchers-table {
--_bg-table: #111;
--_bg-head: #101010;
--_bg-row: #2f2f2f;
width: 100%;
border-collapse: collapse;
position: relative;
text-align: center;
margin-bottom: 1em;
thead {
position: sticky;
top: 0;
background-color: var(--_bg-head);
}
th {
padding: 0.5em;
}
tr {
background-color: var(--_bg-row);
border-bottom: 2px solid black;
&:last-child {
border: none;
}
}
td {
padding: 0.75em;
.level-badge {
margin: 0 auto;
}
}
}
.text {
&--online {
color: springgreen;
}
&--offline {
color: #ddd;
}
.journal-list {
display: flex;
flex-direction: column;
gap: 0.5em;
text-align: left;
}
</style>
@@ -9,7 +9,11 @@
</span>
<span>
<strong class="text--primary">
<strong
data-tooltip-type="BaseTooltip"
:data-tooltip-content="getCategoryExplanation(timetable.trainCategoryCode)"
class="text--primary tooltip-help"
>
{{ timetable.trainCategoryCode }}
</strong>
<strong>&nbsp;{{ timetable.trainNo }}</strong>
@@ -26,7 +30,8 @@
<strong
v-if="apiStore.donatorsData.includes(timetable.driverName)"
class="text--donator"
:title="$t('donations.driver-message')"
data-tooltip-type="DonatorTooltip"
:data-tooltip-content="$t('donations.driver-message')"
>
{{ timetable.driverName }}
</strong>
@@ -82,9 +87,10 @@ import dateMixin from '../../../mixins/dateMixin';
import modalTrainMixin from '../../../mixins/modalTrainMixin';
import styleMixin from '../../../mixins/styleMixin';
import { useApiStore } from '../../../store/apiStore';
import trainCategoryMixin from '../../../mixins/trainCategoryMixin';
export default defineComponent({
mixins: [dateMixin, modalTrainMixin, styleMixin],
mixins: [dateMixin, modalTrainMixin, styleMixin, trainCategoryMixin],
data() {
return {
@@ -1,101 +0,0 @@
<template>
<ul class="journal-list">
<transition-group name="list-anim">
<li
v-for="{ timetable, showExtraInfo } in computedTimetableHistory"
class="journal_item"
:key="timetable.id"
@click="showExtraInfo.value = !showExtraInfo.value"
>
<div class="journal_item-info">
<!-- General -->
<TimetableGeneral :timetable="timetable" />
<!-- Route -->
<span class="item-route">
<b>{{ timetable.route.replace('|', ' - ') }}</b>
</span>
<hr />
<!-- Stops -->
<TimetableStops :timetable="timetable" :showExtraInfo="showExtraInfo.value" />
<!-- Status -->
<TimetableStatus :timetable="timetable" />
<button class="btn--action btn--show">
{{ $t('journal.stock-info') }}
<img
:src="`/images/icon-arrow-${showExtraInfo.value ? 'asc' : 'desc'}.svg`"
alt="Arrow icon"
/>
</button>
<!-- Extra -->
<TimetableExtra :timetable="timetable" :showExtraInfo="showExtraInfo.value" />
</div>
</li>
</transition-group>
</ul>
</template>
<script lang="ts">
import { PropType, defineComponent, ref } from 'vue';
import TimetableGeneral from './TimetableGeneral.vue';
import TimetableStops from './TimetableStops.vue';
import TimetableStatus from './TimetableStatus.vue';
import TimetableExtra from './TimetableExtra.vue';
import { API } from '../../../typings/api';
export default defineComponent({
components: { TimetableGeneral, TimetableStops, TimetableStatus, TimetableExtra },
props: {
timetableHistory: {
type: Array as PropType<API.TimetableHistory.Response>,
required: true
}
},
computed: {
computedTimetableHistory() {
return this.timetableHistory.map((timetable) => ({
timetable,
showExtraInfo: ref(false)
}));
}
}
});
</script>
<style lang="scss" scoped>
@import '../../../styles/variables';
@import '../../../styles/responsive';
@import '../../../styles/JournalSection';
.btn--show {
display: flex;
font-weight: bold;
padding: 0.2em 0.45em;
img {
height: 1.3em;
}
}
hr {
margin: 0.25em 0;
}
@include smallScreen {
.journal_item-info {
text-align: center;
}
.item-route {
display: flex;
justify-content: center;
}
.btn--show {
margin: 1em auto 0 auto;
}
}
</style>
@@ -7,7 +7,7 @@
{{ $t('scenery.history-list-empty') }}
</div>
<div v-else class="history-list">
<div v-else class="journal-list">
<div v-for="historyItem in historyList" :key="historyItem.id">
<span>
<span class="text--grayed" style="margin-right: 10px">
@@ -165,14 +165,14 @@ export default defineComponent({
overflow: auto;
}
.history-list {
.journal-list {
display: flex;
flex-direction: column;
gap: 0.5em;
text-align: left;
}
.history-list > div {
.journal-list > div {
display: flex;
justify-content: space-between;
align-items: center;
@@ -195,7 +195,7 @@ export default defineComponent({
}
@include smallScreen {
.history-list > div {
.journal-list > div {
flex-direction: column;
justify-content: center;
text-align: center;
@@ -73,15 +73,20 @@
>
<span class="timetable-general">
<span class="general-info">
<span class="info-number">
<strong>{{ row.train.timetableData!.category }}</strong>
{{ row.train.trainNo }}
<span>
<b
data-tooltip-type="BaseTooltip"
:data-tooltip-content="getCategoryExplanation(row.train.timetableData!.category)"
class="text--primary tooltip-help"
>
{{ row.train.timetableData!.category }}
</b>
<b>&nbsp;{{ row.train.trainNo }}</b>
<span v-if="row.checkpointStop.comments" :title="row.checkpointStop.comments">
<img src="/images/icon-warning.svg" />
</span>
</span>
&nbsp;|&nbsp;
&nbsp;&bull;&nbsp;
<span>
{{ row.train.driverName }}
</span>
@@ -180,13 +185,14 @@ import { useApiStore } from '../../store/apiStore';
import { ActiveScenery, Station } from '../../typings/common';
import { SceneryTimetableRow } from './typings';
import { getTrainStopStatus, stopStatusPriority } from './utils';
import trainCategoryMixin from '../../mixins/trainCategoryMixin';
export default defineComponent({
name: 'SceneryTimetable',
components: { Loading, ScheduledTrainStatus },
mixins: [dateMixin, routerMixin, modalTrainMixin],
mixins: [dateMixin, routerMixin, modalTrainMixin, trainCategoryMixin],
props: {
station: {
@@ -243,7 +249,7 @@ export default defineComponent({
return this.onlineScenery.scheduledTrains
.filter(
(ct) =>
ct.timetablePathElement.stationName == sceneryName &&
// ct.timetablePathElement.stationName == sceneryName &&
ct.train.region == this.mainStore.region.id &&
this.chosenCheckpoint &&
ct.checkpointStop.stopNameRAW.toLowerCase() == this.chosenCheckpoint.toLowerCase()
@@ -19,7 +19,7 @@
{{ $t('scenery.history-list-empty') }}
</div>
<div v-else class="history-list">
<div v-else class="journal-list">
<div v-for="timetableHistory in historyList" :key="timetableHistory.id">
<span>
<div>
@@ -219,14 +219,14 @@ export default defineComponent({
}
}
.history-list {
.journal-list {
display: flex;
flex-direction: column;
gap: 0.5em;
text-align: left;
}
.history-list > div {
.journal-list > div {
display: flex;
justify-content: space-between;
align-items: center;
@@ -235,7 +235,7 @@ export default defineComponent({
line-height: 1.5em;
}
.history-list > div > button > img {
.journal-list > div > button > img {
width: 2em;
transform: rotate(180deg);
}
+1
View File
@@ -23,6 +23,7 @@ export default defineComponent({
justify-content: center;
align-items: center;
gap: 0.5em;
white-space: pre-line;
padding: 0.25em 0.5em;
border-radius: 0.25em;
@@ -50,7 +50,7 @@ export default defineComponent({
},
watch: {
'tooltipStore.type'(prev, val) {
vehicleName(prev, val) {
if (prev != val) this.imageState = 'loading';
}
},
@@ -81,18 +81,6 @@ export default defineComponent({
(c) => c.id == this.tooltipStore.content.split(':')[1]
);
}
// vehicleProps() {
// const vehicleDataArray = this.apiStore.vehiclesData?.vehicleList.find(
// ([name]) => name === this.vehicleName
// );
// if (!vehicleDataArray) return null;
// return (
// this.apiStore.vehiclesData!.vehicleProps.find((v) => v.type == vehicleDataArray[1]) ?? null
// );
// }
}
});
</script>
+46 -42
View File
@@ -8,15 +8,9 @@
<span
v-if="stop.position != 'begin'"
class="date arrival"
:data-status="
stop.arrivalDelay > 0 && stop.status != 'unconfirmed'
? 'delayed'
: stop.arrivalDelay < 0 && stop.status != 'unconfirmed'
? 'preponed'
: stop.arrivalDelay == 0 && stop.status == 'confirmed'
? 'on-time'
: ''
"
:data-status-delayed="stop.arrivalDelay > 0"
:data-status-preponed="stop.arrivalDelay < 0"
:data-status="stop.status"
>
p.
<span v-if="stop.arrivalDelay != 0 && stop.status != 'unconfirmed'">
@@ -31,10 +25,7 @@
</span>
<span
v-if="
stop.duration ||
(stop.status == 'stopped' && stop.position != 'begin' && stop.departureDelay > 0)
"
v-if="stop.duration"
class="date stop"
:data-stop-types="stop.type.replace(', ', '-')"
:data-stop-status="stop.departureDelay > 0 && !stop.duration ? 'delayed' : ''"
@@ -53,20 +44,12 @@
(stop.duration != 0 || stop.status == 'stopped' || stop.departureDelay != stop.arrivalDelay)
"
class="date departure"
:data-status="
stop.departureDelay > 0 && stop.status == 'confirmed'
? 'delayed'
: stop.departureDelay < 0 && stop.status == 'confirmed'
? 'preponed'
: stop.departureDelay == 0 && stop.status == 'confirmed'
? 'on-time'
: ''
"
:data-status-delayed="stop.departureDelay > 0"
:data-status-preponed="stop.departureDelay < 0"
:data-status-confirmed="stop.status == 'confirmed'"
>
o.
<span
v-if="stop.departureDelay != 0 && (stop.status == 'confirmed' || stop.status == 'stopped')"
>
<span v-if="stop.departureDelay != 0 && stop.status == 'confirmed'">
<s>{{ timestampToString(stop.departureScheduled) }}</s>
{{ timestampToString(stop.departureReal) }}
@@ -105,6 +88,10 @@ $stopExchangeClr: #db8e29;
$stopDefaultClr: #252525;
$stopNameClr: #303030;
s {
color: #ccc;
}
.stop-label {
display: flex;
flex-wrap: wrap;
@@ -157,27 +144,44 @@ $stopNameClr: #303030;
color: $delayedClr;
}
}
}
.arrival,
.departure {
&[data-status='delayed'] {
s {
color: #ccc;
}
span {
color: $delayedClr;
}
.stop .arrival {
&[data-status='confirmed'][data-status-delayed='true'] {
span {
color: $delayedClr;
}
}
&[data-status='preponed'] {
s {
color: #ccc;
}
&[data-status='confirmed'][data-status-preponed='true'] {
span {
color: $preponedClr;
}
}
span {
color: $preponedClr;
}
&[data-status='stopped'][data-status-preponed='true'] {
span {
color: $preponedClr;
}
}
&[data-status='stopped'][data-status-delayed='true'] {
span {
color: $delayedClr;
}
}
}
.stop .departure[data-status-confirmed='true'] {
&[data-status-delayed='true'] {
span {
color: $delayedClr;
}
}
&[data-status-preponed='true'] {
span {
color: $preponedClr;
}
}
}
+11 -7
View File
@@ -28,12 +28,15 @@
</span>
</span>
<strong>
<span v-if="train.timetableData" class="text--primary"
>{{ train.timetableData.category }}&nbsp;</span
>
<span class="train-number">{{ train.trainNo }}</span>
</strong>
<b
v-if="train.timetableData"
data-tooltip-type="BaseTooltip"
:data-tooltip-content="getCategoryExplanation(train.timetableData.category)"
class="text--primary tooltip-help"
>
{{ train.timetableData.category }}
</b>
<b class="train-number">{{ train.trainNo }}</b>
<span>&bull;</span>
<b
class="level-badge driver"
@@ -181,9 +184,10 @@ import { useApiStore } from '../../store/apiStore';
import StockList from '../Global/StockList.vue';
import modalTrainMixin from '../../mixins/modalTrainMixin';
import { Train } from '../../typings/common';
import trainCategoryMixin from '../../mixins/trainCategoryMixin';
export default defineComponent({
mixins: [trainInfoMixin, styleMixin, modalTrainMixin],
mixins: [trainInfoMixin, styleMixin, modalTrainMixin, trainCategoryMixin],
components: { ProgressBar, StockList },
props: {
+41 -1
View File
@@ -48,7 +48,47 @@
"footer": {
"discord": "Stacjownik Discord server"
},
"categories": {
"EI": "domestic express",
"EC": "international express",
"EN": "domestic night express",
"MP": "intervoivodeship bullet",
"MO": "intervoivodeship regio",
"MM": "international bullet",
"MH": "intervoivodeship night bullet",
"RP": "voivodeship bullet",
"RM": "international voivodeship regio",
"RO": "voivodeship regio",
"RA": "voivodeship regio (urban)",
"PW": "empty passenger",
"PX": "empty passenger test drive",
"TC": "international freight (intermodal)",
"TG": "international freight (organized cargo)",
"TR": "international freight (unorganized cargo)",
"TD": "domestic freight (intermodal)",
"TM": "domestic freight (organized cargo)",
"TN": "domestic freight (unorganized cargo)",
"TK": "freight (for stations & sidings)",
"TS": "empty freight test drive",
"TH": "locomotive rolling stock (over 3 vehicles)",
"LT": "freight locomotive only",
"LP": "passenger locomotive only",
"LS": "shunting locomotive only",
"LZ": "shunting locomotive only",
"ZN": "inspection / diagnostic type",
"ZU": "other maintenance type",
"E": "electric loco",
"J": "EMU",
"S": "diesel loco",
"M": "DMU"
},
"vehicle-preview": {
"loading": "Loading preview...",
"error": "Oops! The vehicle preview seems to be missing! :/"
@@ -123,7 +163,7 @@
"search-train": "Train no.",
"search-driver": "Driver name",
"search-dispatcher": "Dispatcher name",
"search-station": "Scenery name",
"search-station": "Scenery name / #",
"search-author": "Timetable author name",
"search-issuedFrom": "Issuing scenery name",
"search-via": "Via scenery name",
+42 -1
View File
@@ -45,6 +45,47 @@
"footer": {
"discord": "Serwer Discord Stacjownika"
},
"categories": {
"EI": "ekspres krajowy",
"EC": "ekspres międzynarodowy",
"EN": "ekspres krajowy nocny",
"MP": "międzywojewódzki pospieszny",
"MO": "międzywojewódzki osobowy",
"MM": "międzynarodowy pospieszny",
"MH": "międzywojewódzki pospieszny (nocny)",
"RP": "wojewódzki pospieszny",
"RO": "wojewódzki osobowy",
"RM": "wojewódzki osobowy międzynarodowy",
"RA": "wojewódzki osobowy algomeracyjny",
"PW": "pasażerski próżny - służbowy",
"PX": "pasażerski próżny próbny",
"TC": "towarowy międzynarodowy intermodalny",
"TG": "towarowy międzynarodowy masowy",
"TR": "towarowy międzynarodowy niemasowy",
"TD": "towarowy krajowy intermodalny",
"TM": "towarowy krajowy masowy",
"TN": "towarowy krajowy niemasowy",
"TK": "towarowy zdawczy",
"TS": "towarowy próżny próbny",
"TH": "skład lokomotyw (powyżej 3 pojazdów)",
"LT": "lokomotywa towarowa luzem",
"LP": "lokomotywa pasażerska luzem",
"LS": "lokomotywa manewrowa luzem",
"LZ": "lokomotywa dla poc. utrzymaniowo-naprawczych",
"ZN": "inspekcyjny / diagnostyczny",
"ZU": "inny utrzymaniowy",
"E": "elektrowóz",
"J": "EZT",
"S": "spalinowóz",
"M": "SZT"
},
"vehicle-preview": {
"loading": "Ładowanie podglądu...",
"error": "Ups! Nie znaleziono podglądu pojazdu! :/"
@@ -119,7 +160,7 @@
"search-train": "Nr pociągu / #",
"search-driver": "Nick maszynisty",
"search-dispatcher": "Nick dyżurnego",
"search-station": "Nazwa scenerii",
"search-station": "Nazwa scenerii / #",
"search-author": "Nick autora rozkładu jazdy",
"search-issuedFrom": "Sceneria początkowa",
"search-via": "Przez scenerię",
+12
View File
@@ -0,0 +1,12 @@
import { defineComponent } from 'vue';
export default defineComponent({
methods: {
getCategoryExplanation(categoryCode: string) {
const categoryKey = categoryCode.slice(0, 2);
const vehicleTypeKey = categoryCode.slice(-1);
return `${this.$t('categories.' + categoryKey)}\n(${this.$t('categories.' + vehicleTypeKey)})`;
}
}
});
+12 -9
View File
@@ -108,16 +108,19 @@ export default defineComponent({
},
currentDelay(stops: TrainStop[]) {
const delay =
stops.find(
(stop, i) =>
(i == 0 && !stop.confirmed) || (i > 0 && stops[i - 1].confirmed && !stop.confirmed)
)?.departureDelay || 0;
const lastConfirmedStop = stops.find(
(stop, i) =>
(i == 0 && !stop.confirmed) ||
(i > 0 && stops[i - 1].confirmed && !stop.confirmed) ||
(stops[i + 1] == undefined && stop.confirmed)
);
if (delay > 0)
return `<span style='color: salmon'>${this.$t('trains.delayed')} ${delay} min</span>`;
else if (delay < 0)
return `<span style='color: lightgreen'>${this.$t('trains.preponed')} ${delay} min</span>`;
const lastDelay = lastConfirmedStop?.departureDelay ?? lastConfirmedStop?.arrivalDelay ?? 0;
if (lastDelay > 0)
return `<span style='color: salmon'>${this.$t('trains.delayed')} ${lastDelay} min</span>`;
else if (lastDelay < 0)
return `<span style='color: lightgreen'>${this.$t('trains.preponed')} ${lastDelay} min</span>`;
else return this.$t('trains.on-time');
},
+22 -17
View File
@@ -18,7 +18,7 @@ export const useApiStore = defineStore('apiStore', {
donatorsData: [] as API.Donators.Response,
sceneryData: [] as StationJSONData[],
lastFetchData: new Date(),
nextUpdateTime: 0,
client: undefined as AxiosInstance | undefined,
@@ -51,27 +51,33 @@ export const useApiStore = defineStore('apiStore', {
// Static data
this.fetchDonatorsData();
this.fetchStationsGeneralInfo();
// Ponowne pobieranie danych po ServiceWorkerze
setTimeout(() => {
this.fetchStationsGeneralInfo();
}, Math.floor(Math.random() * 500) + 1000);
this.fetchVehiclesInfo();
window.requestAnimationFrame(this.updateTick);
},
updateTick(t: number) {
if (this.dataStatuses.connection == Status.Data.Offline) return;
if (t >= this.nextUpdateTime) {
this.fetchActiveData();
this.nextUpdateTime = t + 20000;
}
window.requestAnimationFrame(this.updateTick);
},
async fetchActiveData() {
if (import.meta.env.VITE_API_ACTIVE_DATA_MODE == 'mocking') {
import('../../tests/data/getActiveData.json').then((data) => {
console.warn('activeData: mocking mode');
this.activeData = data.default as API.ActiveData.Response;
this.lastFetchData = new Date();
// if (import.meta.env.VITE_API_ACTIVE_DATA_MODE == 'mocking') {
// import('../../tests/data/getActiveData.json').then((data) => {
// console.warn('activeData: mocking mode');
// this.activeData = data.default as API.ActiveData.Response;
this.dataStatuses.connection = Status.Data.Loaded;
});
// this.dataStatuses.connection = Status.Data.Loaded;
// });
return;
}
// return;
// }
if (!this.activeData) this.dataStatuses.connection = Status.Data.Loading;
@@ -79,7 +85,6 @@ export const useApiStore = defineStore('apiStore', {
const response = await this.client!.get<API.ActiveData.Response>('api/getActiveData');
this.activeData = response.data;
this.lastFetchData = new Date();
this.dataStatuses.connection = Status.Data.Loaded;
} catch (error) {
this.dataStatuses.connection = Status.Data.Error;
+6 -2
View File
@@ -104,7 +104,7 @@ export const useMainStore = defineStore('mainStore', {
arrivalRouteExt: arrival,
departureRouteExt: departure,
stationName: station.split(' ').slice(0, -1).join(' '),
stationHash: station.split(' ').slice(-1).join(' ')
stationHash: station.split(' ').slice(-1).join(' ').replace('.sc', '')
};
})
}
@@ -281,7 +281,11 @@ export const useMainStore = defineStore('mainStore', {
if (!scheduledTrains) return;
scheduledTrains.forEach(({ train, checkpointStop, timetablePathElement, ...v }) => {
if (scenery.name != timetablePathElement.stationName) return;
if (
scenery.name != timetablePathElement.stationName &&
scenery.hash != timetablePathElement.stationHash
)
return;
scenery.scheduledTrains.push({ train, checkpointStop, timetablePathElement, ...v });
+4
View File
@@ -308,6 +308,10 @@ a.a-button {
z-index: 100;
}
.tooltip-help {
cursor: help;
}
@include smallScreen {
::-webkit-scrollbar {
width: 0.5em;
+1 -1
View File
@@ -24,7 +24,7 @@ export namespace API {
export type Response = Data[];
export interface Data {
id: string;
id: number;
currentDuration: number;
dispatcherId: number;
dispatcherName: string;
+4 -1
View File
@@ -267,7 +267,10 @@ export default defineComponent({
const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined;
if (dispatcher) queries.push(`dispatcherName=${dispatcher}`);
if (station) queries.push(`stationName=${station}`);
if (station.startsWith("#")) queries.push(`stationHash=${station.slice(1)}`);
else if (station.length > 0) queries.push(`stationName=${station}`);
if (timestampFrom && timestampTo)
queries.push(`timestampFrom=${timestampFrom}`, `timestampTo=${timestampTo}`);
File diff suppressed because one or more lines are too long
+518 -876
View File
File diff suppressed because it is too large Load Diff