Compare commits

..

41 Commits

Author SHA1 Message Date
Spythere 430a05ab38 Merge pull request #145 from Spythere/development
v1.30.7
2025-11-28 01:14:13 +01:00
Spythere f335ca8fc2 chore: updated welcome card english flag image 2025-11-28 00:58:19 +01:00
Spythere 15e599fe3c chore: moved language button to sceneries table top bar 2025-11-27 21:33:19 +01:00
Spythere bd25914ed4 fix: missing typings for hidden property 2025-11-27 21:30:32 +01:00
Spythere 01ea259381 fix: added hiding project filter propositions for hidden sceneries 2025-11-27 21:10:34 +01:00
Spythere aea26fa538 chore: groupped station filters inputs to the top of the card; added project filter 2025-11-27 21:04:55 +01:00
Spythere 28d78cd2bc chore: improved scenery timetables history router link style 2025-11-22 23:00:11 +01:00
Spythere a021deae96 refactor: scenery timetables history date parsing 2025-11-22 22:54:43 +01:00
Spythere 8840576796 chore: changed alignment and order of history mode buttons 2025-11-22 22:05:20 +01:00
Spythere 5018e21736 chore: updated locales 2025-11-22 22:04:57 +01:00
Spythere a7fa1dfb6d chore: added filter for fetching all scenery timetables 2025-11-22 22:04:37 +01:00
Spythere a3558c0b30 bump: v1.30.7 2025-11-22 01:30:48 +01:00
Spythere ee159fd582 fix: vehicle thumbnail overflowing text 2025-11-22 01:30:29 +01:00
Spythere 35c9fb7ef1 Merge pull request #142 from Spythere/development
hotfix: loading indicator for scenery history tabs
2025-10-25 19:40:33 +02:00
Spythere e24097c240 hotfix: loading indicator for scenery history tabs 2025-10-25 19:37:02 +02:00
Spythere 01cbebd019 Merge pull request #141 from Spythere/development
hotfix: preload & prefetch optimization
2025-10-07 22:36:59 +02:00
Spythere 3a5ef7e025 hotfix: preload & prefetch optimization 2025-10-07 18:37:27 +02:00
Spythere c78a5b4d67 Merge pull request #140 from Spythere/development
hotfix: checkpoints filtering for unknown sceneries
2025-09-17 20:06:19 +02:00
Spythere 023de9f7b8 fix: view caching & icons flicker 2025-09-16 22:32:04 +02:00
Spythere 1024e44cc0 hotfix: checkpoints filtering for unknown sceneries 2025-09-16 20:45:27 +02:00
Spythere 580d404d4a Merge pull request #139 from Spythere/development
v1.30.6
2025-09-15 14:23:43 +02:00
Spythere 6d1ef26ac1 chore: added a display of the timetable max speed in the active train info & tooltip 2025-09-14 15:10:48 +02:00
Spythere bf9799e0c3 bump: v1.30.6 2025-09-14 14:52:04 +02:00
Spythere 1d13e31d79 chore: restored station filtering by non-electric double track routes 2025-09-14 14:51:44 +02:00
Spythere 16f272bd7d chore: added SCS+SPK station filter 2025-09-14 14:43:20 +02:00
Spythere 23ca33264c chore: PWA caching 2025-09-14 14:38:56 +02:00
Spythere 324ca3de4d chore: config adjustments 2025-09-14 14:38:42 +02:00
Spythere e0548e593c fix: stats header font size 2025-09-14 14:38:17 +02:00
Spythere 2727350837 Merge pull request #138 from Spythere/development
v1.30.5
2025-09-07 23:57:19 +02:00
Spythere 6c3af0a8d3 chore: updated packages versions 2025-09-06 14:04:18 +02:00
Spythere e784202a36 chore: removed displaying exit track speeds if they are the same as the base ones 2025-09-06 13:42:00 +02:00
Spythere c24f691693 fix: track count depiction in train schedule for unknown sceneries 2025-09-06 13:34:03 +02:00
Spythere 3aeabd63c9 bump: v1.30.5 2025-09-06 02:18:14 +02:00
Spythere 4c79376318 chore: restored SCS+SPK control type support 2025-09-06 02:17:28 +02:00
Spythere bc1c446c37 chore: added missing checkpoints data fallback for unknown sceneries 2025-09-06 01:57:59 +02:00
Spythere fba335d0c7 fix: scenery without general info shown always as offline; wrong route info for unknown sceneries 2025-09-05 20:03:17 +02:00
Spythere b4e536da40 Merge pull request #137 from Spythere/development
v1.30.4 hotfixes
2025-07-27 14:42:53 +02:00
Spythere 8cde8e6323 hotfix: offline train badge visibility 2025-07-27 14:39:44 +02:00
Spythere d7a9e93978 hotfix: added missing input field keybind prevention 2025-07-27 14:37:50 +02:00
Spythere 69aa62e77f Merge pull request #136 from Spythere/development
hotfix: translations
2025-07-20 14:09:44 +02:00
Spythere 4b842627fb hotfix: translations 2025-07-20 14:09:07 +02:00
36 changed files with 1953 additions and 8575 deletions
+60 -4
View File
@@ -22,10 +22,64 @@
<link rel="icon" href="favicon.ico" />
<link rel="stylesheet" href="fa/css/fontawesome.css" />
<link rel="stylesheet" href="fa/css/brands.css" />
<link rel="stylesheet" href="fa/css/regular.css" />
<link rel="stylesheet" href="fa/css/solid.css" />
<link rel="stylesheet" href="/fa/css/fontawesome.css" />
<link rel="stylesheet" href="/fa/css/brands.css" />
<link rel="stylesheet" href="/fa/css/regular.css" />
<link rel="stylesheet" href="/fa/css/solid.css" />
<!-- Preloads -->
<link rel="preload" href="fonts/Quicksand-Bold.woff2" as="font" type="font/woff2" crossorigin />
<link
rel="preload"
href="/fonts/Quicksand-Light.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="/fonts/Quicksand-Medium.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="/fonts/Quicksand-Regular.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="/fonts/Quicksand-SemiBold.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link rel="preload" as="image" href="/images/icon-pl.svg" />
<link rel="preload" as="image" href="/images/stacjownik-header-logo.svg" />
<link rel="preload" as="image" href="/images/icon-dispatcher.svg" />
<link rel="preload" as="image" href="/images/icon-train.svg" />
<link rel="preload" as="image" href="/images/icon-arrow-asc.svg" />
<link rel="preload" as="image" href="/images/icon-arrow-desc.svg" />
<link rel="preload" as="image" href="/images/icon-filter2.svg" />
<link rel="preload" as="image" href="/images/icon-stats.svg" />
<link rel="preload" as="image" href="/images/icon-gnr.svg" />
<link rel="preload" as="image" href="/images/icon-pojazdownik.svg" />
<link rel="preload" as="image" href="/images/icon-diamond.svg" />
<link rel="preload" as="image" href="/images/icon-user.svg" />
<link rel="preload" as="image" href="/images/icon-like.svg" />
<link rel="preload" as="image" href="/images/icon-spawn.svg" />
<link rel="preload" as="image" href="/images/icon-timetableAll.svg" />
<link rel="preload" as="image" href="/images/icon-timetableUnconfirmed.svg" />
<link rel="preload" as="image" href="/images/icon-timetableConfirmed.svg" />
<link rel="preload" as="image" href="/images/icon-discord.png" />
<!-- Static OpenGraph meta -->
<meta name="description" content="Pomocnik maszynisty i dyżurnego symulatora Train Driver 2" />
@@ -36,10 +90,12 @@
property="og:description"
content="Pomocnik maszynisty i dyżurnego symulatora Train Driver 2"
/>
<meta
property="og:image"
content="https://raw.githubusercontent.com/Spythere/api/main/thumbnails/stacjownik.jpg"
/>
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:site_name" content="Stacjownik" />
-6962
View File
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "stacjownik",
"version": "1.30.4",
"version": "1.30.7",
"private": true,
"type": "module",
"scripts": {
@@ -17,7 +17,7 @@
},
"dependencies": {
"core-js": "^3.42.0",
"dotenv": "^16.5.0",
"dotenv": "^17.2.2",
"pinia": "^3.0.2",
"sass": "^1.87.0",
"showdown": "^2.1.0",
@@ -26,17 +26,17 @@
"vue-router": "^4.4.0"
},
"devDependencies": {
"@types/node": "^22.15.15",
"@types/node": "^24.3.1",
"@types/showdown": "^2.0.6",
"@vite-pwa/assets-generator": "^1.0.0",
"@vitejs/plugin-vue": "^5.1.0",
"@vue/tsconfig": "^0.7.0",
"@vitejs/plugin-vue": "^6.0.1",
"@vue/tsconfig": "^0.8.1",
"axios": "^1.9.0",
"prettier": "^3.3.3",
"typescript": "^5.5.4",
"vite": "^6.3.5",
"vite": "^7.1.4",
"vite-plugin-pwa": "^1.0.0",
"vue-tsc": "^2.0.28"
"vue-tsc": "^3.0.6"
},
"browserslist": [
"> 1%",
+7
View File
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-gb" viewBox="0 0 640 480">
<path fill="#012169" d="M0 0h640v480H0z"/>
<path fill="#FFF" d="m75 0 244 181L562 0h78v62L400 241l240 178v61h-80L320 301 81 480H0v-60l239-178L0 64V0z"/>
<path fill="#C8102E" d="m424 281 216 159v40L369 281zm-184 20 6 35L54 480H0zM640 0v3L391 191l2-44L590 0zM0 0l239 176h-60L0 42z"/>
<path fill="#FFF" d="M241 0v480h160V0zM0 160v160h640V160z"/>
<path fill="#C8102E" d="M0 193v96h640v-96zM273 0v480h96V0z"/>
</svg>

After

Width:  |  Height:  |  Size: 504 B

+6 -4
View File
@@ -1,4 +1,6 @@
<svg width="39" height="23" viewBox="0 0 39 23" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="39" height="23" fill="#FF0F0F"/>
<rect width="39" height="11.5" fill="white"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-pl" viewBox="0 0 640 480">
<g fill-rule="evenodd">
<path fill="#fff" d="M640 480H0V0h640z"/>
<path fill="#dc143c" d="M640 480H0V240h640z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 199 B

After

Width:  |  Height:  |  Size: 219 B

+4 -11
View File
@@ -9,11 +9,11 @@
<Tooltip />
<AppHeader :current-lang="store.currentLocale" @change-lang="changeLang" />
<AppHeader />
<main class="app_main">
<router-view v-slot="{ Component }">
<keep-alive exclude="SceneryView">
<keep-alive>
<component :is="Component" :key="$route.name" />
</keep-alive>
</router-view>
@@ -159,18 +159,11 @@ export default defineComponent({
this.apiStore.connectToAPI();
},
changeLang(lang: string) {
this.$i18n.locale = lang;
this.store.currentLocale = lang;
StorageManager.setStringValue('lang', lang);
},
loadLang() {
const storageLang = StorageManager.getStringValue('lang');
if (storageLang) {
this.changeLang(storageLang);
this.store.changeLocale(storageLang);
return;
}
@@ -179,7 +172,7 @@ export default defineComponent({
const naviLanguage = window.navigator.language.toString();
if (!naviLanguage.startsWith('pl')) {
this.changeLang('en');
this.store.changeLocale('en');
return;
}
},
+2 -37
View File
@@ -1,18 +1,6 @@
<template>
<header class="app_header">
<div class="header_container">
<div class="header_icons">
<span class="icons-top">
<img
src="/images/icon-pl.svg"
alt="icon-pl"
@click="changeLang('en')"
v-if="currentLang == 'pl'"
/>
<img src="/images/icon-en.jpg" alt="icon-en" @click="changeLang('pl')" v-else />
</span>
</div>
<div class="header_body">
<StatusIndicator />
@@ -76,27 +64,12 @@ import RegionDropdown from '../Global/RegionDropdown.vue';
export default defineComponent({
components: { StatusIndicator, Clock, RegionDropdown },
emits: ['changeLang'],
props: {
currentLang: {
type: String,
required: true
}
},
setup() {
return {
store: useMainStore()
};
},
methods: {
changeLang(lang: string) {
this.$emit('changeLang', lang);
}
},
computed: {
onlineTrainsCount() {
return this.store.trainList.filter((train) => train.region == this.store.region.id).length;
@@ -141,7 +114,7 @@ export default defineComponent({
border-radius: 0 0 1em 1em;
@include responsive.smallScreen{
@include responsive.smallScreen {
position: relative;
margin-top: 0.5em;
}
@@ -180,20 +153,12 @@ export default defineComponent({
padding: 0.5em;
@include responsive.smallScreen{
@include responsive.smallScreen {
transform: translateX(85%);
}
}
}
// ICONS
.icons-top {
img {
width: 2.5em;
cursor: pointer;
}
}
// COUNTER
.info_counter {
display: flex;
+3 -13
View File
@@ -4,12 +4,12 @@
<h1>{{ $t('welcome.title') }}</h1>
<div class="language-select">
<button :data-active="$i18n.locale == 'pl'" @click="changeLang('pl')">
<button :data-active="$i18n.locale == 'pl'" @click="store.changeLocale('pl')">
<img src="/images/icon-pl.svg" alt="" width="45" />
</button>
<button :data-active="$i18n.locale == 'en'" @click="changeLang('en')">
<img src="/images/icon-en.jpg" alt="" width="45" />
<button :data-active="$i18n.locale == 'en'" @click="store.changeLocale('en')">
<img src="/images/icon-en.svg" alt="" width="45" />
</button>
</div>
@@ -114,12 +114,9 @@
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import Card from '../Global/Card.vue';
import { useMainStore } from '../../store/mainStore';
import StorageManager from '../../managers/storageManager';
const i18n = useI18n();
const store = useMainStore();
const emit = defineEmits(['toggleCard']);
@@ -130,13 +127,6 @@ const props = defineProps({
function toggleCard(state: boolean) {
emit('toggleCard', state);
}
function changeLang(localeName: string) {
i18n.locale.value = localeName;
store.currentLocale = localeName;
StorageManager.setStringValue('lang', localeName);
}
</script>
<style lang="scss" scoped>
+6 -5
View File
@@ -9,7 +9,7 @@
<img
v-for="(thumbnailImage, imageIndex) in images"
:src="`https://stacjownik.spythere.eu/static/thumbnails/${thumbnailImage}.png`"
height="60"
height="70"
loading="lazy"
data-tooltip-type="VehiclePreviewTooltip"
:data-tooltip-content="vehicleString"
@@ -20,7 +20,7 @@
</div>
</template>
<script setup lang="ts">
<script setup lang="ts">
import { computed, PropType, Ref, ref } from 'vue';
const props = defineProps({
@@ -56,16 +56,17 @@ function onImageLoad() {
transition: opacity 100ms ease-in-out;
&[data-load-status='loading'] {
min-height: 60px;
min-height: 70px;
min-width: 200px;
}
}
.stock-text {
max-width: 90%;
text-align: center;
color: #aaa;
font-size: 0.9em;
margin-bottom: 0.25em;
font-size: 0.85em;
margin: 0 auto;
padding: 0.25em 0;
}
@@ -96,6 +96,7 @@ export default defineComponent({
data() {
return {
historyList: [] as API.DispatcherHistory.Response,
lastStationName: '',
dataStatus: Status.Data.Loading,
DataStatus: Status.Data,
apiStore: useApiStore()
@@ -103,10 +104,10 @@ export default defineComponent({
},
async activated() {
// if (this.historyList.length == 0) {
this.historyList.length = 0;
const fetchedHistory = await this.fetchAPIData();
if (fetchedHistory) this.historyList = fetchedHistory;
// }
},
methods: {
@@ -194,7 +195,7 @@ export default defineComponent({
color: springgreen;
}
@include responsive.smallScreen{
@include responsive.smallScreen {
.journal-list > div {
flex-direction: column;
justify-content: center;
@@ -118,6 +118,7 @@ export default defineComponent({
align-items: center;
width: 3em;
height: 3em;
margin: 0.25em;
border: 2px solid #4e4e4e;
@@ -43,8 +43,12 @@
<span :class="{ 'no-catenary': !route.isElectric, internal: route.isInternal }">
{{ route.routeName }}
</span>
<span v-if="route.routeSpeed" class="speed">{{ route.routeSpeed }}</span>
<span v-if="route.routeSpeedExit" class="speed">| {{ route.routeSpeedExit }}</span>
<span v-if="route.routeSpeed" class="speed">
<span>{{ route.routeSpeed }}</span>
<span v-if="route.routeSpeedExit && route.routeSpeedExit != route.routeSpeed">
| {{ route.routeSpeedExit }}
</span>
</span>
<span v-if="route.routeLength" class="length">
{{ (route.routeLength / 1000).toFixed(1) + 'km' }}
</span>
@@ -156,7 +160,7 @@ ul.routes-list {
-moz-user-select: none;
-webkit-user-select: none;
span {
& > span {
padding: 0.2em;
background-color: #007599;
font-weight: bold;
@@ -87,7 +87,8 @@ export default defineComponent({
const stop = train.timetableData?.followingStops.find(
(stop) =>
stop.stopNameRAW.toLowerCase() == name.toLowerCase() ||
this.station?.generalInfo?.checkpoints.includes(stop.stopNameRAW)
this.station?.generalInfo?.checkpoints.includes(stop.stopNameRAW) ||
this.onlineScenery?.missingCheckpoints.includes(stop.stopNameRAW)
);
const sceneryName =
+35 -13
View File
@@ -54,6 +54,18 @@
>
</template>
</div>
<div class="timetable-checkpoints" v-else-if="onlineScenery">
<template v-for="(ch, i) in onlineScenery.missingCheckpoints" :key="i">
<template v-if="i > 0">&bull;</template>
<router-link
class="checkpoint-item"
:class="{ current: chosenCheckpoint === ch }"
:to="`/scenery?station=${onlineScenery.name}&checkpoint=${ch}`"
>{{ ch }}</router-link
>
</template>
</div>
</div>
<div class="timetable-list">
@@ -287,6 +299,7 @@ export default defineComponent({
const chosenCheckpoint = ref(
props.station?.generalInfo?.checkpoints[0] ??
props.onlineScenery?.missingCheckpoints[0] ??
props.station?.name ??
route.query['station']?.toString() ??
''
@@ -365,21 +378,30 @@ export default defineComponent({
methods: {
loadSelectedOption() {
if (!this.station) return;
if (!this.station.generalInfo) {
this.chosenCheckpoint = this.station.name;
return;
}
const queryCheckpoint = this.$route.query['checkpoint']?.toString();
this.chosenCheckpoint =
this.station.generalInfo.checkpoints.find(
(ch) => ch.toLocaleLowerCase() === queryCheckpoint?.toLocaleLowerCase()
) ??
this.station.generalInfo.checkpoints[0] ??
this.station.name;
let checkpointsListRef: string[] | null = null;
let sceneryName = '';
if (this.station && this.station.generalInfo) {
checkpointsListRef = this.station.generalInfo.checkpoints;
sceneryName = this.station.name;
} else if (this.onlineScenery) {
checkpointsListRef = this.onlineScenery.missingCheckpoints;
sceneryName = this.onlineScenery.name;
} else if (this.station) {
this.chosenCheckpoint = this.station.name;
sceneryName = this.station.name;
}
if (checkpointsListRef) {
this.chosenCheckpoint =
checkpointsListRef.find(
(ch) => ch.toLocaleLowerCase() === queryCheckpoint?.toLocaleLowerCase()
) ??
checkpointsListRef[0] ??
sceneryName;
}
},
setCheckpoint(cp: string) {
@@ -40,36 +40,28 @@
<span>
{{ $t('scenery.timetable-issued-date') }}
<b>
{{
localeDateTime(
timetableHistory.createdAt > timetableHistory.beginDate
? timetableHistory.beginDate
: timetableHistory.createdAt,
$i18n.locale
)
}}
</b></span
>
<span v-if="timetableHistory.authorName">
{{ $t('scenery.timetable-issued-by') }}
<b>
<router-link
:to="`/journal/timetables?search-dispatcher=${timetableHistory.authorName}`"
>
{{ timetableHistory.authorName }}
</router-link>
{{ parseCreatedDate(timetableHistory, $i18n.locale) }}
</b>
</span>
<span>
{{ $t('scenery.timetable-issued-for') }}
<b>
<router-link
:to="`/journal/timetables?search-driver=${timetableHistory.driverName}`"
>
{{ timetableHistory.driverName }}
</router-link>
</b>
<router-link
class="journal-link"
:to="`/journal/timetables?search-driver=${timetableHistory.driverName}`"
>
{{ timetableHistory.driverName }}
</router-link>
</span>
<span v-if="timetableHistory.authorName">
{{ $t('scenery.timetable-issued-by') }}
<router-link
class="journal-link"
:to="`/journal/timetables?search-dispatcher=${timetableHistory.authorName}`"
>
{{ timetableHistory.authorName }}
</router-link>
</span>
</div>
</span>
@@ -106,7 +98,7 @@ import { useApiStore } from '../../store/apiStore';
import routerMixin from '../../mixins/routerMixin';
import { useMainStore } from '../../store/mainStore';
const historyModeList = ['via', 'issuedFrom', 'terminatingAt'] as const;
const historyModeList = ['includesScenery', 'issuedFrom', 'via', 'terminatingAt'] as const;
type HistoryMode = (typeof historyModeList)[number];
export default defineComponent({
@@ -131,17 +123,19 @@ export default defineComponent({
dataStatus: Status.Data.Loading,
DataStatus: Status.Data,
checkedHistoryMode: 'via' as HistoryMode
checkedHistoryMode: 'includesScenery' as HistoryMode
};
},
async activated() {
this.checkedHistoryMode = 'includesScenery';
this.fetchAPIData();
},
methods: {
async fetchAPIData() {
const stationName = this.$route.query['station'];
this.dataStatus = Status.Data.Loading;
if (!stationName) {
this.historyList = [];
@@ -152,6 +146,7 @@ export default defineComponent({
const requestFilters: Record<string, any> = {};
requestFilters[this.checkedHistoryMode] = stationName.toString();
requestFilters.countLimit = 30;
requestFilters['returnType'] = 'short';
try {
const response: API.TimetableHistory.Response = await (
@@ -165,12 +160,12 @@ export default defineComponent({
this.dataStatus = Status.Data.Loaded;
} catch (error) {
console.error(error);
this.dataStatus = Status.Data.Error;
}
},
checkHistoryMode(mode: HistoryMode) {
this.checkedHistoryMode = mode;
this.dataStatus = Status.Data.Loading;
this.fetchAPIData();
},
@@ -181,6 +176,18 @@ export default defineComponent({
[`search-${this.checkedHistoryMode}`]: this.station?.name || this.onlineScenery?.name
}
});
},
parseCreatedDate(timetable: API.TimetableHistory.Data, locale: string) {
const createdDate =
timetable.createdAt > timetable.beginDate
? new Date(timetable.beginDate)
: new Date(timetable.createdAt);
return createdDate.toLocaleString(locale == 'pl' ? 'pl-PL' : 'en-GB', {
timeStyle: 'short',
dateStyle: 'medium'
});
}
},
components: { Loading }
@@ -215,7 +222,15 @@ export default defineComponent({
button {
padding: 0.35em;
min-width: 120px;
}
}
.journal-link {
font-weight: bold;
color: #eee;
&:hover {
color: var(--clr-primary);
}
}
@@ -21,9 +21,7 @@
<template v-else>{{ $t('filters.no-changed-filters') }}</template>
</div>
<section class="card_sceneries-search">
<h3 class="section-header">{{ $t('filters.sceneries-search') }}</h3>
<section class="card_input-search">
<datalist id="sceneries">
<option
v-for="scenery in sortedStationList"
@@ -32,18 +30,60 @@
></option>
</datalist>
<form action="javascript:void(0);" @submit="handleSceneriesInput">
<input
v-model="chosenSearchScenery"
id="scenery-search"
list="sceneries"
:placeholder="$t('filters.sceneries-placeholder')"
@focus="preventKeyDown = true"
@blur="preventKeyDown = false"
/>
<input
v-model="chosenSearchScenery"
id="scenery-search"
list="sceneries"
:placeholder="$t('filters.sceneries-placeholder')"
@focus="preventKeyDown = true"
@blur="preventKeyDown = false"
/>
<button class="btn--action">{{ $t('filters.search-button-title') }}</button>
</form>
<button class="btn--action" @click="handleSceneriesInput">
{{ $t('filters.search-button-title') }}
</button>
</section>
<section class="card_input-search authors">
<datalist id="authors" name="authors">
<option v-for="(author, i) in authorsOptions" :key="i" :value="author"></option>
</datalist>
<input
type="text"
id="author"
list="authors"
name="authors"
v-model="filters['authors']"
:placeholder="$t('filters.authors-placeholder')"
@focus="preventKeyDown = true"
@blur="preventKeyDown = false"
/>
<button class="btn--action btn--image" @click="resetAuthorsInput">
<img src="/images/icon-exit.svg" alt="reset authors search" />
</button>
</section>
<section class="card_input-search">
<datalist id="projects" name="projects">
<option v-for="(project, i) in projectsOptions" :key="i" :value="project"></option>
</datalist>
<input
type="text"
id="projects"
list="projects"
name="projects"
v-model="filters['projects']"
:placeholder="$t('filters.projects-placeholder')"
@focus="preventKeyDown = true"
@blur="preventKeyDown = false"
/>
<button class="btn--action btn--image" @click="resetProjectsInput">
<img src="/images/icon-exit.svg" alt="reset projects search" />
</button>
</section>
<section class="card_options">
@@ -97,29 +137,6 @@
</span>
</section>
<section class="card_authors-search">
<h3 class="section-header">{{ $t('filters.authors-search') }}</h3>
<datalist id="authors" name="authors">
<option v-for="(author, i) in authorsHint" :key="i" :value="author"></option>
</datalist>
<form action="javascript:void(0);" @submit="handleAuthorsInput">
<input
type="text"
id="author"
list="authors"
name="authors"
v-model="authors"
:placeholder="$t('filters.authors-placeholder')"
@focus="preventKeyDown = true"
@blur="preventKeyDown = false"
/>
<button class="btn--action">{{ $t('filters.search-button-title') }}</button>
</form>
</section>
<section class="card_sliders">
<div class="slider" v-for="(slider, i) in sliderStates" :key="i">
<input
@@ -200,7 +217,8 @@ export default defineComponent({
sliderStates,
minimumHours: 0,
authors: '',
authorSearchFilter: '',
projectSearchFilter: '',
currentRegion: { id: '', value: '' },
@@ -255,11 +273,7 @@ export default defineComponent({
.sort((s1, s2) => (s1.name > s2.name ? 1 : -1));
},
currentOptionsActive() {
return true;
},
authorsHint() {
authorsOptions() {
return this.store.stationList
.reduce((acc, station) => {
station.generalInfo?.authors?.forEach((author) => {
@@ -270,6 +284,17 @@ export default defineComponent({
return acc;
}, [] as string[])
.sort((a, b) => a.localeCompare(b));
},
projectsOptions() {
return this.store.stationList
.reduce((acc, station) => {
if (!station.generalInfo || !station.generalInfo.project || station.generalInfo.hidden) return acc;
if (!acc.includes(station.generalInfo.project.trim())) acc.push(station.generalInfo.project.trim());
return acc;
}, [] as string[])
.sort((a, b) => a.localeCompare(b));
}
},
@@ -294,8 +319,12 @@ export default defineComponent({
this.scrollTop = (e.target as HTMLElement).scrollTop;
},
handleAuthorsInput() {
this.filters['authors'] = this.authors;
resetAuthorsInput() {
this.filters['authors'] = this.authorSearchFilter;
},
resetProjectsInput() {
this.filters['projects'] = this.projectSearchFilter;
},
handleSceneriesInput() {
@@ -340,7 +369,7 @@ export default defineComponent({
// Reset local model values
this.minimumHours = 0;
this.authors = '';
this.authorSearchFilter = '';
// Reset global filters
Object.keys(this.filters).forEach((filterKey) => {
@@ -456,27 +485,23 @@ h3.section-header {
}
}
.card_authors-search,
.card_sceneries-search {
margin: 1em 0;
.card_input-search {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5em;
form {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 0.5em;
width: 100%;
margin-top: 1em;
button {
height: 100%;
}
input {
width: 70%;
max-width: 400px;
width: 100%;
padding: 0.5em;
outline: 1px solid white;
border: 1px solid #aaa;
}
&.authors {
margin-top: 1em;
}
}
+4 -4
View File
@@ -14,10 +14,10 @@
<transition name="dropdown-anim">
<div class="dropdown_wrapper" v-if="showDropdown">
<div>
<h1 class="stats-title text--primary">
<h2 class="stats-title text--primary">
<img src="/images/icon-stats.svg" alt="Open filters icon" height="28" />
{{ $t('station-stats.title') }}
</h1>
</h2>
<hr style="margin: 0.5em 0" />
@@ -245,7 +245,7 @@ export default defineComponent({
@use '../../styles/badge';
@use '../../styles/responsive';
h1.stats-title img {
.stats-title img {
vertical-align: text-bottom;
}
@@ -279,7 +279,7 @@ h1.stats-title img {
}
@include responsive.smallScreen {
h1.stats-title {
.stats-title {
text-align: center;
}
+10 -7
View File
@@ -132,22 +132,25 @@ function filterSliderValues(filters: Record<string, any>, generalInfo: StationGe
filters['minOneWayCatenary'] > routes.singleElectrifiedNames.length ||
filters['minOneWay'] > routes.singleOtherNames.length ||
filters['minTwoWayCatenary'] > routes.doubleElectrifiedNames.length ||
// filters['minTwoWay'] > routes.doubleOtherNames.length ||
filters['minTwoWay'] > routes.doubleOtherNames.length ||
filters['minOneWayCatenaryInt'] >
internalRoutes.filter((r) => r.routeTracks == 1 && r.isElectric == true).length ||
filters['minOneWayInt'] >
internalRoutes.filter((r) => r.routeTracks == 1 && r.isElectric == false).length ||
filters['minTwoWayCatenaryInt'] >
internalRoutes.filter((r) => r.routeTracks == 2 && r.isElectric == true).length
internalRoutes.filter((r) => r.routeTracks == 2 && r.isElectric == true).length ||
filters['minTwoWayInt'] >
internalRoutes.filter((r) => r.routeTracks == 2 && r.isElectric == false).length
);
}
function filterInputValues(filters: Record<string, any>, generalInfo: StationGeneralInfo) {
return (
filters['authors'].length > 3 &&
!generalInfo.authors
?.map((a) => a.toLocaleLowerCase())
.includes(filters['authors'].toLocaleLowerCase())
(filters['authors'].length > 3 &&
!generalInfo.authors
?.map((a) => a.toLocaleLowerCase())
.includes(filters['authors'].toLocaleLowerCase())) ||
(filters['projects'].length > 0 && generalInfo.project != filters['projects'])
);
}
@@ -243,7 +246,7 @@ export const sortStations = (a: Station, b: Station, sorter: ActiveSorter) => {
return a.name.localeCompare(b.name);
};
export const filterStations = (station: Station, filters: Record<string, any>) => {
export const filterStations = (station: Station, filters: Record<string, any>) => {
if (filters['free'] && (!station.onlineInfo || station.onlineInfo.dispatcherId == -1))
return false;
+12 -3
View File
@@ -9,6 +9,17 @@
<div class="text--primary">
<b>{{ trainInfo.stockList[0] }}</b> &bull; {{ trainInfo.length }}m &bull;
{{ (trainInfo.mass / 1000).toFixed(2) }}t
<span v-if="trainInfo.timetableData">
&bull; vRJ:
{{
trainInfo.timetableData?.trainMaxSpeed ||
getStockSpeedLimit(trainInfo.stockList, trainInfo.mass)
}}km/h
</span>
<span v-else class="text--grayed font--italic">
&bull; vMax:
{{ getStockSpeedLimit(trainInfo.stockList, trainInfo.mass) }}km/h
</span>
</div>
<div class="text--grayed">
@@ -46,9 +57,7 @@ export default defineComponent({
return this.mainStore.trainList.find((t) => t.id === this.tooltipStore.content);
},
lastSceneryStatus() {
}
lastSceneryStatus() {}
}
});
</script>
+9 -52
View File
@@ -111,7 +111,7 @@
</div>
<div
v-if="!train.online && train.lastSeen >= Date.now() - 60000"
v-if="!train.online && train.lastSeen <= Date.now() - 60000"
class="train-badge offline"
>
<i class="fa-solid fa-user-slash"></i>
@@ -135,7 +135,11 @@
<img src="/images/icon-speed.svg" alt="speed icon" />
{{ train.speed }} km/h
<span v-if="stockSpeedLimit != Infinity">
<span v-if="train.timetableData">
&bull; vRJ: {{ train.timetableData.trainMaxSpeed }} km/h
</span>
<span v-else-if="stockSpeedLimit != Infinity">
&bull;
<em
class="text--grayed"
@@ -219,57 +223,9 @@ export default defineComponent({
computed: {
stockSpeedLimit() {
let isPassenger = true;
// Check the whole consist speed limit
const vehicleMaxSpeed = this.train.stockList.reduce((acc, stockName, i) => {
const [vehicleName, vehicleCargo] = stockName.split(':');
const vehicleData = this.apiStore.vehiclesData?.find((v) => v.name == vehicleName);
if (!vehicleData) return acc;
let vehicleSpeed = vehicleData.group.speed;
if (vehicleData.type == 'wagon-freight') {
isPassenger = false;
if (vehicleCargo !== undefined && vehicleData.group.speedLoaded) {
vehicleSpeed = vehicleData.group.speedLoaded;
}
}
return Math.min(vehicleSpeed, acc);
}, Infinity);
// Check the head vehicle speed limit
const headLocoName = this.train.stockList[0];
const headLocoVehicleData = this.apiStore.vehiclesData?.find((v) => v.name == headLocoName);
// Omit speed check for head vehicle if there's no data for it
if (!headLocoName || !headLocoVehicleData || !headLocoVehicleData.group.massSpeeds)
return vehicleMaxSpeed;
const massSpeeds =
headLocoVehicleData.group.massSpeeds[
this.train.stockList.length == 1 ? 'none' : isPassenger ? 'passenger' : 'cargo'
];
// Omit speed check if there's no data on mass speeds
if (!massSpeeds) return vehicleMaxSpeed;
// Number type for locomotives alone
if (typeof massSpeeds === 'number') return massSpeeds;
// Record type for passenger or cargo, find the closest range
const massKey = Object.keys(massSpeeds).findLast(
(massKey) => this.train.mass >= Number(massKey)
);
const massMaxSpeed = massKey ? massSpeeds[massKey] : Infinity;
return Math.min(massMaxSpeed, vehicleMaxSpeed);
return this.getStockSpeedLimit(this.train.stockList, this.train.mass);
},
journalRouteLocation() {
return {
path: '/journal/timetables',
@@ -397,6 +353,7 @@ export default defineComponent({
.status-badges {
display: flex;
flex-wrap: wrap;
margin-left: 0.25em;
gap: 0.25em;
@@ -43,6 +43,8 @@
id="search-active-driver"
:placeholder="$t(`options.search-driver`)"
v-model="searchedDriver"
@focus="preventKeyDown = true"
@blur="preventKeyDown = false"
/>
<button class="btn btn--action search-exit" @click="onInputClear('driver')">
+37 -26
View File
@@ -12,8 +12,16 @@
:data-delayed="stop.departureDelay > 0"
:data-stop-type="stop.type"
:data-is-active="stop.isActive"
:data-track-count-departure="stop.departureLineInfo?.routeTracks ?? 2"
:data-track-count-arrival="stop.arrivalLineInfo?.routeTracks ?? 2"
:data-track-count-departure="
stop.departureLineInfo?.routeTracks ??
stop.nextPointRef?.arrivalLineInfo?.routeTracks ??
2
"
:data-track-count-arrival="
stop.arrivalLineInfo?.routeTracks ??
scheduleStops[i - 1]?.departureLineInfo?.routeTracks ??
2
"
>
<span class="stop_info">
<span class="distance">
@@ -60,7 +68,8 @@
<span>
|
{{
stop.departureLineInfo.routeSpeedExit
stop.departureLineInfo.routeSpeedExit &&
stop.departureLineInfo.routeSpeedExit != stop.departureLineInfo.routeSpeed
? `${stop.departureLineInfo.routeSpeedExit} (${stop.departureLineInfo.routeSpeed})`
: stop.departureLineInfo.routeSpeed
}}</span
@@ -113,10 +122,16 @@
<span> {{ stop.nextPointRef.arrivalLine }}</span>
<span v-if="stop.nextPointRef.arrivalLineInfo">
<span> | {{ stop.nextPointRef.arrivalLineInfo!.routeSpeed }}</span>
<span v-if="stop.nextPointRef.arrivalLineInfo!.routeSpeedExit"
>({{ stop.nextPointRef.arrivalLineInfo!.routeSpeedExit }})</span
<span> | {{ stop.nextPointRef.arrivalLineInfo.routeSpeed }}</span>
<span
v-if="
stop.nextPointRef.arrivalLineInfo.routeSpeedExit &&
stop.nextPointRef.arrivalLineInfo.routeSpeedExit !=
stop.nextPointRef.arrivalLineInfo.routeSpeed
"
>
({{ stop.nextPointRef.arrivalLineInfo.routeSpeedExit }})
</span>
<img
:src="
@@ -186,26 +201,28 @@ export default defineComponent({
const sceneryData =
this.store.stationList?.find((sc) => sc.name == pathEl.stationName) ?? null;
if (!sceneryData || !sceneryData.generalInfo) return null;
const activeScenery = this.apiStore.activeData?.activeSceneries?.find(
(sc) => sc.stationName == pathEl.stationName
);
const arrivalLineData = pathEl.arrivalRouteExt
? (sceneryData.generalInfo.routes.all.find(
(rt) => rt.routeName == pathEl.arrivalRouteExt
) ?? null)
const arrivalLineData = sceneryData?.generalInfo
? pathEl.arrivalRouteExt
? (sceneryData.generalInfo.routes.all.find(
(rt) => rt.routeName == pathEl.arrivalRouteExt
) ?? null)
: null
: null;
const departureLineData = pathEl.departureRouteExt
? (sceneryData.generalInfo.routes.all.find(
(rt) => rt.routeName == pathEl.departureRouteExt
) ?? null)
const departureLineData = sceneryData?.generalInfo
? pathEl.departureRouteExt
? (sceneryData.generalInfo.routes.all.find(
(rt) => rt.routeName == pathEl.departureRouteExt
) ?? null)
: null
: null;
return {
generalInfo: sceneryData.generalInfo,
generalInfo: sceneryData?.generalInfo ?? null,
isOnline:
activeScenery &&
(activeScenery.isOnline == 1 || activeScenery.lastSeen >= Date.now() - 60000),
@@ -234,7 +251,7 @@ export default defineComponent({
let isActive = false;
if (pathData?.departureLineData) {
// arrivalLineInfo = pathData.departureLineData;
arrivalLineInfo = pathData.departureLineData;
departureLineInfo = pathData.departureLineData;
}
@@ -245,22 +262,16 @@ export default defineComponent({
isExternal = true;
departureLineInfo = pathData?.arrivalLineData ?? null;
if (pathData?.arrivalLineData) {
arrivalLineInfo = pathData.arrivalLineData;
}
arrivalLineInfo = pathData.arrivalLineData;
}
let correctedDepartureLineData: StationRoutesInfo | null = null;
const internalRouteInfo = stop.departureLine
? pathData?.generalInfo.routes.all.find(
? pathData?.generalInfo?.routes.all.find(
(route) => route.isInternal && route.routeName == stop.departureLine
)
: undefined;
if (internalRouteInfo) {
correctedDepartureLineData = internalRouteInfo;
departureLineInfo = internalRouteInfo;
}
+4 -4
View File
@@ -13,10 +13,10 @@
<transition name="dropdown-anim">
<div class="dropdown_wrapper" v-if="showOptions">
<h1 class="text--primary">
<h2 class="stats-title text--primary">
<img src="/images/icon-stats.svg" alt="Open filters icon" height="28" />
{{ $t('train-stats.title') }}
</h1>
</h2>
<hr style="margin: 0.5em 0" />
@@ -229,7 +229,7 @@ export default defineComponent({
@use '../../styles/badge';
@use '../../styles/responsive';
h1 img {
.stats-title img {
vertical-align: text-bottom;
}
@@ -257,7 +257,7 @@ h3 {
text-align: center;
}
h1 {
.stats-title {
text-align: center;
}
}
+14 -12
View File
@@ -76,6 +76,7 @@
"tooltip-driver-offline": "Driver is offline",
"tooltip-scenery-offline": "Scenery is offline",
"pojazdownik-link-content": "POJAZDOWNIK",
"language-tooltip-content": "JĘZYK / LANGUAGE",
"gnr-link-content": "TRAIN ORDERS <br> GENERATOR"
},
"footer": {
@@ -142,7 +143,7 @@
"title": "Control type",
"SPK": "SPK",
"SCS": "SCS",
"SCS-SPK": "SCS/SPK",
"SCS-SPK": "SCS + SPK",
"SPE": "SPE",
"ręczne": "manual",
"ręczne+SPK": "manual + SPK",
@@ -153,7 +154,7 @@
"abbrevs": {
"SPK": "SPK",
"SCS": "SCS",
"SCS-SPK": "S/S",
"SCS-SPK": "S+S",
"SPE": "SPE",
"ręczne": "R",
"ręczne+SPK": "R",
@@ -271,6 +272,7 @@
"SCS": "SCS",
"SCS-R": "SCS + MANUAL",
"SCS-M": "SCS + MECH.",
"SCS-SPK": "SCS + SPK",
"SPE": "SPE",
"manual": "MANUAL",
"mechanical": "MECHANICAL",
@@ -304,10 +306,9 @@
"minTwoWayCatenaryInt": "MIN. INTERNAL CATENARY DOUBLE TRACK ROUTES",
"minTwoWayInt": "MIN. INTERNAL OTHER DOUBLE TRACK ROUTES"
},
"sceneries-search": "SCENERY SEARCH:",
"sceneries-placeholder": "Enter scenery name...",
"authors-search": "SEARCH BY AUTHOR NAME (other filters apply):",
"authors-placeholder": "Enter the author nickname...",
"sceneries-placeholder": "Search for scenery",
"authors-placeholder": "Scenery author (other filters apply)",
"projects-placeholder": "Scenery project (other filters apply)",
"search-button-title": "SEARCH",
"minimum-hours-title": "SHOW ONLY SCENERIES UNTIL:",
"now": "NOW",
@@ -343,8 +344,8 @@
"ASDEK": "ASDEK program available (defect detection of moving rolling stock)",
"TWB-all": "This scenery has two-way route blockade on all routes",
"TWB-routes": "This scenery has two-way route blockade on following routes: ",
"default": "Scenery available in game package",
"nonDefault": "Sceneria available to download from forum site",
"default": "Scenery available in the game package",
"nonDefault": "Scenery available to download from the forum site",
"req-level": "all dispatcher levels | requries {lvl} dispatcher lvl | requires {lvl} dispatcher lvl",
"non-public": "Non-public scenery",
"unavailable": "Unavailable scenery",
@@ -559,12 +560,13 @@
"option-active-timetables": "Active timetables",
"option-timetables-history": "Timetables history PL1",
"option-dispatchers-history": "Dispatchers history PL1",
"timetable-via": "ALL TIMETABLES",
"timetable-includesScenery": "ALL TIMETABLES",
"timetable-via": "PASSES THROUGH",
"timetable-issuedFrom": "BEGINS HERE",
"timetable-terminatingAt": "TERMINATES HERE",
"timetable-terminatingAt": "ENDS HERE",
"timetable-issued-date": "Issued",
"timetable-issued-by": " by:",
"timetable-issued-for": " for driver:",
"timetable-issued-for": " for:",
"dispatcher-rate": "Rate:",
"dispatcher-status-changes": "Status changes:",
"req-level": "all dispatcher levels | dispatcher level {lvl} required | dispatcher level {lvl} required",
@@ -609,4 +611,4 @@
"search-train": "Train no.",
"search-driver": "Driver name"
}
}
}
+13 -11
View File
@@ -73,6 +73,7 @@
"tooltip-driver-offline": "Maszynista offline",
"tooltip-scenery-offline": "Sceneria offline",
"pojazdownik-link-content": "POJAZDOWNIK",
"language-tooltip-content": "JĘZYK / LANGUAGE",
"gnr-link-content": "GENERATOR <br> ROZKAZÓW PISEMNYCH"
},
"footer": {
@@ -139,7 +140,7 @@
"title": "Sterowanie",
"SPK": "SPK",
"SCS": "SCS",
"SCS-SPK": "SCS/SPK",
"SCS-SPK": "SCS + SPK",
"SPE": "SPE",
"ręczne": "ręczne",
"ręczne+SPK": "ręczne z SPK",
@@ -150,7 +151,7 @@
"abbrevs": {
"SPK": "SPK",
"SCS": "SCS",
"SCS-SPK": "S/S",
"SCS-SPK": "S+S",
"SPE": "SPE",
"ręczne": "R",
"ręczne+SPK": "R",
@@ -269,6 +270,7 @@
"SCS": "SCS",
"SCS-R": "SCS + RĘCZNE",
"SCS-M": "SCS + MECH.",
"SCS-SPK": "SCS + SPK",
"SPE": "SPE",
"manual": "RĘCZNE",
"SUP": "SUP (RASP-UZK)",
@@ -302,10 +304,9 @@
"minTwoWayCatenaryInt": "SZLAKI DWUTOROWE ZELEKTR. WEWNĘTRZNE (MINIMUM)",
"minTwoWayInt": "SZLAKI DWUTOROWE NIEZELEKTR. WEWNĘTRZNE (MINIMUM)"
},
"sceneries-search": "WYSZUKAJ SCENERIĘ:",
"sceneries-placeholder": "Wpisz nazwę scenerii...",
"authors-search": "WYSZUKAJ AUTORA (uwzględnia inne filtry):",
"authors-placeholder": "Wpisz nick autora...",
"sceneries-placeholder": "Wyszukaj scenerię",
"authors-placeholder": "Autor scenerii (uwzględnia inne filtry)",
"projects-placeholder": "Projekt scenerii (uwzględnia inne filtry)",
"search-button-title": "SZUKAJ",
"minimum-hours-title": "POKAŻ TYLKO SCENERIE DOSTĘPNE MINIMUM DO:",
"now": "TERAZ",
@@ -340,7 +341,7 @@
"SUP": "Wymaga programu SUP do kontroli systemu RASP-UZK",
"ASDEK": "Dostępny program ASDEK do detekcji stanów awaryjnych taboru w ruchu",
"default": "Sceneria dostępna domyślnie w paczce z grą",
"nonDefault": "Sceneria dostępna do pobrania ze strony forum",
"nonDefault": "Sceneria dostępna do pobrania z forum symulatora",
"req-level": "ogólnodostępna | od {lvl} poz. DR | od {lvl} poz. DR",
"non-public": "Sceneria niepubliczna",
"unavailable": "Sceneria niedostępna",
@@ -545,12 +546,13 @@
"option-active-timetables": "Aktywne rozkłady jazdy",
"option-timetables-history": "Historia rozkładów PL1",
"option-dispatchers-history": "Historia dyżurów PL1",
"timetable-via": "WSZYSTKIE RJ",
"timetable-includesScenery": "WSZYSTKIE RJ",
"timetable-via": "PRZEJEŻDŻA",
"timetable-issuedFrom": "ROZPOCZYNA BIEG",
"timetable-terminatingAt": "KOŃCZY BIEG",
"timetable-issued-date": "Wystawiony",
"timetable-issued-date": "Wystawiony: ",
"timetable-issued-by": " przez:",
"timetable-issued-for": " dla maszynisty:",
"timetable-issued-for": " dla:",
"dispatcher-rate": "Ocena:",
"dispatcher-status-changes": "Zmiany statusów:",
"req-level": "ogólnodostępna | minimum {lvl} poziom dyżurnego | minimum {lvl} poziom dyżurnego",
@@ -593,4 +595,4 @@
"history": {
"title": "DZIENNIK ROZKŁADÓW JAZDY"
}
}
}
+23 -9
View File
@@ -31,6 +31,7 @@ export const initFilters = {
mechanical: false,
'SPK-M': false,
'SCS-M': false,
'SCS-SPK': false,
modern: false,
semaphores: false,
historical: false,
@@ -61,12 +62,14 @@ export const initFilters = {
maxLevel: 20,
minOneWay: 0,
minOneWayCatenary: 0,
minTwoWayCatenary: 0,
minOneWayInt: 0,
minOneWayCatenaryInt: 0,
minTwoWay: 0,
minTwoWayCatenary: 0,
minTwoWayInt: 0,
minTwoWayCatenaryInt: 0,
// minTwoWay: 0,
authors: ''
authors: '',
projects: ''
};
export const sliderStates = [
@@ -76,12 +79,12 @@ export const sliderStates = [
{ id: 'maxLevel', minRange: 0, maxRange: 20, step: 1 },
{ id: 'minOneWay', minRange: 0, maxRange: 5, step: 1 },
{ id: 'minOneWayCatenary', minRange: 0, maxRange: 5, step: 1 },
{ id: 'minTwoWayCatenary', minRange: 0, maxRange: 5, step: 1 },
{ id: 'minOneWayInt', minRange: 0, maxRange: 5, step: 1 },
{ id: 'minOneWayCatenaryInt', minRange: 0, maxRange: 5, step: 1 },
{ id: 'minTwoWayCatenaryInt', minRange: 0, maxRange: 5, step: 1 },
// { id: 'minTwoWay', minRange: 0, maxRange: 5, step: 1 },
// { id: 'minTwoWayInt', minRange: 0, maxRange: 5, step: 1 }
{ id: 'minTwoWay', minRange: 0, maxRange: 5, step: 1 },
{ id: 'minTwoWayCatenary', minRange: 0, maxRange: 5, step: 1 },
{ id: 'minTwoWayInt', minRange: 0, maxRange: 5, step: 1 },
{ id: 'minTwoWayCatenaryInt', minRange: 0, maxRange: 5, step: 1 }
];
export type StationFilter = keyof typeof initFilters;
@@ -95,7 +98,18 @@ export const filtersSections: Record<StationFilterSection, StationFilter[]> = {
stationType: ['junction', 'nonJunction'],
access: ['nonPublic', 'unavailable', 'abandoned'],
addons: ['SUP', 'ASDEK', 'noSUP', 'noASDEK'],
control: ['SPK', 'SCS', 'SPE', 'SPK-M', 'SCS-M', 'mechanical', 'SPK-R', 'SCS-R', 'manual'],
control: [
'SPK',
'SCS',
'SPE',
'SCS-SPK',
'SPK-M',
'SCS-M',
'mechanical',
'SPK-R',
'SCS-R',
'manual'
],
blockades: ['SBL', 'PBL'],
signals: ['modern', 'semaphores', 'mixed', 'historical']
};
@@ -116,7 +130,7 @@ export function setupFilters(currentFilters: Record<string, any>) {
});
}
export function getChangedFilters(currentFilters: Record<string, any>): string[] {
export function getChangedFilters(currentFilters: Record<string, any>): string[] {
return (
Object.keys(currentFilters).filter(
(filterKey) =>
+53 -37
View File
@@ -1,45 +1,10 @@
import { defineComponent } from 'vue';
import { Train, TrainStop } from '../typings/common';
import { useApiStore } from '../store/apiStore';
export default defineComponent({
data: () => ({
STATS: {
main: [
{
name: 'speed',
unit: 'km/h'
},
{
name: 'length',
unit: 'm'
},
{
name: 'mass',
unit: 't',
multiplier: 0.001
}
],
position: [
{
name: 'scenery',
prop: 'currentStationName'
},
{
name: 'route',
prop: 'connectedTrack'
},
{
name: 'signal',
prop: 'signal'
},
{
name: 'distance',
prop: 'distance',
unit: 'm'
}
]
}
apiStore: useApiStore()
}),
methods: {
@@ -150,6 +115,57 @@ export default defineComponent({
if (distance < 1000) return `${distance}m`;
return `${(distance / 1000).toPrecision(2)}km`;
},
getStockSpeedLimit(stockList: string[], trainMass: number) {
let isPassenger = true;
// Check the whole consist speed limit
const vehicleMaxSpeed = stockList.reduce((acc, stockName, i) => {
const [vehicleName, vehicleCargo] = stockName.split(':');
const vehicleData = this.apiStore.vehiclesData?.find((v) => v.name == vehicleName);
if (!vehicleData) return acc;
let vehicleSpeed = vehicleData.group.speed;
if (vehicleData.type == 'wagon-freight') {
isPassenger = false;
if (vehicleCargo !== undefined && vehicleData.group.speedLoaded) {
vehicleSpeed = vehicleData.group.speedLoaded;
}
}
return Math.min(vehicleSpeed, acc);
}, Infinity);
// Check the head vehicle speed limit
const headLocoName = stockList[0];
const headLocoVehicleData = this.apiStore.vehiclesData?.find((v) => v.name == headLocoName);
// Omit speed check for head vehicle if there's no data for it
if (!headLocoName || !headLocoVehicleData || !headLocoVehicleData.group.massSpeeds)
return vehicleMaxSpeed;
const massSpeeds =
headLocoVehicleData.group.massSpeeds[
stockList.length == 1 ? 'none' : isPassenger ? 'passenger' : 'cargo'
];
// Omit speed check if there's no data on mass speeds
if (!massSpeeds) return vehicleMaxSpeed;
// Number type for locomotives alone
if (typeof massSpeeds === 'number') return massSpeeds;
// Record type for passenger or cargo, find the closest range
const massKey = Object.keys(massSpeeds).findLast((massKey) => trainMass >= Number(massKey));
const massMaxSpeed = massKey ? massSpeeds[massKey] : Infinity;
return Math.min(massMaxSpeed, vehicleMaxSpeed);
}
}
});
+65 -6
View File
@@ -11,8 +11,11 @@ import {
} from '../typings/common';
import { useApiStore } from './apiStore';
import { MainStoreState } from './typings';
import i18n from '../i18n';
import StorageManager from '../managers/storageManager';
const checkpointsTrains: Map<string, CheckpointTrain[]> = new Map();
const unknownSceneryCheckpoints: Map<string, Set<string>> = new Map();
const sceneriesTrains: Map<string, Train[]> = new Map();
export const useMainStore = defineStore('mainStore', {
@@ -36,12 +39,22 @@ export const useMainStore = defineStore('mainStore', {
currentLocale: 'pl'
}) as MainStoreState,
actions: {
changeLocale(localeName: string) {
(i18n.global.locale.value as any) = localeName;
this.currentLocale = localeName;
StorageManager.setStringValue('lang', localeName);
}
},
getters: {
trainList(): Train[] {
const apiStore = useApiStore();
checkpointsTrains.clear();
sceneriesTrains.clear();
unknownSceneryCheckpoints.clear();
const dateNow = new Date();
@@ -133,8 +146,13 @@ export const useMainStore = defineStore('mainStore', {
// Checkpoints trains map
if (trainObj.timetableData) {
let currentSceneryIndex = 0;
const timetablePath = trainObj.timetableData.timetablePath;
let currentSceneryIndex = 0;
let currentSceneryData: Station | null =
this.stationList.find(
(s) => s.name == timetablePath[currentSceneryIndex].stationName
) ?? null;
trainObj.timetableData.followingStops.forEach((stop, i) => {
if (/strong|podg|pe/.test(stop.stopName)) {
@@ -153,16 +171,41 @@ export const useMainStore = defineStore('mainStore', {
timetablePathElement: timetablePath[currentSceneryIndex]
};
// Adding missing sceneries checkpoints as a fallback when scenery data is missing (and "generalInfo" is unavailable)
if (!currentSceneryData) {
const sceneryCheckpointsSet = unknownSceneryCheckpoints.get(
checkpointTrain.timetablePathElement.stationName
);
if (!sceneryCheckpointsSet) {
unknownSceneryCheckpoints.set(
checkpointTrain.timetablePathElement.stationName,
new Set([stop.stopNameRAW])
);
} else {
sceneryCheckpointsSet.add(stop.stopNameRAW);
}
}
// Adding trains to their corresponding checkpoints
if (checkpointsTrains.has(stop.stopNameRAW.toLowerCase())) {
checkpointsTrains.set(stop.stopNameRAW.toLowerCase(), [
...checkpointsTrains.get(stop.stopNameRAW.toLowerCase())!,
checkpointTrain
]);
} else checkpointsTrains.set(stop.stopNameRAW.toLowerCase(), [checkpointTrain]);
} else {
checkpointsTrains.set(stop.stopNameRAW.toLowerCase(), [checkpointTrain]);
}
}
if (timetablePath[currentSceneryIndex].departureRouteExt == stop.departureLine)
if (timetablePath[currentSceneryIndex].departureRouteExt == stop.departureLine) {
currentSceneryIndex++;
currentSceneryData =
this.stationList.find(
(s) => s.name == timetablePath[currentSceneryIndex].stationName
) ?? null;
}
});
}
@@ -222,7 +265,9 @@ export const useMainStore = defineStore('mainStore', {
all: 0,
confirmed: 0,
unconfirmed: 0
}
},
missingCheckpoints: []
});
});
@@ -266,7 +311,9 @@ export const useMainStore = defineStore('mainStore', {
all: 0,
confirmed: 0,
unconfirmed: 0
}
},
missingCheckpoints: []
});
return list;
@@ -277,7 +324,7 @@ export const useMainStore = defineStore('mainStore', {
for (let i = 0, n = allActiveSceneries.length; i < n; i++) {
const scenery = allActiveSceneries[i];
const station = this.stationList.find((s) => s.name === scenery.name);
let station = this.stationList.find((s) => s.name === scenery.name);
let checkpointsSet: Set<string> = new Set();
@@ -293,6 +340,18 @@ export const useMainStore = defineStore('mainStore', {
scenery.stationTrains =
sceneriesTrains.get(scenery.name)?.filter((sc) => sc.region == this.region.id) ?? [];
// Missing checkpoints as a fallback for sceneries without generalInfo & checkpoints property
const missingCheckpointsToAdd = unknownSceneryCheckpoints.get(scenery.name);
if (missingCheckpointsToAdd) {
[...missingCheckpointsToAdd].forEach((cp) => {
if (cp.toLowerCase() == scenery.name.toLowerCase()) return;
checkpoints.push(cp);
scenery.missingCheckpoints.push(cp);
});
}
const uniqueTrainIds: string[] = [];
checkpoints.forEach((cp) => {
const scheduledTrains = checkpointsTrains.get(cp.toLowerCase());
+1
View File
@@ -23,6 +23,7 @@ export interface StationJSONData {
project: string;
projectUrl: string;
hash: string;
hidden: boolean;
reqLevel: number;
+6
View File
@@ -225,6 +225,12 @@ ul {
}
}
.font {
&--italic {
font-style: italic;
}
}
button,
a.a-button {
cursor: pointer;
+1 -1
View File
@@ -36,6 +36,6 @@
}
&.SCS-SPK {
color: white;
color: #aefff8;
}
}
+2
View File
@@ -118,6 +118,7 @@ export interface StationGeneralInfo {
availability: Availability;
routes: StationRoutes;
checkpoints: string[];
hidden: boolean;
}
export interface StationRoutes {
@@ -170,6 +171,7 @@ export interface ActiveScenery {
confirmed: number;
unconfirmed: number;
};
missingCheckpoints: string[];
}
export interface ScenerySpawn {
+21
View File
@@ -13,6 +13,18 @@
</div>
<div class="topbar-links">
<button
class="btn--image lang-button"
@click="toggleLocales()"
data-tooltip-type="HtmlTooltip"
:data-tooltip-content="`<b>${$t('app.language-tooltip-content')}</b>`"
>
<img
:src="`/images/icon-${mainStore.currentLocale}.svg`"
alt="change language flag icon"
/>
</button>
<a
class="a-button btn--image gnr-link"
href="https://generator-td2.web.app/"
@@ -96,6 +108,10 @@ export default defineComponent({
methods: {
toggleDonationCard(value: boolean) {
this.isDonationCardOpen = value;
},
toggleLocales() {
this.mainStore.changeLocale(this.mainStore.currentLocale == 'pl' ? 'en' : 'pl');
}
}
});
@@ -149,6 +165,11 @@ button.donation-button {
}
}
button.lang-button {
padding: 0 0.5em;
background-color: #111;
}
a.pojazdownik-link {
background-color: #1f263b;
+2 -2
View File
@@ -1,8 +1,8 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"module": "nodenext",
"moduleResolution": "nodenext",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
+2 -4
View File
@@ -5,7 +5,7 @@ import path from 'path';
export default defineConfig({
server: { port: 5123, open: true },
preview: { port: 4001, open: true },
preview: { port: 4001, open: false },
publicDir: 'public',
css: {
preprocessorOptions: {
@@ -21,11 +21,9 @@ export default defineConfig({
vue(),
VitePWA({
registerType: 'autoUpdate',
includeAssets: ['/images/*.{png,svg,jpg}', '/fonts/*.{woff,woff2}'],
workbox: {
disableDevLogs: true,
globPatterns: ['**/*.{js,css,html,png,svg,jpg}'],
globPatterns: ['**/*.{js,css,html,ico,woff,woff2,ttf}', '**/*.{png,jpg,jpeg,svg,webp,gif}'],
cleanupOutdatedCaches: true,
runtimeCaching: [
{
+1399 -1245
View File
File diff suppressed because it is too large Load Diff