Compare commits

..

10 Commits

Author SHA1 Message Date
Spythere 03ff4d8648 Merge pull request #132 from Spythere/development
v1.30.2
2025-05-27 17:47:28 +02:00
Spythere 23767801d5 chore: added timeout for welcome card appearance 2025-05-27 17:42:31 +02:00
Spythere 310261fb59 chore: queries handling 2025-05-27 17:38:53 +02:00
Spythere 742754ceef bump: v1.30.2 2025-05-27 17:19:48 +02:00
Spythere b9bb9dc201 refactor: globalized current locale, translation improvements 2025-05-27 17:19:32 +02:00
Spythere 611927f96f feat: added welcome card for new incoming users 2025-05-27 16:55:58 +02:00
Spythere 2e191f355e Merge pull request #131 from Spythere/development
fix: english typo
2025-05-24 14:42:22 +02:00
Spythere f974643e37 fix: english typo 2025-05-14 13:59:07 +02:00
Spythere 02afe2bf33 Merge pull request #130 from Spythere/development
hotfix: twr detection
2025-05-12 21:23:49 +02:00
Spythere ebdffc6241 hotfix: twr detection 2025-05-12 21:18:50 +02:00
13 changed files with 356 additions and 23 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "stacjownik", "name": "stacjownik",
"version": "1.30.1", "version": "1.30.2",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+31 -5
View File
@@ -5,9 +5,11 @@
@toggle-card="() => (isUpdateCardOpen = false)" @toggle-card="() => (isUpdateCardOpen = false)"
/> />
<AppWelcomeCard :is-card-open="isWelcomeCardOpen" @toggle-card="closeWelcomeCard" />
<Tooltip /> <Tooltip />
<AppHeader :current-lang="currentLang" @change-lang="changeLang" /> <AppHeader :current-lang="store.currentLocale" @change-lang="changeLang" />
<main class="app_main"> <main class="app_main">
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
@@ -44,8 +46,10 @@ import UpdateCard from './components/App/UpdateCard.vue';
import StorageManager from './managers/storageManager'; import StorageManager from './managers/storageManager';
import AppFooter from './components/App/AppFooter.vue'; import AppFooter from './components/App/AppFooter.vue';
import AppWelcomeCard from './components/App/AppWelcomeCard.vue';
const STORAGE_VERSION_KEY = 'app_version'; const STORAGE_VERSION_KEY = 'app_version';
const WELCOME_CARD_SEEN_KEY = 'welcome_card_seen';
export default defineComponent({ export default defineComponent({
components: { components: {
@@ -54,6 +58,7 @@ export default defineComponent({
AppHeader, AppHeader,
AppFooter, AppFooter,
UpdateCard, UpdateCard,
AppWelcomeCard,
Tooltip Tooltip
}, },
@@ -64,8 +69,8 @@ export default defineComponent({
tooltipStore: useTooltipStore(), tooltipStore: useTooltipStore(),
isUpdateCardOpen: false, isUpdateCardOpen: false,
isWelcomeCardOpen: false,
currentLang: 'pl',
isOnProductionHost: location.hostname == 'stacjownik-td2.web.app' isOnProductionHost: location.hostname == 'stacjownik-td2.web.app'
}), }),
@@ -85,13 +90,29 @@ export default defineComponent({
this.loadLang(); this.loadLang();
this.setupOfflineHandling(); this.setupOfflineHandling();
this.checkAppVersion(); this.checkAppVersion();
this.handleQueries();
this.apiStore.setupAPIData(); this.apiStore.setupAPIData();
}, },
handleQueries() {
const query = new URLSearchParams(window.location.search);
if (query.get('welcomeCard') == '1') {
this.isWelcomeCardOpen = true;
}
},
async checkAppVersion() { async checkAppVersion() {
const isWelcomeCardSeen = StorageManager.getBooleanValue(WELCOME_CARD_SEEN_KEY);
const storageVersion = StorageManager.getStringValue(STORAGE_VERSION_KEY); const storageVersion = StorageManager.getStringValue(STORAGE_VERSION_KEY);
if (isWelcomeCardSeen == false && storageVersion == '') {
setTimeout(() => {
this.isWelcomeCardOpen = true;
}, 1500);
}
try { try {
const releaseData = await ( const releaseData = await (
await axios.get('https://api.github.com/repos/Spythere/stacjownik/releases/latest') await axios.get('https://api.github.com/repos/Spythere/stacjownik/releases/latest')
@@ -140,7 +161,7 @@ export default defineComponent({
changeLang(lang: string) { changeLang(lang: string) {
this.$i18n.locale = lang; this.$i18n.locale = lang;
this.currentLang = lang; this.store.currentLocale = lang;
StorageManager.setStringValue('lang', lang); StorageManager.setStringValue('lang', lang);
}, },
@@ -157,10 +178,15 @@ export default defineComponent({
const naviLanguage = window.navigator.language.toString(); const naviLanguage = window.navigator.language.toString();
if (naviLanguage.startsWith('en')) { if (!naviLanguage.startsWith('pl')) {
this.changeLang('en'); this.changeLang('en');
return; return;
} }
},
closeWelcomeCard() {
this.isWelcomeCardOpen = false;
StorageManager.setBooleanValue(WELCOME_CARD_SEEN_KEY, true);
} }
} }
}); });
+4 -2
View File
@@ -9,7 +9,9 @@
<br /> <br />
<a href="https://discord.gg/x2mpNN3svk"> <a href="https://discord.gg/x2mpNN3svk">
<img src="/images/icon-discord.png" alt="" />&nbsp;<b>{{ $t('footer.discord') }}</b> <img src="/images/icon-discord.png" alt="discord logo icon" />&nbsp;<b class="text--discord">
{{ $t('footer.discord') }}
</b>
</a> </a>
<div style="display: none">&int; ukryta taktyczna całka do programowania w HTMLu</div> <div style="display: none">&int; ukryta taktyczna całka do programowania w HTMLu</div>
@@ -36,4 +38,4 @@ export default defineComponent({
} }
} }
}); });
</script> </script>
+246
View File
@@ -0,0 +1,246 @@
<template>
<Card :is-open="props.isCardOpen">
<div class="body-content">
<h1>{{ $t('welcome.title') }}</h1>
<div class="language-select">
<button :data-active="$i18n.locale == 'pl'" @click="changeLang('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>
</div>
<section class="app-description">
<i18n-t keypath="welcome.app-desc" tag="p">
<template v-slot:b1>
<b>{{ $t('welcome.app-desc-b1') }}</b>
</template>
<template v-slot:link>
<a href="https://td2.info.pl/" class="link" target="_blank">Train Driver 2</a>
</template>
</i18n-t>
</section>
<section class="tabs">
<div class="tab-description">
<h2 class="text--primary">{{ $t('welcome.sceneries-header') }}</h2>
<hr />
<i18n-t keypath="welcome.sceneries-desc" tag="p">
<template v-slot:b1>
<b>{{ $t('welcome.sceneries-desc-b1') }}</b>
</template>
</i18n-t>
</div>
<div class="tab-description">
<h2 class="text--primary">{{ $t('welcome.trains-header') }}</h2>
<hr />
<i18n-t keypath="welcome.trains-desc" tag="p">
<template v-slot:b1>
<b>{{ $t('welcome.trains-desc-b1') }}</b>
</template>
</i18n-t>
</div>
<div class="tab-description">
<h2 class="text--primary">{{ $t('welcome.journal-header') }}</h2>
<hr />
<i18n-t keypath="welcome.journal-desc" tag="p">
<template v-slot:b1>
<b>{{ $t('welcome.journal-desc-b1') }}</b>
</template>
</i18n-t>
</div>
</section>
<section class="other-apps">
<b class="text--primary">
{{ $t('welcome.other-apps') }}
</b>
<div class="apps-grid">
<a class="app-item" href="https://pojazdownik-td2.web.app/" target="_blank">
<img src="/images/icon-pojazdownik.svg" alt="pojazdownik app logo" />
<h3 class="text--primary">Pojazdownik</h3>
<p>{{ $t('welcome.pojazdownik-desc') }}</p>
</a>
<a class="app-item" href="https://generator-td2.web.app/" target="_blank">
<img src="/images/icon-gnr.svg" alt="generator app logo" />
<h3 class="text--primary">GeneraTOR</h3>
<p>{{ $t('welcome.generator-desc') }}</p>
</a>
<a class="app-item" href="https://srjp-td2.web.app/" target="_blank">
<img src="/images/icon-srjp.svg" alt="srjp app logo" />
<h3 class="text--primary">Rozkładownik</h3>
<p>{{ $t('welcome.srjp-desc') }}</p>
</a>
</div>
</section>
<section class="bottom-info">
<i18n-t keypath="welcome.donation-info" tag="div" class="donation-info">
<template v-slot:icon1>
<img src="/images/icon-diamond.svg" alt="diamond icon" width="25" />
<span class="text--donator">&nbsp;{{ $t('welcome.donation-info-icon1-text') }}</span>
</template>
</i18n-t>
<i18n-t keypath="welcome.discord-info" tag="div" class="discord-info">
<template v-slot:discord>
<a href="https://discord.gg/x2mpNN3svk" class="link" target="_blank">
<b class="text--discord">{{ $t('welcome.discord-info-link-text') }}</b>
</a>
</template>
</i18n-t>
<div class="bottom-text">
<i>{{ $t('welcome.bottom-text') }}</i>
</div>
<div class="bottom-actions">
<button class="btn btn--action" @click="toggleCard(false)">
{{ $t('welcome.button-confirm') }}
</button>
</div>
</section>
</div>
</Card>
</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']);
const props = defineProps({
isCardOpen: Boolean
});
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>
.body-content {
max-width: 800px;
min-height: 900px;
padding: 1em 0.5em;
text-align: center;
font-size: 1.1em;
}
hr {
margin-bottom: 0.5em;
}
a.link {
text-decoration: underline;
img {
vertical-align: middle;
margin-right: 0.2em;
}
}
.language-select {
display: flex;
justify-content: center;
margin: 0.5em 0;
button[data-active='false'] img {
opacity: 0.5;
}
}
.app-description {
margin: 1em 0;
}
.tab-description {
margin-top: 0.5em;
}
.other-apps {
font-weight: bold;
margin: 1em 0;
font-size: 1.1em;
}
.apps-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1em;
padding: 1em;
}
.apps-grid > a.app-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5em;
padding: 1em;
background-color: #2b2b2b;
transition: background-color 100ms ease-in-out;
border-radius: 0.5em;
&:hover {
background-color: #3b3b3b;
}
img {
width: 2.5em;
}
}
.donation-info {
font-weight: bold;
font-size: 1.1em;
img {
vertical-align: middle;
}
}
.discord-info {
margin-top: 1em;
font-weight: bold;
img {
vertical-align: middle;
}
}
.bottom-text {
margin: 1em 0;
font-weight: bold;
font-size: 1.2em;
}
.bottom-actions {
display: flex;
justify-content: center;
margin-top: 1em;
font-size: 1.25em;
}
</style>
+2 -2
View File
@@ -9,7 +9,7 @@
<span <span
class="train-badge twr" class="train-badge twr"
v-if="train.timetableData?.TWR" v-if="train.timetableData?.twr"
data-tooltip-type="BaseTooltip" data-tooltip-type="BaseTooltip"
:data-tooltip-content="$t('warnings.TWR')" :data-tooltip-content="$t('warnings.TWR')"
> >
@@ -156,7 +156,7 @@
v-if="extended && train.timetableData && train.timetableData.warningNotes" v-if="extended && train.timetableData && train.timetableData.warningNotes"
> >
<div class="dangers-badges"> <div class="dangers-badges">
<div v-if="train.timetableData?.TWR"> <div v-if="train.timetableData?.twr">
<div class="train-badge twr">TWR</div> <div class="train-badge twr">TWR</div>
- {{ $t('warnings.TWR') }} - {{ $t('warnings.TWR') }}
</div> </div>
+25 -1
View File
@@ -1,4 +1,28 @@
{ {
"welcome": {
"title": "Welcome to Stacjownik!",
"app-desc": "{b1} is a web tool made for {link}, which main goal is to assist in-game dispatchers and drivers on their duties. Here you can find who is currently online and on what scenery, find a train driver or browse the journal, which contains a history of past timetables and dispatcher duty.",
"app-desc-b1": "Stacjownik",
"sceneries-header": "Sceneries",
"sceneries-desc": "Under the {b1} tab, you will find information about the dispatchers and sceneries they currently occupy. You can also browse all available sceneries in the simulator (in the filters, check the “Free” option to show the rest of the unoccupied ones) and filter them by various aspects, such as control types, additional software, signaling types, required duty level or types of routes. Click on the corresponding scenery in the table to show its details.",
"sceneries-desc-b1": "Sceneries",
"trains-header": "Trains",
"trains-desc": "The {b1} tab contains a list of active drivers and timetables that are currently realized on the selected server (server selection is at the top of the page, next to the counters). The list can be filtered and sorted, taking into account the most important criteria, such as driver name, train number or timetable details. You can also click on the train to show additional information.",
"trains-desc-b1": "Trains",
"journal-header": "Journal",
"journal-desc": "The {b1} is a tab where you can find dispatcher duty and timetables history (currently only from the main PL1 game server). You can also search for a particular player's history using additional filters.",
"journal-desc-b1": "Journal",
"other-apps": "Also check out other apps designed to make TD2 gameplay easier:",
"pojazdownik-desc": "online rolling stock editor",
"generator-desc": "graphical manager of train orders",
"srjp-desc": "Polish working train timetable",
"donation-info": "If you appreciate the Stacjownik project as well as other applications, please consider supporting it financially - click the button with {icon1} to learn more!",
"donation-info-icon1-text": "the diamond icon",
"discord-info": "I also invite you to the official {discord}, where you will find a dedicated Stacjobot, with which you can search for additional data from the simulator, unavailable on this site!",
"discord-info-link-text": "Stacjownik Discord server",
"bottom-text": "Enjoy!\n~Spythere",
"button-confirm": "Start using the app!"
},
"donations": { "donations": {
"button-title": "TOSS A COIN", "button-title": "TOSS A COIN",
"header": "Toss a coin to Stacjownik!", "header": "Toss a coin to Stacjownik!",
@@ -527,7 +551,7 @@
"lines-title": "Real lines", "lines-title": "Real lines",
"project-title": "Project name", "project-title": "Project name",
"additional-tools-title": "Additional tools", "additional-tools-title": "Additional tools",
"one-way-routes": "Signle track routes", "one-way-routes": "Single track routes",
"two-way-routes": "Double track routes", "two-way-routes": "Double track routes",
"no-data": "No available data about this scenery", "no-data": "No available data about this scenery",
"option-active-timetables": "Active timetables", "option-active-timetables": "Active timetables",
+25 -1
View File
@@ -1,4 +1,28 @@
{ {
"welcome": {
"title": "Witaj na Stacjowniku!",
"app-desc": "{b1} to aplikacja stworzona dla symulatora {link}, której celem jest wspomaganie dyżurnego ruchu i maszynisty. Możesz sprawdzić tutaj kto i na jakiej scenerii obecnie pełni służbę, znaleźć maszynistę lub przejrzeć dziennik, który zawiera historię przeszłych dyżurów i rozkładów jazdy.",
"app-desc-b1": "Stacjownik",
"sceneries-header": "Scenerie",
"sceneries-desc": "W zakładce {b1} znajdziesz informacje o dyżurnych ruchu pełniących służby na wybranych sceneriach. Możesz również przeglądać wszystkie dostępne scenerie w symulatorze (w filtrach należy zaznaczyć opcję \"Wolna\", aby pokazać resztę niezajętych) i filtrować je pod względem wielu kryteriów, takich jak sterowanie, dodatkowe oprogramowanie, sygnalizacja, wymagany poziom dyżurnego czy szlaki. Aby wyświetlić detale danej scenerii, kliknij na nią w tabelce.",
"sceneries-desc-b1": "Scenerie",
"trains-header": "Pociągi",
"trains-desc": " Zakładka {b1} zawiera listę aktywnych maszynistów i rozkładów jazdy, które są obecnie realizowane na wybranym serwerze (wybór serwera znajduje się na górze strony). Listę można filtrować i sortować uwzględniając najważniejsze kryteria, takie jak nazwa maszynisty, numer pociągu lub detale rozkładu jazdy. Możesz również kliknąć na dany pociąg, aby wyświetlić dodatkowe informacje.",
"trains-desc-b1": "Pociągi",
"journal-header": "Dziennik",
"journal-desc": "{b1} to zakładka, w której znajdziesz historię dyżurów i rozkładów jazdy, obecnie jedynie z głównego serwera rozgrywki PL1. Możesz także wyszukać historię danego użytkownika używając dodatkowych filtrów.",
"journal-desc-b1": "Dziennik",
"other-apps": "Sprawdź także inne aplikacje stworzone z myślą ułatwienia rozgrywki w TD2:",
"pojazdownik-desc": "edytor składów online",
"generator-desc": "graficzny menadżer rozkazów pisemnych",
"srjp-desc": "służbowy rozkład jazdy pociągu",
"donation-info": "Jeśli doceniasz projekt Stacjownika jak i inne aplikacje mojego autorstwa, rozważ jego wsparcie finansowe - kliknij przycisk z {icon1}, aby dowiedzieć się więcej!",
"donation-info-icon1-text": "ikoną diamentu",
"discord-info": "Zapraszam także na oficjalnego {discord}, gdzie znajdziesz dedykowanego Stacjobota, za pomocą którego możesz wyszukiwać dodatkowe dane z symulatora niedostępne na tej stronie",
"discord-info-link-text": "Discorda Stacjownika",
"bottom-text": "Miłego korzystania\n~Spythere",
"button-confirm": "Zacznij korzystać z aplikacji!"
},
"donations": { "donations": {
"button-title": "GROSZA DAJ", "button-title": "GROSZA DAJ",
"header": "Grosza daj Stacjownikowi!", "header": "Grosza daj Stacjownikowi!",
@@ -566,4 +590,4 @@
"history": { "history": {
"title": "DZIENNIK ROZKŁADÓW JAZDY" "title": "DZIENNIK ROZKŁADÓW JAZDY"
} }
} }
+2 -2
View File
@@ -43,7 +43,7 @@ function filterTrainList(
return train.timetableData?.followingStops.some((stop) => stop.comments); return train.timetableData?.followingStops.some((stop) => stop.comments);
case TrainFilterId.twr: case TrainFilterId.twr:
return !train.timetableData?.TWR; return !train.timetableData?.twr;
case TrainFilterId.pn: case TrainFilterId.pn:
return !train.timetableData?.hasExtraDeliveries; return !train.timetableData?.hasExtraDeliveries;
@@ -52,7 +52,7 @@ function filterTrainList(
return !train.timetableData?.hasDangerousCargo; return !train.timetableData?.hasDangerousCargo;
case TrainFilterId.common: case TrainFilterId.common:
return train.timetableData?.SKR || train.timetableData?.TWR; return train.timetableData?.twr;
case TrainFilterId.passenger: case TrainFilterId.passenger:
return !/^[AMRE]\D{2}$/.test(train.timetableData?.category || ''); return !/^[AMRE]\D{2}$/.test(train.timetableData?.category || '');
+3 -3
View File
@@ -32,7 +32,8 @@ export const useMainStore = defineStore('mainStore', {
chosenModalTrainId: undefined, chosenModalTrainId: undefined,
modalLastClickedTarget: null modalLastClickedTarget: null,
currentLocale: 'pl'
}) as MainStoreState, }) as MainStoreState,
getters: { getters: {
@@ -97,8 +98,7 @@ export const useMainStore = defineStore('mainStore', {
followingStops: timetable.stopList, followingStops: timetable.stopList,
routeDistance: timetable.stopList[timetable.stopList.length - 1].stopDistance, routeDistance: timetable.stopList[timetable.stopList.length - 1].stopDistance,
sceneries: timetable.sceneries, sceneries: timetable.sceneries,
TWR: timetable.TWR, twr: timetable.twr,
SKR: timetable.SKR,
warningNotes: timetable.warningNotes, warningNotes: timetable.warningNotes,
hasDangerousCargo: timetable.hasDangerousCargo, hasDangerousCargo: timetable.hasDangerousCargo,
hasExtraDeliveries: timetable.hasExtraDeliveries, hasExtraDeliveries: timetable.hasExtraDeliveries,
+1
View File
@@ -12,6 +12,7 @@ export interface MainStoreState {
driverStatsStatus: Status.Data; driverStatsStatus: Status.Data;
chosenModalTrainId?: string; chosenModalTrainId?: string;
modalLastClickedTarget: EventTarget | null; modalLastClickedTarget: EventTarget | null;
currentLocale: string;
} }
export interface StationJSONData { export interface StationJSONData {
+14 -2
View File
@@ -23,7 +23,6 @@
--clr-donator: #f7a4ff; --clr-donator: #f7a4ff;
--no-scroll-padding: 17px; --no-scroll-padding: 17px;
--max-container-width: 1700px; --max-container-width: 1700px;
@@ -207,10 +206,23 @@ ul {
background: linear-gradient(90deg, #ff88db 30%, #ffffff 70%); background: linear-gradient(90deg, #ff88db 30%, #ffffff 70%);
background-clip: text; background-clip: text;
-webkit-background-clip: text; -webkit-background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
text-shadow: #f050ff 0 0 10px; text-shadow: #f050ff 0 0 10px;
} }
&--discord {
color: var(--clr-donator);
color: transparent;
background: var(--clr-donator);
background: linear-gradient(90deg, #7fb6ff 0%, #60ecff 70%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: #88ddff 0 0 10px;
}
} }
button, button,
+1 -2
View File
@@ -197,8 +197,7 @@ export namespace API {
category: string; category: string;
route: string; route: string;
stopList: TimetableStop[]; stopList: TimetableStop[];
TWR: boolean; twr: boolean;
SKR: boolean;
hasDangerousCargo: boolean; hasDangerousCargo: boolean;
hasExtraDeliveries: boolean; hasExtraDeliveries: boolean;
warningNotes: string | null; warningNotes: string | null;
+1 -2
View File
@@ -83,8 +83,7 @@ export interface TrainTimetableData {
category: string; category: string;
route: string; route: string;
followingStops: TrainStop[]; followingStops: TrainStop[];
TWR: boolean; twr: boolean;
SKR: boolean;
hasDangerousCargo: boolean; hasDangerousCargo: boolean;
hasExtraDeliveries: boolean; hasExtraDeliveries: boolean;
warningNotes: string | null; warningNotes: string | null;