Compare commits

..

11 Commits

Author SHA1 Message Date
Spythere a34eef098b Merge pull request #158 from Spythere/development
v1.33.0
2026-03-22 23:17:43 +01:00
Spythere 97a829a21c Merge pull request #156 from Spythere/development
v1.32.0 hotfixes
2026-02-27 00:32:11 +01:00
Spythere 83444f64d0 Merge pull request #155 from Spythere/development
v1.32.0
2026-02-26 21:15:04 +01:00
Spythere c2f7eef146 Merge pull request #153 from Spythere/development
v1.31.1
2026-01-16 22:27:16 +01:00
Spythere 3c78af4dc0 Merge pull request #151 from Spythere/development
v1.31.0
2026-01-10 21:22:48 +01:00
Spythere fc7a9be9dd Merge pull request #150 from Spythere/development
Fix for station statistics dropdown overflow
2025-12-13 02:21:19 +01:00
Spythere 3c3a114a38 Merge pull request #149 from Spythere/development
Information about migration to the new domain
2025-12-13 00:20:13 +01:00
Spythere fe6972c1f8 Merge pull request #148 from Spythere/development
Extended isChristmas check from 20th to 6th December
2025-12-05 21:28:04 +01:00
Spythere 08b9b72dcd Merge pull request #147 from Spythere/development
Hotfix for VPS deploy
2025-12-04 00:27:38 +01:00
Spythere c90be042e7 Merge pull request #146 from Spythere/development
Updated GitHub workflow for deploying files to dedicated VPS
2025-12-04 00:21:41 +01:00
Spythere 430a05ab38 Merge pull request #145 from Spythere/development
v1.30.7
2025-11-28 01:14:13 +01:00
61 changed files with 2003 additions and 2636 deletions
+2 -1
View File
@@ -15,12 +15,13 @@ pnpm-debug.log*
# Editor directories and files # Editor directories and files
.idea .idea
.vscode
*.suo *.suo
*.ntvs* *.ntvs*
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
.vscode/settings.json node_modules
*.log *.log
-7
View File
@@ -1,7 +0,0 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"
}
-3
View File
@@ -1,3 +0,0 @@
{
"recommendations": ["Vue.volar", "esbenp.prettier-vscode"]
}
Vendored
-1
View File
@@ -1 +0,0 @@
/// <reference types="vite/client" />
+3 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "stacjownik", "name": "stacjownik",
"version": "1.35.0", "version": "1.33.0",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -26,12 +26,12 @@
"vue-router": "^4.4.0" "vue-router": "^4.4.0"
}, },
"devDependencies": { "devDependencies": {
"@tsconfig/node24": "^24.0.4", "@types/node": "^24.3.1",
"@types/node": "^24.12.0",
"@types/showdown": "^2.0.6", "@types/showdown": "^2.0.6",
"@vite-pwa/assets-generator": "^1.0.0", "@vite-pwa/assets-generator": "^1.0.0",
"@vitejs/plugin-vue": "^6.0.1", "@vitejs/plugin-vue": "^6.0.1",
"@vue/tsconfig": "^0.8.1", "@vue/tsconfig": "^0.8.1",
"axios": "^1.9.0",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"typescript": "^5.5.4", "typescript": "^5.5.4",
"vite": "^7.1.4", "vite": "^7.1.4",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

+6 -9
View File
@@ -30,6 +30,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import axios from 'axios';
import { version } from '../package.json'; import { version } from '../package.json';
import { Status } from './typings/common'; import { Status } from './typings/common';
@@ -113,15 +114,11 @@ export default defineComponent({
} }
try { try {
const response = await fetch( const releaseData = await (
'https://api.github.com/repos/Spythere/stacjownik/releases/latest' await axios.get('https://api.github.com/repos/Spythere/stacjownik/releases/latest')
); ).data;
if (!response.ok) { if (!releaseData) return;
throw new Error('Failed to fetch release data from repository!');
}
const releaseData = await response.json();
this.store.appUpdate = { this.store.appUpdate = {
version, version,
@@ -133,7 +130,7 @@ export default defineComponent({
(storageVersion != '' && storageVersion != version && this.isOnProductionHost) || (storageVersion != '' && storageVersion != version && this.isOnProductionHost) ||
import.meta.env.VITE_UPDATE_TEST === 'test'; import.meta.env.VITE_UPDATE_TEST === 'test';
} catch (error) { } catch (error) {
console.error(error); console.error(`Wystąpił błąd podczas pobierania danych z API GitHuba: ${error}`);
} }
StorageManager.setStringValue(STORAGE_VERSION_KEY, version); StorageManager.setStringValue(STORAGE_VERSION_KEY, version);
+3 -19
View File
@@ -1,9 +1,7 @@
<template> <template>
<Card :is-open="isUpdateCardOpen" @toggle-card="toggleCard(false)"> <Card :is-open="isUpdateCardOpen" @toggle-card="toggleCard(false)">
<div class="content" tabindex="0" ref="content"> <div class="content" tabindex="0" ref="content">
<h1 class="content-title"> <h1 class="content-title"><i class="fa-solid fa-wand-sparkles"></i> {{ $t('update.title') }}</h1>
<i class="fa-solid fa-wand-sparkles"></i> {{ $t('update.title') }}
</h1>
<div class="features-body" v-if="htmlChangelog != ''" v-html="htmlChangelog"></div> <div class="features-body" v-if="htmlChangelog != ''" v-html="htmlChangelog"></div>
<div class="no-features" v-else>{{ $t('update.no-data') }}</div> <div class="no-features" v-else>{{ $t('update.no-data') }}</div>
@@ -89,27 +87,13 @@ export default defineComponent({
::v-deep(h2) { ::v-deep(h2) {
padding: 0.5em 0; padding: 0.5em 0;
border-bottom: 1px solid #aaa;
&::after {
content: '';
display: block;
height: 2px;
width: 100%;
background-color: #aaa;
margin-top: 0.25em;
}
}
::v-deep(h3) {
padding-bottom: 0.25em;
} }
::v-deep(ul) { ::v-deep(ul) {
list-style: disc; list-style: disc;
padding: 0.5em 1.5em;
line-height: 1.5em; line-height: 1.5em;
padding: 0 1.5em;
padding-bottom: 0.5em;
} }
.content { .content {
@@ -1,325 +0,0 @@
<template>
<div class="driver-propositions">
<h3>{{ t('trains.number-propositions-header') }}</h3>
<div class="categories-select">
<button
v-for="(category, i) in availableCategories"
class="btn btn--option btn--action"
@click="selectCategory(i)"
:class="{ checked: i == chosenCategoryIndex }"
>
{{ category }}
</button>
</div>
<div v-if="numberPropositions.length > 0" class="propositions-numbers">
<div v-if="chosenCategory">
<b>{{ chosenCategory }} </b> -
{{ t(`categories.${chosenCategory.slice(0, 2)}`) }}
({{ t(`categories.${chosenCategory.slice(2)}`) }})
</div>
<div v-if="chosenCategoryRules">
<span v-if="chosenCategoryRules[0]"
>{{ t('trains.number-propositions-third-number') }}
<b class="text--primary">{{ chosenCategoryRules[0] }}</b> &bull;
</span>
<span
>{{
t('trains.number-propositions-last-nums', {
count: chosenCategoryRules[1].length
})
}}
<b class="text--primary">{{ chosenCategoryRules[1] }}</b> -
<b class="text--primary">{{ chosenCategoryRules[2] }}</b></span
>
</div>
<div style="margin-top: 0.5em">
<b>{{ t('trains.number-propositions-title') }}&nbsp;</b>
<i>{{ numberPropositions.join(', ') }}</i>
</div>
</div>
<div class="no-propositions" v-else>{{ t('trains.number-propositions-empty') }}</div>
<div class="cargo-warnings" v-if="getCargoWarnings.size > 0">
<hr />
<h3>{{ t('cargo-warnings.title') }}</h3>
<div class="warnings-container">
<div
v-for="warning in getCargoWarnings"
class="train-badge"
:class="`${warning.split('-')[0]}`"
>
{{ t('cargo-warnings.' + warning) }}
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, PropType, watch, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { Train } from '../../typings/common';
import rulesJSON from '../../data/trainNumberRules.json';
import { useApiStore } from '../../store/apiStore';
const { t } = useI18n();
const apiStore = useApiStore();
const props = defineProps({
chosenTrain: {
type: Object as PropType<Train>,
required: true
}
});
const emits = defineEmits(['selectCategory']);
const chosenCategoryIndex = ref(0);
const numberPropositions = ref<string[]>([]);
const chosenCategoryRules = ref<any[]>([]);
watch(
computed(() => props.chosenTrain.trainNo),
() => {
chosenCategoryIndex.value = 0;
generateNumberPropositions();
}
);
onMounted(() => {
generateNumberPropositions();
});
function generateNumberPropositions() {
const categoryCode = chosenCategory.value?.slice(0, 2);
const trainNoStr = props.chosenTrain.trainNo.toString();
// Get category rules
const rules = categoryCode
? ((rulesJSON.categoriesRules as any)[categoryCode] as any[])
: undefined;
if (!categoryCode || !rules) {
numberPropositions.value.length = 0;
chosenCategoryRules.value.length = 0;
return;
}
const [thirdNumber, minRange, maxRange] = rules;
const propositionsArr: string[] = [];
for (let i = 0; i < 5; i++) {
let generatedNumStr = '';
generatedNumStr += trainNoStr[0] ?? Math.floor(Math.random() * 10);
generatedNumStr += trainNoStr[1] ?? Math.floor(Math.random() * 10);
// Third number
generatedNumStr += thirdNumber ?? '';
// Remaining numbers
const rangeNums = minRange?.length ?? 3;
const randRange = Math.floor(
Math.random() * (Number(maxRange) - Number(minRange)) + Number(minRange)
).toString();
const leadingZeros = new Array(Math.abs(randRange.toString().length - rangeNums))
.fill('0')
.join('');
generatedNumStr += `${leadingZeros}${randRange}`;
const isNumberTaken =
apiStore.activeData?.trains?.some((t) => t.trainNo.toString() == generatedNumStr) ?? false;
if (!isNumberTaken) {
propositionsArr.push(generatedNumStr);
} else {
i--;
}
if (Number(randRange) > Number(maxRange)) break;
}
numberPropositions.value = propositionsArr;
chosenCategoryRules.value = rules;
}
const chosenCategory = computed(() => {
return availableCategories.value[chosenCategoryIndex.value];
});
const getCargoWarnings = computed(() => {
const stockList = props.chosenTrain.stockList;
let warnings: Set<string> = new Set();
stockList.forEach((stockVehicle) => {
const [vehicleName, vehicleCargo] = stockVehicle.split(':');
if (vehicleName.startsWith('WB117')) warnings.add(vehicleCargo ? 'twr-un1965' : 'tn-un1965');
else if (vehicleName.startsWith('445Rb'))
warnings.add(vehicleCargo ? 'tn-un1202' : 'tn-un1202-empty');
else if (vehicleName.startsWith('EDK80')) warnings.add('pn-edk80');
if (vehicleCargo) {
if (vehicleCargo.startsWith('wt_20')) warnings.add('pn-innofreight');
else if (/^(tank|vehicles_01|truck)/.test(vehicleCargo)) warnings.add('pn-military');
}
});
return warnings;
});
const availableCategories = computed(() => {
const stockList = props.chosenTrain.stockList;
const headVehicle = stockList[0]?.split('-')[0] ?? '';
let availableCategories: string[] = [];
let categoryTraction = 'E';
let vehicleTypesSet = new Set<string>();
let wagonsNamesSet = new Set<string>();
let cargoNamesSet = new Set<string>();
for (const stockName of stockList) {
const [vehicleName, ...cargoList] = stockName.split(':');
const vehicleData = apiStore.vehiclesData?.vehicles.find((v) => v.name == vehicleName);
if (!vehicleData) continue;
vehicleTypesSet.add(vehicleData.type);
if (vehicleData.type.startsWith('wagon-')) wagonsNamesSet.add(vehicleData.name.split('_')[0]);
if (cargoList !== undefined) cargoList.forEach((c) => cargoNamesSet.add(c.split('_')[0]));
}
let vehicleTypesArr = [...vehicleTypesSet];
let wagonsNamesArr = [...wagonsNamesSet];
// Traction
if (vehicleTypesArr[0] == 'loco-electric') categoryTraction = 'E';
else if (vehicleTypesArr[0] == 'loco-diesel') categoryTraction = 'S';
else if (vehicleTypesArr[0] == 'unit-electric') categoryTraction = 'J';
else categoryTraction = 'M';
// EMU / DMU - M*, R*, P*
if (vehicleTypesArr.length == 1 && (categoryTraction == 'J' || categoryTraction == 'M')) {
availableCategories.push('MO', 'MP', 'MM', 'RO', 'RP', 'RA', 'RM', 'PW');
}
// Only locos (up to 3) - LT, LP, LS
else if (stockList.length <= 3 && vehicleTypesArr.every((v) => v.startsWith('loco-'))) {
if (/^(EU|ET|201E|4E|SU|ST|M62|CTLR4C)/.test(headVehicle)) availableCategories.push('LT');
if (/^(EU|EP|SU|SP)/.test(headVehicle)) availableCategories.push('LP');
if (/^(SM)/.test(headVehicle)) availableCategories.push('LS');
}
// Only locos (more than 3) - TH
else if (stockList.length > 3 && vehicleTypesArr.every((v) => v.startsWith('loco-'))) {
availableCategories.push('TH');
}
// Loco(s) + passenger only wagons - M*, R*, E*, P*
else if (vehicleTypesArr.every((v) => v.startsWith('loco-') || v == 'wagon-passenger')) {
availableCategories.push('EI', 'EC', 'EN', 'MO', 'MP', 'MM', 'RO', 'RP', 'RA', 'RM', 'PW');
}
// Loco(s) + cargo only / mixed wagons - T*, Z*
else {
if (wagonsNamesArr.every((v) => /^(627Z|412Z)/.test(v)))
availableCategories.push('TC', 'TD', 'TS');
else if (stockList.slice(1).every((v) => /PKPE/.test(v))) {
availableCategories.push('ZU', 'ZN');
} else if (wagonsNamesArr.length < 3 || cargoNamesSet.size < 3) {
availableCategories.push('TM', 'TG', 'TS', 'TK');
} else {
availableCategories.push('TN', 'TR', 'TS', 'TK');
}
}
return availableCategories.map((c) => `${c}${categoryTraction}`);
});
function selectCategory(i: number) {
chosenCategoryIndex.value = i;
generateNumberPropositions();
}
</script>
<style lang="scss" scoped>
@use '../../styles/responsive';
@use '../../styles/badge';
.driver-propositions {
margin-bottom: 1em;
padding: 0.5em;
background-color: #111;
}
.categories-select {
display: inline-flex;
flex-wrap: wrap;
gap: 0.5em;
margin-top: 0.5em;
position: relative;
&::after {
content: '';
position: absolute;
bottom: calc(-0.5em);
left: 0;
width: 100%;
height: 2px;
background-color: #aaa;
}
}
.propositions-numbers {
margin-top: 1em;
}
.no-propositions {
margin-top: 1em;
color: #ccc;
}
.cargo-warnings {
margin-top: 0.5em;
h3 {
margin: 0.5em 0;
}
}
.warnings-container {
display: flex;
flex-wrap: wrap;
gap: 0.5em;
}
@include responsive.smallScreen {
.driver-propositions {
text-align: center;
}
.categories-select {
justify-content: center;
}
.warnings-container {
justify-content: center;
}
}
</style>
+244 -7
View File
@@ -4,18 +4,63 @@
<!-- Train action buttons --> <!-- Train action buttons -->
<div class="train-stock-actions"> <div class="train-stock-actions">
<button class="btn btn--action" @click="copyStockToClipboard()"> <button class="btn btn--action" style="margin: 1em 0" @click="copyStockToClipboard()">
<i class="fa-regular fa-copy"></i> {{ i18n.t('trains.stock-copy') }} <i class="fa-regular fa-copy"></i> {{ i18n.t('trains.stock-copy') }}
</button> </button>
<button class="btn btn--action" @click="toggleNumberPropositions()"> <button class="btn btn--action" style="margin: 1em 0" @click="toggleNumberPropositions()">
<i class="fa-regular fa-lightbulb"></i> {{ i18n.t('trains.number-propositions') }} <i class="fa-regular fa-lightbulb"></i> {{ i18n.t('trains.number-propositions') }}
</button> </button>
</div> </div>
<!-- Proposed numbers container --> <!-- Proposed numbers container -->
<transition name="view-anim"> <transition name="view-anim" class="propositions-container">
<DriverPropositions :chosenTrain="chosenTrain" v-if="arePropositionsVisible" /> <div v-if="arePropositionsVisible">
<h3 style="margin-bottom: 0.5em">{{ i18n.t('trains.number-propositions-header') }}</h3>
<div class="categories-select">
<button
v-for="(category, i) in availableCategories"
class="btn btn--option btn--action"
@click="selectCategory(i)"
:class="{ checked: i == chosenCategoryIndex }"
>
{{ category }}
</button>
</div>
<div v-if="numberPropositions.length > 0" class="propositions-numbers">
<div v-if="chosenCategory">
<b>{{ chosenCategory }} </b> -
{{ i18n.t(`categories.${chosenCategory.slice(0, 2)}`) }}
({{ i18n.t(`categories.${chosenCategory.slice(2)}`) }})
</div>
<div v-if="chosenCategoryRules">
<span v-if="chosenCategoryRules[0]"
>{{ i18n.t('trains.number-propositions-third-number') }}
<b class="text--primary">{{ chosenCategoryRules[0] }}</b> &bull;
</span>
<span
>{{
i18n.t('trains.number-propositions-last-nums', {
count: chosenCategoryRules[1].length
})
}}
<b class="text--primary">{{ chosenCategoryRules[1] }}</b> -
<b class="text--primary">{{ chosenCategoryRules[2] }}</b></span
>
</div>
<div style="margin-top: 0.5em">
<b>{{ i18n.t('trains.number-propositions-title') }}&nbsp;</b>
<i>{{ numberPropositions.join(', ') }}</i>
</div>
</div>
<div class="no-propositions" v-else>{{ i18n.t('trains.number-propositions-empty') }}</div>
</div>
</transition> </transition>
<StockList :trainStockList="chosenTrain.stockList" :key="chosenTrain.id" :showPreviews="true" /> <StockList :trainStockList="chosenTrain.stockList" :key="chosenTrain.id" :showPreviews="true" />
@@ -27,15 +72,25 @@
import { PropType, ref } from 'vue'; import { PropType, ref } from 'vue';
import { Train } from '../../typings/common'; import { Train } from '../../typings/common';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useApiStore } from '../../store/apiStore';
import StockList from '../Global/StockList.vue'; import StockList from '../Global/StockList.vue';
import TrainSchedule from '../TrainsView/TrainSchedule.vue'; import TrainSchedule from '../TrainsView/TrainSchedule.vue';
import TrainInfo from '../TrainsView/TrainInfo.vue'; import TrainInfo from '../TrainsView/TrainInfo.vue';
import DriverPropositions from './DriverPropositions.vue';
import rulesJSON from '../../data/trainNumberRules.json';
import { computed } from 'vue';
import { watch } from 'vue';
const apiStore = useApiStore();
const i18n = useI18n(); const i18n = useI18n();
const arePropositionsVisible = ref(false); const arePropositionsVisible = ref(false);
const chosenCategoryIndex = ref(0);
const numberPropositions = ref<string[]>([]);
const chosenCategoryRules = ref<any[]>([]);
const props = defineProps({ const props = defineProps({
chosenTrain: { chosenTrain: {
@@ -64,7 +119,153 @@ function copyStockToClipboard() {
function toggleNumberPropositions() { function toggleNumberPropositions() {
arePropositionsVisible.value = !arePropositionsVisible.value; arePropositionsVisible.value = !arePropositionsVisible.value;
if (arePropositionsVisible.value) generateNumberPropositions();
} }
function selectCategory(i: number) {
chosenCategoryIndex.value = i;
generateNumberPropositions();
}
function generateNumberPropositions() {
const categoryCode = chosenCategory.value?.slice(0, 2);
const trainNoStr = props.chosenTrain.trainNo.toString();
// Get category rules
const rules = categoryCode
? ((rulesJSON.categoriesRules as any)[categoryCode] as any[])
: undefined;
if (!categoryCode || !rules) {
numberPropositions.value.length = 0;
chosenCategoryRules.value.length = 0;
return;
}
const [thirdNumber, minRange, maxRange] = rules;
const propositionsArr: string[] = [];
for (let i = 0; i < 5; i++) {
let generatedNumStr = '';
generatedNumStr += trainNoStr.at(0) ?? Math.floor(Math.random() * 10);
generatedNumStr += trainNoStr.at(1) ?? Math.floor(Math.random() * 10);
// Third number
generatedNumStr += thirdNumber ?? '';
// Remaining numbers
const rangeNums = minRange?.length ?? 3;
const randRange = Math.floor(
Math.random() * (Number(maxRange) - Number(minRange)) + Number(minRange)
).toString();
const leadingZeros = new Array(Math.abs(randRange.toString().length - rangeNums))
.fill('0')
.join('');
generatedNumStr += `${leadingZeros}${randRange}`;
const isNumberTaken =
apiStore.activeData?.trains?.some((t) => t.trainNo.toString() == generatedNumStr) ?? false;
if (!isNumberTaken) {
propositionsArr.push(generatedNumStr);
} else {
i--;
}
if (Number(randRange) > Number(maxRange)) break;
}
numberPropositions.value = propositionsArr;
chosenCategoryRules.value = rules;
}
const chosenCategory = computed(() => {
return availableCategories.value.at(chosenCategoryIndex.value);
});
const availableCategories = computed(() => {
const stockList = props.chosenTrain.stockList;
const headVehicle = stockList.at(0)?.split('-')[0] ?? '';
let availableCategories: string[] = [];
let categoryTraction = 'E';
let vehicleTypesSet = new Set<string>();
let wagonsNamesSet = new Set<string>();
let cargoNamesSet = new Set<string>();
for (const stockName of stockList) {
const [vehicleName, ...cargoList] = stockName.split(':');
const vehicleData = apiStore.vehiclesData?.vehicles.find((v) => v.name == vehicleName);
if (!vehicleData) continue;
vehicleTypesSet.add(vehicleData.type);
if (vehicleData.type.startsWith('wagon-')) wagonsNamesSet.add(vehicleData.name.split('_')[0]);
if (cargoList !== undefined) cargoList.forEach((c) => cargoNamesSet.add(c.split('_')[0]));
}
let vehicleTypesArr = [...vehicleTypesSet];
let wagonsNamesArr = [...wagonsNamesSet];
// Traction
if (vehicleTypesArr[0] == 'loco-electric') categoryTraction = 'E';
else if (vehicleTypesArr[0] == 'loco-diesel') categoryTraction = 'S';
else if (vehicleTypesArr[0] == 'unit-electric') categoryTraction = 'J';
else categoryTraction = 'M';
// EMU / DMU - M*, R*, P*
if (vehicleTypesArr.length == 1 && (categoryTraction == 'J' || categoryTraction == 'M')) {
availableCategories.push('MO', 'MP', 'MM', 'RO', 'RP', 'RA', 'RM', 'PW');
}
// Only locos (up to 3) - LT, LP, LS
else if (stockList.length <= 3 && vehicleTypesArr.every((v) => v.startsWith('loco-'))) {
if (/^(EU|ET|201E|4E|SU|ST|M62|CTLR4C)/.test(headVehicle)) availableCategories.push('LT');
if (/^(EU|EP|SU|SP)/.test(headVehicle)) availableCategories.push('LP');
if (/^(SM)/.test(headVehicle)) availableCategories.push('LS');
}
// Only locos (more than 3) - TH
else if (stockList.length > 3 && vehicleTypesArr.every((v) => v.startsWith('loco-'))) {
availableCategories.push('TH');
}
// Loco(s) + passenger only wagons - M*, R*, E*, P*
else if (vehicleTypesArr.every((v) => v.startsWith('loco-') || v == 'wagon-passenger')) {
availableCategories.push('EI', 'EC', 'EN', 'MO', 'MP', 'MM', 'RO', 'RP', 'RA', 'RM', 'PW');
}
// Loco(s) + cargo only / mixed wagons - T*, Z*
else {
if (wagonsNamesArr.every((v) => /^(627Z|412Z)/.test(v)))
availableCategories.push('TC', 'TD', 'TS');
else if (stockList.slice(1).every((v) => /PKPE/.test(v))) {
availableCategories.push('ZU', 'ZN');
} else if (wagonsNamesArr.length < 3 || cargoNamesSet.size < 3) {
availableCategories.push('TM', 'TG', 'TS', 'TK');
} else {
availableCategories.push('TN', 'TR', 'TS', 'TK');
}
}
return availableCategories.map((c) => `${c}${categoryTraction}`);
});
watch(
computed(() => `${props.chosenTrain.trainNo}`),
() => {
chosenCategoryIndex.value = 0;
generateNumberPropositions();
}
);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -78,13 +279,49 @@ function toggleNumberPropositions() {
.train-stock-actions { .train-stock-actions {
display: flex; display: flex;
gap: 0.5em;
}
.propositions-container {
margin-bottom: 1em;
padding: 0.5em;
background-color: #111;
}
.categories-select {
display: inline-flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.5em; gap: 0.5em;
margin: 1em 0;
position: relative;
&::after {
content: '';
position: absolute;
bottom: calc(-0.5em);
left: 0;
width: 100%;
height: 2px;
background-color: #aaa;
}
}
.propositions-numbers {
margin-top: 1em;
}
.no-propositions {
margin-top: 1em;
color: #ccc;
} }
@include responsive.smallScreen { @include responsive.smallScreen {
.train-stock-actions { .propositions-container {
text-align: center;
}
.categories-select {
justify-content: center; justify-content: center;
} }
} }
+1 -3
View File
@@ -8,7 +8,6 @@
:images="images" :images="images"
:image-fallbacks="imagesFallbacks" :image-fallbacks="imagesFallbacks"
:show-previews="showPreviews" :show-previews="showPreviews"
:thumbnail-size="thumbnailSize"
/> />
</li> </li>
</ul> </ul>
@@ -26,8 +25,7 @@ export default defineComponent({
props: { props: {
trainStockList: { type: Array as PropType<string[]>, required: true }, trainStockList: { type: Array as PropType<string[]>, required: true },
tractionOnly: { type: Boolean, required: false }, tractionOnly: { type: Boolean, required: false },
showPreviews: { type: Boolean }, showPreviews: { type: Boolean }
thumbnailSize: { type: Number }
}, },
data() { data() {
+3 -4
View File
@@ -9,7 +9,7 @@
<img <img
v-for="(thumbnailImage, imageIndex) in images" v-for="(thumbnailImage, imageIndex) in images"
:src="`https://stacjownik.spythere.eu/static/thumbnails/${thumbnailImage}.png`" :src="`https://stacjownik.spythere.eu/static/thumbnails/${thumbnailImage}.png`"
:height="thumbnailSize || 70" height="70"
loading="lazy" loading="lazy"
:data-crosshair-cursor="showPreviews" :data-crosshair-cursor="showPreviews"
:data-tooltip-type="showPreviews ? 'VehiclePreviewTooltip' : ''" :data-tooltip-type="showPreviews ? 'VehiclePreviewTooltip' : ''"
@@ -28,8 +28,7 @@ const props = defineProps({
vehicleString: { type: String, required: true }, vehicleString: { type: String, required: true },
images: { type: Object as PropType<string[]>, required: true }, images: { type: Object as PropType<string[]>, required: true },
imageFallbacks: { type: Object as PropType<string[]>, required: true }, imageFallbacks: { type: Object as PropType<string[]>, required: true },
showPreviews: { type: Boolean }, showPreviews: { type: Boolean }
thumbnailSize: { type: Number }
}); });
const thumbRef = ref(null) as Ref<HTMLElement | null>; const thumbRef = ref(null) as Ref<HTMLElement | null>;
@@ -68,7 +67,7 @@ function onImageLoad() {
max-width: 90%; max-width: 90%;
text-align: center; text-align: center;
color: #aaa; color: #aaa;
font-size: 0.8em; font-size: 0.85em;
margin: 0 auto; margin: 0 auto;
padding: 0.25em 0; padding: 0.25em 0;
} }
@@ -18,20 +18,7 @@
</b> </b>
<span <span
v-if="isCreator(entry.dispatcherName)" v-if="apiStore.donatorsData.includes(entry.dispatcherName)"
data-tooltip-type="CreatorTooltip"
:data-tooltip-content="$t('donations.creator-message')"
>
<router-link
class="text--creator"
:to="`/journal/dispatchers?search-dispatcher=${entry.dispatcherName}`"
>
{{ entry.dispatcherName }}
</router-link>
</span>
<span
v-else-if="apiStore.donatorsData.includes(entry.dispatcherName)"
data-tooltip-type="DonatorTooltip" data-tooltip-type="DonatorTooltip"
:data-tooltip-content="$t('donations.dispatcher-message')" :data-tooltip-content="$t('donations.dispatcher-message')"
> >
@@ -135,7 +122,6 @@ import styleMixin from '../../../mixins/styleMixin';
import { useApiStore } from '../../../store/apiStore'; import { useApiStore } from '../../../store/apiStore';
import StationStatusBadge from '../../Global/StationStatusBadge.vue'; import StationStatusBadge from '../../Global/StationStatusBadge.vue';
import FlagIcon from '../../Global/FlagIcon.vue'; import FlagIcon from '../../Global/FlagIcon.vue';
import { isCreator } from '../../../utils/userUtils';
export default defineComponent({ export default defineComponent({
props: { props: {
@@ -148,7 +134,7 @@ export default defineComponent({
emits: ['toggleShowExtraInfo'], emits: ['toggleShowExtraInfo'],
data() { data() {
return { regions, apiStore: useApiStore(), isCreator }; return { regions, apiStore: useApiStore() };
}, },
methods: { methods: {
+4 -11
View File
@@ -269,9 +269,9 @@ export default defineComponent({
this.searchTimeout = window.setTimeout(async () => { this.searchTimeout = window.setTimeout(async () => {
try { try {
const suggestions: string[] = await this.apiStore.client.get( const suggestions: string[] = await (
`api/get${type}Suggestions?name=${value}` await this.apiStore.client!.get(`api/get${type}Suggestions?name=${value}`)
); ).data;
this[`${type}Suggestions`] = suggestions; this[`${type}Suggestions`] = suggestions;
} catch (error) { } catch (error) {
@@ -336,17 +336,10 @@ export default defineComponent({
display: grid; display: grid;
grid-template-rows: 1fr auto; grid-template-rows: 1fr auto;
overflow: hidden; overflow: hidden;
max-height: calc(100% - 4.5em); max-height: 530px;
top: 3.5em;
padding: 1em 0;
} }
.options_content { .options_content {
overflow: auto; overflow: auto;
padding: 0 1em;
}
.options_actions {
padding: 0 1em;
} }
</style> </style>
@@ -64,18 +64,10 @@ function navigateToProfile() {
<style lang="scss" scoped> <style lang="scss" scoped>
@use '../../styles/dropdown'; @use '../../styles/dropdown';
@use '../../styles/dropdown-filters'; @use '../../styles/dropdown-filters';
@use '../../styles/responsive';
.dropdown_wrapper { .dropdown_wrapper {
left: auto; left: auto;
right: 0; right: 0;
max-width: 700px; max-width: 700px;
top: 3.5em;
}
@include responsive.smallScreen {
.dropdown_wrapper {
top: 6.25em;
}
} }
</style> </style>
@@ -201,20 +201,22 @@ const driverRouteLocation = computed<RouteLocationRaw | null>(() => {
async function fetchTimetableDetails() { async function fetchTimetableDetails() {
try { try {
const responseData = await apiStore.client.get<API.TimetableHistory.Response>( const responseData = await apiStore.client!.get<API.TimetableHistory.Response>(
'api/getTimetables', 'api/getTimetables',
{ {
params: {
timetableId: props.timetableEntry.id, timetableId: props.timetableEntry.id,
returnType: 'detailed' returnType: 'detailed'
} }
}
); );
if (!responseData || responseData.length != 1) { if (!responseData || responseData.data.length != 1) {
timetableDetails.value = null; timetableDetails.value = null;
return; return;
} }
timetableDetails.value = responseData[0]; timetableDetails.value = responseData.data[0];
} catch (error) { } catch (error) {
// this.dataStatus = Status.Data.Error; // this.dataStatus = Status.Data.Error;
console.error(error); console.error(error);
@@ -59,17 +59,7 @@
</strong> </strong>
<router-link <router-link
v-if="isCreator(timetable.driverName)" v-if="apiStore.donatorsData.includes(timetable.driverName)"
class="text--creator"
data-tooltip-type="CreatorTooltip"
:data-tooltip-content="$t('donations.creator-message')"
:to="`/journal/timetables?search-driver=${timetable.driverName}`"
>
<strong>{{ timetable.driverName }}</strong>
</router-link>
<router-link
v-else-if="apiStore.donatorsData.includes(timetable.driverName)"
class="text--donator" class="text--donator"
data-tooltip-type="DonatorTooltip" data-tooltip-type="DonatorTooltip"
:data-tooltip-content="$t('donations.driver-message')" :data-tooltip-content="$t('donations.driver-message')"
@@ -125,7 +115,6 @@ import styleMixin from '../../../mixins/styleMixin';
import { useApiStore } from '../../../store/apiStore'; import { useApiStore } from '../../../store/apiStore';
import trainCategoryMixin from '../../../mixins/trainCategoryMixin'; import trainCategoryMixin from '../../../mixins/trainCategoryMixin';
import FlagIcon from '../../Global/FlagIcon.vue'; import FlagIcon from '../../Global/FlagIcon.vue';
import { isCreator } from '../../../utils/userUtils';
export default defineComponent({ export default defineComponent({
components: { FlagIcon }, components: { FlagIcon },
@@ -133,8 +122,7 @@ export default defineComponent({
data() { data() {
return { return {
apiStore: useApiStore(), apiStore: useApiStore()
isCreator
}; };
}, },
+1 -2
View File
@@ -15,8 +15,7 @@ export namespace Journal {
| 'search-issuedFrom' | 'search-issuedFrom'
| 'search-terminatingAt' | 'search-terminatingAt'
| 'search-via' | 'search-via'
| 'select-categoryCode' | 'select-categoryCode';
| 'search-headUnit';
export type TimetableSearchType = { export type TimetableSearchType = {
[key in TimetableSearchKey]: string; [key in TimetableSearchKey]: string;
@@ -5,10 +5,7 @@
<ProfilePlayerAvatar :playerTD2Info="playerTD2Info" /> <ProfilePlayerAvatar :playerTD2Info="playerTD2Info" />
<div> <div>
<h2 <h2 class="player-name-header" :class="{ 'text--donator': isPlayerDonator }">
class="player-name-header"
:class="{ 'text--donator': isPlayerDonator, 'text--creator': isPlayerCreator }"
>
<a :href="`https://td2.info.pl/profile/?u=${route.query.playerId}`" target="_blank"> <a :href="`https://td2.info.pl/profile/?u=${route.query.playerId}`" target="_blank">
<img <img
v-if="isPlayerDonator" v-if="isPlayerDonator"
@@ -235,7 +232,6 @@ import { useApiStore } from '../../store/apiStore';
import StationStatusBadge from '../Global/StationStatusBadge.vue'; import StationStatusBadge from '../Global/StationStatusBadge.vue';
import ProfilePlayerAvatar from './ProfilePlayerAvatar.vue'; import ProfilePlayerAvatar from './ProfilePlayerAvatar.vue';
import { getRegionNameById } from '../../utils/regionUtils'; import { getRegionNameById } from '../../utils/regionUtils';
import { isCreator } from '../../utils/userUtils';
const { t } = useI18n(); const { t } = useI18n();
@@ -261,8 +257,6 @@ const isPlayerDonator = computed(() =>
props.playerName ? apiStore.donatorsData.includes(props.playerName) : false props.playerName ? apiStore.donatorsData.includes(props.playerName) : false
); );
const isPlayerCreator = computed(() => (props.playerName ? isCreator(props.playerName) : false));
const activeDispatches = computed(() => { const activeDispatches = computed(() => {
if (!props.playerName) return []; if (!props.playerName) return [];
if (!apiStore.activeData || !apiStore.activeData.activeSceneries) return []; if (!apiStore.activeData || !apiStore.activeData.activeSceneries) return [];
@@ -127,8 +127,9 @@ export default defineComponent({
this.station?.name || this.onlineScenery?.name this.station?.name || this.onlineScenery?.name
}&countFrom=${countFrom}&countLimit=${countLimit}`; }&countFrom=${countFrom}&countLimit=${countLimit}`;
const historyAPIData: API.DispatcherHistory.Response = const historyAPIData: API.DispatcherHistory.Response = await (
await this.apiStore.client.get(requestString); await this.apiStore.client!.get(requestString)
).data;
this.dataStatus = Status.Data.Loaded; this.dataStatus = Status.Data.Loaded;
return historyAPIData; return historyAPIData;
+7 -1
View File
@@ -24,6 +24,12 @@ import { useRoute, useRouter } from 'vue-router';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const prevPath = ref('/');
onMounted(() => {
prevPath.value = (route.meta['prevPath'] as string) ?? '/';
});
defineProps({ defineProps({
station: { station: {
type: Object as PropType<Station> type: Object as PropType<Station>
@@ -40,7 +46,7 @@ defineProps({
}); });
function onReturnButtonClick() { function onReturnButtonClick() {
router.push('/'); router.push(prevPath.value);
} }
</script> </script>
@@ -9,16 +9,9 @@
</span> </span>
<router-link class="dispatcher-name" :to="`/profile?playerId=${onlineScenery.dispatcherId}`"> <router-link class="dispatcher-name" :to="`/profile?playerId=${onlineScenery.dispatcherId}`">
<span
class="text--creator"
v-if="isCreator(onlineScenery.dispatcherName)"
:title="$t('donations.creator-message')"
>
{{ onlineScenery.dispatcherName }}
</span>
<span <span
class="text--donator" class="text--donator"
v-else-if="apiStore.donatorsData.includes(onlineScenery.dispatcherName)" v-if="apiStore.donatorsData.includes(onlineScenery.dispatcherName)"
:title="$t('donations.dispatcher-message')" :title="$t('donations.dispatcher-message')"
> >
{{ onlineScenery.dispatcherName }} {{ onlineScenery.dispatcherName }}
@@ -58,7 +51,6 @@ import StationStatusBadge from '../../Global/StationStatusBadge.vue';
import { ActiveScenery } from '../../../typings/common'; import { ActiveScenery } from '../../../typings/common';
import { useApiStore } from '../../../store/apiStore'; import { useApiStore } from '../../../store/apiStore';
import FlagIcon from '../../Global/FlagIcon.vue'; import FlagIcon from '../../Global/FlagIcon.vue';
import { isCreator } from '../../../utils/userUtils';
export default defineComponent({ export default defineComponent({
mixins: [styleMixin, dateMixin, routerMixin], mixins: [styleMixin, dateMixin, routerMixin],
@@ -66,8 +58,7 @@ export default defineComponent({
data() { data() {
return { return {
apiStore: useApiStore(), apiStore: useApiStore()
isCreator
}; };
}, },
@@ -1,6 +1,6 @@
<template> <template>
<section class="info-routes" v-if="station.generalInfo"> <section class="info-routes" v-if="station.generalInfo">
<div class="routes one-way" v-if="singleRoutesAvailable.length > 0"> <div class="routes one-way" v-if="oneWayRoutes.length > 0">
<button <button
class="routes-btn" class="routes-btn"
@click="toggleRoutesVisibility('single')" @click="toggleRoutesVisibility('single')"
@@ -12,7 +12,7 @@
</button> </button>
<ul class="routes-list"> <ul class="routes-list">
<li v-for="route in singleRoutesFiltered" :key="route.routeName"> <li v-for="route in oneWayRoutes" :key="route.routeName">
<span :class="{ 'no-catenary': !route.isElectric, internal: route.isInternal }"> <span :class="{ 'no-catenary': !route.isElectric, internal: route.isInternal }">
{{ route.routeName }}</span {{ route.routeName }}</span
> >
@@ -24,17 +24,10 @@
</span> </span>
<span v-if="route.isRouteSBL" class="sbl">SBL</span> <span v-if="route.isRouteSBL" class="sbl">SBL</span>
</li> </li>
<li v-if="singleRoutesFiltered.length == 0">
<span class="routes-hidden">
<i class="fa-solid fa-eye-slash"></i>
{{ $t('scenery.routes-hidden') }}
</span>
</li>
</ul> </ul>
</div> </div>
<div class="routes two-way" v-if="doubleRoutesAvailable.length > 0"> <div class="routes two-way" v-if="twoWayRoutes.length > 0">
<button <button
class="routes-btn" class="routes-btn"
@click="toggleRoutesVisibility('double')" @click="toggleRoutesVisibility('double')"
@@ -46,7 +39,7 @@
</button> </button>
<ul class="routes-list"> <ul class="routes-list">
<li v-for="route in doubleRoutesFiltered" :key="route.routeName"> <li v-for="route in twoWayRoutes" :key="route.routeName">
<span :class="{ 'no-catenary': !route.isElectric, internal: route.isInternal }"> <span :class="{ 'no-catenary': !route.isElectric, internal: route.isInternal }">
{{ route.routeName }} {{ route.routeName }}
</span> </span>
@@ -61,13 +54,6 @@
</span> </span>
<span v-if="route.isRouteSBL" class="sbl">SBL</span> <span v-if="route.isRouteSBL" class="sbl">SBL</span>
</li> </li>
<li v-if="doubleRoutesFiltered.length == 0">
<span class="routes-hidden">
<i class="fa-solid fa-eye-slash"></i>
{{ $t('scenery.routes-hidden') }}
</span>
</li>
</ul> </ul>
</div> </div>
</section> </section>
@@ -116,32 +102,20 @@ export default defineComponent({
}, },
computed: { computed: {
singleRoutesAvailable() { oneWayRoutes() {
return ( return (
this.station.generalInfo?.routes.single this.station.generalInfo?.routes.single
.filter((r) => !r.hidden) .filter((r) => !r.isInternal || r.isInternal == this.showInternalSingleRoutes)
.sort((r1, r2) => r1.routeName.localeCompare(r2.routeName)) ?? [] .sort((r1, r2) => r1.routeName.localeCompare(r2.routeName)) ?? []
); );
}, },
doubleRoutesAvailable() { twoWayRoutes() {
return ( return (
this.station.generalInfo?.routes.double this.station.generalInfo?.routes.double
.filter((r) => !r.hidden) .filter((r) => !r.isInternal || r.isInternal == this.showInternalDoubleRoutes)
.sort((r1, r2) => r1.routeName.localeCompare(r2.routeName)) ?? [] .sort((r1, r2) => r1.routeName.localeCompare(r2.routeName)) ?? []
); );
},
singleRoutesFiltered() {
return this.singleRoutesAvailable.filter(
(r) => this.showInternalSingleRoutes || !r.isInternal
);
},
doubleRoutesFiltered() {
return this.doubleRoutesAvailable.filter(
(r) => this.showInternalDoubleRoutes || !r.isInternal
);
} }
} }
}); });
@@ -180,6 +154,11 @@ ul.routes-list {
li { li {
margin: 0.5em 0.25em; margin: 0.5em 0.25em;
cursor: pointer;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
& > span { & > span {
padding: 0.2em; padding: 0.2em;
@@ -203,16 +182,11 @@ ul.routes-list {
background-color: #303030; background-color: #303030;
color: #cfcfcf; color: #cfcfcf;
} }
&.sbl { &.sbl {
color: var(--clr-primary); color: var(--clr-primary);
background-color: #404040; background-color: #404040;
} }
&.routes-hidden {
background-color: #4b4b4b;
}
&:last-child { &:last-child {
border-radius: 0 0.5em 0.5em 0; border-radius: 0 0.5em 0.5em 0;
} }
@@ -29,8 +29,7 @@
<i <i
v-if=" v-if="
train.timetableData != undefined && train.timetableData != undefined &&
train.lastSeen <= Date.now() - 60000 && (train.lastSeen <= Date.now() - 60000 || !train.online)
!train.online
" "
class="fa-solid fa-user-slash" class="fa-solid fa-user-slash"
style="color: lightcoral" style="color: lightcoral"
@@ -210,7 +210,7 @@
</div> </div>
<div class="item-stock-list" v-if="showStockThumbnails"> <div class="item-stock-list" v-if="showStockThumbnails">
<StockList :trainStockList="row.train.stockList" :thumbnailSize="45" /> <StockList :trainStockList="row.train.stockList" />
</div> </div>
</router-link> </router-link>
</transition-group> </transition-group>
@@ -348,7 +348,7 @@ const tabliceZbiorczeHref = computed(() => {
}); });
const pragotronHref = computed(() => { const pragotronHref = computed(() => {
let url = `https://pragotron-td2.spythere.eu/board?name=${props.station!.name}&region=${mainStore.region.id}`; let url = `https://pragotron-td2.web.app/board?name=${props.station!.name}&region=${mainStore.region.id}`;
if (props.chosenCheckpoint) url += `&checkpoint=${props.chosenCheckpoint}`; if (props.chosenCheckpoint) url += `&checkpoint=${props.chosenCheckpoint}`;
return url; return url;
@@ -149,12 +149,11 @@ export default defineComponent({
requestFilters['returnType'] = 'short'; requestFilters['returnType'] = 'short';
try { try {
const response: API.TimetableHistory.ResponseShort = await this.apiStore.client.get( const response: API.TimetableHistory.ResponseShort = await (
'api/getTimetables', await this.apiStore.client!.get('api/getTimetables', {
requestFilters params: requestFilters
); })
).data;
console.log(response);
this.historyList = response; this.historyList = response;
@@ -1,213 +0,0 @@
<template>
<div class="scenery-top-list">
<h2 class="header">{{ t('scenery.top-list.header') }}</h2>
<div class="top-actions">
<div class="actions-modes">
<button
v-for="mode in availableModes"
:class="`btn btn--option ${mode == currentListMode ? 'checked' : ''}`"
@click="selectListMode(mode)"
>
{{ t(`scenery.top-list.mode-${mode}`) }}
</button>
</div>
<div class="actions-scopes">
<button
v-for="scope in availableScopes"
:class="`btn btn--option ${scope == currentListScope ? 'checked' : ''}`"
@click="selectListScope(scope)"
>
{{ t(`scenery.top-list.scope-${scope}`) }}
</button>
</div>
</div>
<div class="rating-list-wrapper">
<Loading v-if="listState == Status.Data.Loading" />
<div v-else-if="listState == Status.Data.Error">Ups, coś poszło nie tak...</div>
<ul v-else-if="bestScoreList.length > 0">
<li v-for="(value, i) in bestScoreList">
<div>
{{ t('ordinal', { count: i + 1 }) }} {{ t('scenery.top-list.place') }} -
<router-link :to="`/profile?playerId=${value.dispatcherId}`">{{
value.dispatcherName
}}</router-link>
</div>
<div>
<b class="text--primary" v-if="currentListMode == 'dutyCount'">{{
t('scenery.top-list.duty-count', value.value)
}}</b>
<b class="text--primary" v-else-if="currentListMode == 'dispatcherRating'">{{
t('scenery.top-list.dispatcher-rating', value.value)
}}</b>
<b class="text--primary" v-else>
{{ t('scenery.top-list.duration') }}
{{ humanizeDuration(value.value) }}
</b>
</div>
</li>
</ul>
<div v-else class="no-data">
<span v-if="currentListScope == 'name'">{{ t('scenery.top-list.no-data-general') }}</span>
<span v-else>{{ t('scenery.top-list.no-data-current-hash') }}</span>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { onActivated, PropType, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useApiStore } from '../../store/apiStore';
import { Station, ActiveScenery, Status } from '../../typings/common';
import Loading from '../Global/Loading.vue';
import { humanizeDuration } from '../../composables/time';
interface SceneryBestScoreItem {
dispatcherName: string;
dispatcherId: number;
value: number;
}
const { t } = useI18n();
const apiStore = useApiStore();
defineOptions({
name: 'SceneryTopList'
});
const props = defineProps({
station: {
type: Object as PropType<Station>
},
onlineScenery: {
type: Object as PropType<ActiveScenery>
}
});
const availableModes = ['dutyCount', 'dispatcherRating', 'dutyDuration'] as const;
const availableScopes = ['name', 'hash'] as const;
type ListMode = (typeof availableModes)[number];
type ListScope = (typeof availableScopes)[number];
const currentListMode = ref<ListMode>('dutyCount');
const currentListScope = ref<ListScope>('name');
const listState = ref<Status.Data>(Status.Data.Loading);
const bestScoreList = ref<SceneryBestScoreItem[]>([]);
onActivated(() => {
fetchTopDispatchersList();
});
function selectListMode(mode: ListMode) {
currentListMode.value = mode;
fetchTopDispatchersList();
}
function selectListScope(scope: ListScope) {
currentListScope.value = scope;
fetchTopDispatchersList();
}
async function fetchTopDispatchersList() {
const searchedStationValue =
currentListScope.value == 'name'
? props.station?.name
: apiStore.sceneryData.find((sc) => sc.name == props.station!.name)?.hash;
bestScoreList.value = [];
if (!searchedStationValue) {
listState.value = Status.Data.Loaded;
return;
}
try {
listState.value = Status.Data.Loading;
const response: SceneryBestScoreItem[] = await apiStore.client.get(`api/getSceneryBestScores`, {
[currentListScope.value]: searchedStationValue,
type: currentListMode.value,
countLimit: 40
});
bestScoreList.value = response;
listState.value = Status.Data.Loaded;
} catch (error) {
listState.value = Status.Data.Error;
console.error(error);
}
}
</script>
<style lang="scss" scoped>
.scenery-top-list {
display: grid;
grid-template-rows: auto auto 1fr;
overflow: hidden;
gap: 1em;
}
.top-actions {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 0.5em 1.5em;
}
.actions-modes,
.actions-scopes {
display: flex;
flex-wrap: wrap;
gap: 0.5em;
font-weight: bold;
}
.rating-list-wrapper {
overflow: auto;
}
.rating-list-wrapper > ul {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
align-items: center;
gap: 0.65em;
padding-right: 0.5em;
}
.rating-list-wrapper > ul > li {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
padding: 0.25em;
background-color: #2b2b2b;
height: 100%;
line-height: 1.5em;
a {
font-weight: bold;
}
}
.no-data {
padding: 1em 0.5em;
font-size: 1.1em;
background-color: #333;
color: #ccc;
}
</style>
+5 -13
View File
@@ -18,31 +18,23 @@ export function getTrainStopStatus(
return StopStatus.TERMINATED; return StopStatus.TERMINATED;
} }
if ( if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName == sceneryName) {
!stopInfo.terminatesHere &&
stopInfo.confirmed &&
currentStationName.startsWith(sceneryName)
) {
return StopStatus.DEPARTED; return StopStatus.DEPARTED;
} }
if ( if (!stopInfo.terminatesHere && stopInfo.confirmed && currentStationName != sceneryName) {
!stopInfo.terminatesHere &&
stopInfo.confirmed &&
!currentStationName.startsWith(sceneryName)
) {
return StopStatus.DEPARTED_AWAY; return StopStatus.DEPARTED_AWAY;
} }
if (currentStationName.startsWith(sceneryName) && !stopInfo.stopped) { if (currentStationName == sceneryName && !stopInfo.stopped) {
return StopStatus.ONLINE; return StopStatus.ONLINE;
} }
if (currentStationName.startsWith(sceneryName) && stopInfo.stopped) { if (currentStationName == sceneryName && stopInfo.stopped) {
return StopStatus.STOPPED; return StopStatus.STOPPED;
} }
if (!currentStationName.startsWith(sceneryName)) { if (currentStationName != sceneryName) {
return StopStatus.ARRIVING; return StopStatus.ARRIVING;
} }
@@ -278,10 +278,6 @@ export default defineComponent({
color: #ccc; color: #ccc;
} }
.dropdown_wrapper {
top: 2.5em;
}
@include responsive.smallScreen { @include responsive.smallScreen {
.stats-title { .stats-title {
text-align: center; text-align: center;
@@ -290,9 +286,5 @@ export default defineComponent({
.filter-button > span { .filter-button > span {
display: none; display: none;
} }
.no-data {
text-align: center;
}
} }
</style> </style>
+5 -25
View File
@@ -1,5 +1,5 @@
<template> <template>
<section class="station_table" @scroll="onScroll" ref="tableRef"> <section class="station_table">
<Loading <Loading
v-if="apiStore.dataStatuses.connection == Status.Loading && filteredStationList.length == 0" v-if="apiStore.dataStatuses.connection == Status.Loading && filteredStationList.length == 0"
/> />
@@ -131,16 +131,7 @@
<td class="station-dispatcher-name"> <td class="station-dispatcher-name">
<span v-if="station.onlineInfo?.dispatcherName"> <span v-if="station.onlineInfo?.dispatcherName">
<b <b
v-if="isCreator(station.onlineInfo.dispatcherName)" v-if="apiStore.donatorsData.includes(station.onlineInfo.dispatcherName)"
data-tooltip-type="CreatorTooltip"
:data-tooltip-content="$t('donations.creator-message')"
>
<img src="/images/icon-creator.png" alt="creator icon" />
<span class="text--creator">&nbsp;{{ station.onlineInfo.dispatcherName }}</span>
</b>
<b
v-else-if="apiStore.donatorsData.includes(station.onlineInfo.dispatcherName)"
data-tooltip-type="DonatorTooltip" data-tooltip-type="DonatorTooltip"
:data-tooltip-content="$t('donations.dispatcher-message')" :data-tooltip-content="$t('donations.dispatcher-message')"
> >
@@ -362,7 +353,6 @@ import { ActiveSorter, HeadIdsType, headIconsIds, headIds } from './typings';
import { filterStations, sortStations } from './utils'; import { filterStations, sortStations } from './utils';
import { getLanguageNameById } from '../../utils/languageUtils'; import { getLanguageNameById } from '../../utils/languageUtils';
import FlagIcon from '../Global/FlagIcon.vue'; import FlagIcon from '../Global/FlagIcon.vue';
import { isCreator } from '../../utils/userUtils';
export default defineComponent({ export default defineComponent({
emits: ['toggleDonationCard'], emits: ['toggleDonationCard'],
@@ -373,9 +363,7 @@ export default defineComponent({
data: () => ({ data: () => ({
headIconsIds, headIconsIds,
headIds, headIds,
scrollTop: 0, getChangedFilters
getChangedFilters,
isCreator
}), }),
setup() { setup() {
@@ -403,10 +391,6 @@ export default defineComponent({
}; };
}, },
activated() {
(this.$refs['tableRef'] as HTMLElement).scrollTop = this.scrollTop;
},
methods: { methods: {
getSceneryRoute(station: Station) { getSceneryRoute(station: Station) {
this.$router.push({ this.$router.push({
@@ -447,10 +431,6 @@ export default defineComponent({
})); }));
return JSON.stringify(usersTrains); return JSON.stringify(usersTrains);
},
onScroll(e: Event) {
this.scrollTop = (e.target as HTMLElement).scrollTop;
} }
} }
}); });
@@ -633,8 +613,8 @@ tbody tr {
.station-dispatcher-name { .station-dispatcher-name {
img { img {
max-height: 1.3em; max-width: 1.35em;
vertical-align: text-top; vertical-align: text-bottom;
} }
} }
-36
View File
@@ -1,36 +0,0 @@
<template>
<div class="tooltip-content">
<img src="/images/icon-creator.png" alt="creator icon" />
<b class="text--creator">&nbsp;{{ tooltipStore.content }}</b>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { useTooltipStore } from '../../store/tooltipStore';
export default defineComponent({
data() {
return {
tooltipStore: useTooltipStore()
};
}
});
</script>
<style lang="scss" scoped>
.tooltip-content {
padding: 0.5em;
border-radius: 0.25em;
width: 100%;
background-color: #333;
box-shadow: 0 0 10px 2px #aaa;
}
img {
vertical-align: text-bottom;
height: 1.25em;
}
</style>
+8 -3
View File
@@ -1,7 +1,7 @@
<template> <template>
<div class="tooltip-content"> <div class="tooltip-content">
<img src="/images/icon-diamond.svg" alt="donator diamond icon" /> <img src="/images/icon-diamond.svg" alt="donator diamond icon" />
<b class="text--donator">&nbsp;{{ tooltipStore.content }}</b> <span>{{ tooltipStore.content }}</span>
</div> </div>
</template> </template>
@@ -20,6 +20,11 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
.tooltip-content { .tooltip-content {
display: flex;
align-items: center;
gap: 0.5em;
flex-wrap: wrap;
padding: 0.5em; padding: 0.5em;
border-radius: 0.25em; border-radius: 0.25em;
@@ -30,7 +35,7 @@ export default defineComponent({
} }
img { img {
vertical-align: text-bottom; vertical-align: middle;
height: 1.25em; height: 1em;
} }
</style> </style>
+2 -4
View File
@@ -8,13 +8,12 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { useTooltipStore } from '../../store/tooltipStore'; import { useTooltipStore } from '../../store/tooltipStore';
import DonatorTooltip from './DonatorTooltip.vue'; import DonatorTooltip from './DonatorTooltip.vue';
import CreatorTooltip from './CreatorTooltip.vue';
import VehiclePreviewTooltip from './VehiclePreviewTooltip.vue'; import VehiclePreviewTooltip from './VehiclePreviewTooltip.vue';
import BaseTooltip from './BaseTooltip.vue'; import BaseTooltip from './BaseTooltip.vue';
import SpawnsTooltip from './SpawnsTooltip.vue'; import SpawnsTooltip from './SpawnsTooltip.vue';
import UsersTooltip from './UsersTooltip.vue'; import UsersTooltip from './UsersTooltip.vue';
import HtmlTooltip from './HtmlTooltip.vue'; import HtmlTooltip from './HtmlTooltip.vue';
import TrainInfoTooltip from './TrainInfoTooltip.vue'; import TrainInfoTooltip from "./TrainInfoTooltip.vue";
const BOX_PADDING_PX = 20; const BOX_PADDING_PX = 20;
@@ -26,8 +25,7 @@ export default defineComponent({
SpawnsTooltip, SpawnsTooltip,
UsersTooltip, UsersTooltip,
HtmlTooltip, HtmlTooltip,
TrainInfoTooltip, TrainInfoTooltip
CreatorTooltip
}, },
data() { data() {
+8 -15
View File
@@ -56,21 +56,12 @@
</b> </b>
<b <b
v-if="isCreator(train.driverName)" v-if="apiStore.donatorsData.includes(train.driverName)"
data-tooltip-type="CreatorTooltip"
:data-tooltip-content="$t('donations.creator-message')"
>
<img src="/images/icon-creator.png" alt="creator icon" />
<span class="text--creator">&nbsp;{{ train.driverName }}</span>
</b>
<b
v-else-if="apiStore.donatorsData.includes(train.driverName)"
data-tooltip-type="DonatorTooltip" data-tooltip-type="DonatorTooltip"
:data-tooltip-content="$t('donations.driver-message')" :data-tooltip-content="$t('donations.driver-message')"
> >
<span class="text--donator">{{ train.driverName }}&nbsp;</span>
<img src="/images/icon-diamond.svg" alt="donator diamond icon" /> <img src="/images/icon-diamond.svg" alt="donator diamond icon" />
<span class="text--donator">&nbsp;{{ train.driverName }}</span>
</b> </b>
<span v-else>{{ train.driverName }}</span> <span v-else>{{ train.driverName }}</span>
@@ -213,7 +204,6 @@ import trainCategoryMixin from '../../mixins/trainCategoryMixin';
import ProgressBar from '../Global/ProgressBar.vue'; import ProgressBar from '../Global/ProgressBar.vue';
import StockList from '../Global/StockList.vue'; import StockList from '../Global/StockList.vue';
import FlagIcon from '../Global/FlagIcon.vue'; import FlagIcon from '../Global/FlagIcon.vue';
import { isCreator } from '../../utils/userUtils';
export default defineComponent({ export default defineComponent({
mixins: [trainInfoMixin, styleMixin, trainCategoryMixin], mixins: [trainInfoMixin, styleMixin, trainCategoryMixin],
@@ -232,8 +222,7 @@ export default defineComponent({
data() { data() {
return { return {
store: useMainStore(), store: useMainStore(),
apiStore: useApiStore(), apiStore: useApiStore()
isCreator
}; };
}, },
@@ -289,6 +278,8 @@ export default defineComponent({
display: flex; display: flex;
flex-direction: column; flex-direction: column;
font-size: 1em; font-size: 1em;
gap: 0.25em;
background-color: #1a1a1a; background-color: #1a1a1a;
gap: 0.5em; gap: 0.5em;
} }
@@ -367,6 +358,8 @@ export default defineComponent({
.status-badges { .status-badges {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
margin-left: 0.25em;
gap: 0.25em; gap: 0.25em;
img { img {
@@ -379,7 +372,7 @@ export default defineComponent({
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.5em; gap: 0.5em;
padding: 0.25em 0; padding: 0.5em 0;
} }
.progress-distance { .progress-distance {
@@ -210,10 +210,6 @@ export default defineComponent({
@use '../../styles/dropdown'; @use '../../styles/dropdown';
@use '../../styles/dropdown-filters'; @use '../../styles/dropdown-filters';
.dropdown_wrapper {
top: 2.5em;
}
.search_content > div { .search_content > div {
margin: 0.5em auto; margin: 0.5em auto;
} }
-1
View File
@@ -250,7 +250,6 @@ h3 {
.dropdown_wrapper { .dropdown_wrapper {
max-width: 600px; max-width: 600px;
top: 2.5em;
} }
@include responsive.smallScreen{ @include responsive.smallScreen{
-23
View File
@@ -1,23 +0,0 @@
export class HttpClient {
constructor(private readonly baseURL: string) {}
async get<T>(url: string, params?: Record<string, any>): Promise<T> {
const absoluteURL = new URL(this.baseURL + '/' + url);
if (params) {
Object.keys(params).forEach((key) => {
if (params[key] === undefined) return;
absoluteURL.searchParams.append(key, params[key]);
});
}
const data = await fetch(absoluteURL);
if (!data.ok) {
throw new Error(`Cannot fetch ${absoluteURL}: ${data.statusText}`);
}
return data.json();
}
}
+2 -36
View File
@@ -9,43 +9,9 @@ const i18n = createI18n({
warnHtmlMessage: false, warnHtmlMessage: false,
fallbackLocale: 'pl', fallbackLocale: 'pl',
pluralizationRules: {
en: {
ordinal: (ctx: { named: (arg0: string) => number }) => {
const number = ctx.named('count');
const suffixes: Record<number, string> = {
1: 'st',
2: 'nd',
3: 'rd'
};
const suffix = suffixes[number % 10] || 'th';
return `${number}${suffix}`;
}
}
},
messages: { messages: {
en: { en: enLang,
...enLang, pl: plLang
ordinal: (ctx: { named: (arg0: string) => number }) => {
const number = ctx.named('count');
const suffixes: Record<number, string> = {
1: 'st',
2: 'nd',
3: 'rd'
};
const suffix = suffixes[number % 10] || 'th';
return `${number}${suffix}`;
}
},
pl: {
...plLang,
ordinal: '{count}.'
}
}, },
enableLegacy: false enableLegacy: false
}); });
+6 -36
View File
@@ -42,8 +42,7 @@
"action-paypal": "DONATE WITH PAYPAL", "action-paypal": "DONATE WITH PAYPAL",
"action-buycoffee": "BUY ME A COFFEE!", "action-buycoffee": "BUY ME A COFFEE!",
"dispatcher-message": "Dispatcher supporting the Stacjownik project!", "dispatcher-message": "Dispatcher supporting the Stacjownik project!",
"driver-message": "Driver supporting the Stacjownik project!", "driver-message": "Driver supporting the Stacjownik project!"
"creator-message": "Creator of the Stacjownik project"
}, },
"warnings": { "warnings": {
"TWR": "Train with high risk cargo", "TWR": "Train with high risk cargo",
@@ -200,7 +199,6 @@
"search-date-from": "Date (UTC+2 / CEST)", "search-date-from": "Date (UTC+2 / CEST)",
"search-date-to": "Date (UTC+2 / CEST)", "search-date-to": "Date (UTC+2 / CEST)",
"select-categoryCode": "Train category", "select-categoryCode": "Train category",
"search-headUnit": "Traction unit (e.g. EP09, ET22-401)",
"sort-mass": "mass", "sort-mass": "mass",
"sort-speed": "speed", "sort-speed": "speed",
"sort-length": "length", "sort-length": "length",
@@ -439,25 +437,15 @@
"driver-not-found-others": "Player {driver} is online as:", "driver-not-found-others": "Player {driver} is online as:",
"driver-not-found-return": "RETURN TO THE MAIN SITE", "driver-not-found-return": "RETURN TO THE MAIN SITE",
"stock-copy": "COPY THE STOCK", "stock-copy": "COPY THE STOCK",
"number-propositions": "NUMBER & WARNINGS SUGGESTIONS", "number-propositions": "PROPOSE NUMBER",
"stock-clipboard-success": "Successfully copied the railway stock in a text form to your clipboard!", "stock-clipboard-success": "Successfully copied the railway stock in a text form to your clipboard!",
"stock-clipboard-failure": "Oops! Something happened and the railway stock couldn't be copied to your clipboard! :/", "stock-clipboard-failure": "Oops! Something happened and the railway stock couldn't be copied to your clipboard! :/",
"number-propositions-header": "Generate number examples for a train category:", "number-propositions-header": "Generate number examples for selected category:",
"number-propositions-third-number": "Third digit:", "number-propositions-third-number": "Third digit:",
"number-propositions-last-nums": "{count} last digits from the range of:", "number-propositions-last-nums": "{count} last digits from the range of:",
"number-propositions-title": "Propositions:", "number-propositions-title": "Propositions:",
"number-propositions-empty": "No propositions available for the chosen category! :/" "number-propositions-empty": "No propositions available for the chosen category! :/"
}, },
"cargo-warnings": {
"title": "Additional cargo warnings:",
"pn-innofreight": "PN: Innofreight C45: exceeded gauge",
"twr-un1965": "TWR: UN1965 (LPG)",
"tn-un1965": "TN: unclean tanks after UN1965",
"tn-un1202": "TN: UN1202 (diesel fuel)",
"tn-un1202-empty": "TN: unclean tanks after UN1202",
"pn-military": "PN: military transport",
"pn-edk80": "PN: EDK80 railway crane"
},
"train-stats": { "train-stats": {
"stats-button": "STATISTICS", "stats-button": "STATISTICS",
"title": "ONLINE TRAINS STATS", "title": "ONLINE TRAINS STATS",
@@ -570,7 +558,7 @@
"no-users": "NO ACTIVE PLAYERS", "no-users": "NO ACTIVE PLAYERS",
"no-spawns": "NO OPEN SPAWNS", "no-spawns": "NO OPEN SPAWNS",
"no-scenery": "Oops! This scenery doesn't exist!", "no-scenery": "Oops! This scenery doesn't exist!",
"return-btn": "BACK TO SCENERIES", "return-btn": "BACK TO THE MAIN SITE",
"history-btn": "View the dispatcher history", "history-btn": "View the dispatcher history",
"info-btn": "Return to the scenery view", "info-btn": "Return to the scenery view",
"authors-title": "Scenery author | Scenery authors", "authors-title": "Scenery author | Scenery authors",
@@ -580,12 +568,10 @@
"additional-tools-title": "Additional tools", "additional-tools-title": "Additional tools",
"one-way-routes": "Single track routes", "one-way-routes": "Single track routes",
"two-way-routes": "Double track routes", "two-way-routes": "Double track routes",
"routes-hidden": "Hidden internal 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",
"option-timetables-history": "Timetables history PL1", "option-timetables-history": "Timetables history PL1",
"option-dispatchers-history": "Dispatchers history PL1", "option-dispatchers-history": "Dispatchers history PL1",
"option-top-list": "Scenery records",
"btn-show-timetable-thumbnails": "Show rolling stock thumbnails", "btn-show-timetable-thumbnails": "Show rolling stock thumbnails",
"btn-hide-timetable-thumbnails": "Hide rolling stock thumbnails", "btn-hide-timetable-thumbnails": "Hide rolling stock thumbnails",
"timetable-includesScenery": "ALL TIMETABLES", "timetable-includesScenery": "ALL TIMETABLES",
@@ -598,30 +584,14 @@
"dispatcher-rate": "Rate:", "dispatcher-rate": "Rate:",
"dispatcher-status-changes": "Status changes:", "dispatcher-status-changes": "Status changes:",
"req-level": "all dispatcher levels | dispatcher level {lvl} required | dispatcher level {lvl} required", "req-level": "all dispatcher levels | dispatcher level {lvl} required | dispatcher level {lvl} required",
"history-list-empty": "No saved scenery history!", "history-list-empty": "No recorded scenery history!",
"forum-topic": "Scenery's forum topic", "forum-topic": "Scenery's forum topic",
"gnr-link": "Train orders generator", "gnr-link": "Train orders generator",
"pragotron-link": "Timetable pallet board", "pragotron-link": "Timetable pallet board",
"tablice-link": "Timetable summary board <br> (by Thundo)", "tablice-link": "Timetable summary board <br> (by Thundo)",
"bottom-info": "Show full history in the Journal tab", "bottom-info": "Show full history in the Journal tab",
"btn-show-internal-routes": "Show internal routes", "btn-show-internal-routes": "Show internal routes",
"btn-hide-internal-routes": "Hide internal routes", "btn-hide-internal-routes": "Hide internal routes"
"top-list": {
"header": "RECORDS ON THE SCENERY (PL1)",
"mode-dutyCount": "DUTIES",
"mode-dispatcherRating": "RATING",
"mode-dutyDuration": "DUTY DURATION",
"scope-name": "GENERAL",
"scope-hash": "CURRENT HASH",
"place": "place",
"dispatcher-rating": "Rating: {n}",
"duty-count": "No duties | 1 duty | Duties: {n}",
"duration": "Duration:",
"no-data-general": "No best scores for this scenery on the PL1 server!",
"no-data-current-hash": "No best scores for the current scenery hash on the PL1 server!"
}
}, },
"availability": { "availability": {
"title": "Availability", "title": "Availability",
+5 -35
View File
@@ -42,8 +42,7 @@
"action-paypal": "PRZELEJ PAYPALEM", "action-paypal": "PRZELEJ PAYPALEM",
"action-buycoffee": "POSTAW KAWĘ!", "action-buycoffee": "POSTAW KAWĘ!",
"dispatcher-message": "Dyżurny wspierający projekt Stacjownika!", "dispatcher-message": "Dyżurny wspierający projekt Stacjownika!",
"driver-message": "Maszynista wspierający projekt Stacjownika!", "driver-message": "Maszynista wspierający projekt Stacjownika!"
"creator-message": "Twórca projektu Stacjownik"
}, },
"warnings": { "warnings": {
"TWR": "Pociąg z towarami niebezpiecznymi wysokiego ryzyka", "TWR": "Pociąg z towarami niebezpiecznymi wysokiego ryzyka",
@@ -197,7 +196,6 @@
"search-date-from": "Data (UTC+2 / CEST)", "search-date-from": "Data (UTC+2 / CEST)",
"search-date-to": "Data (UTC+2 / CEST)", "search-date-to": "Data (UTC+2 / CEST)",
"select-categoryCode": "Kategoria pociągu", "select-categoryCode": "Kategoria pociągu",
"search-headUnit": "Pojazd trakcyjny (np. EP09, ET22-137)",
"sort-routeDistance": "kilometraż", "sort-routeDistance": "kilometraż",
"sort-allStopsCount": "stacje", "sort-allStopsCount": "stacje",
"sort-beginDate": "data", "sort-beginDate": "data",
@@ -426,25 +424,15 @@
"driver-not-found-others": "Gracz {driver} jest online jako:", "driver-not-found-others": "Gracz {driver} jest online jako:",
"driver-not-found-return": "WRÓĆ NA STRONĘ GŁÓWNĄ", "driver-not-found-return": "WRÓĆ NA STRONĘ GŁÓWNĄ",
"stock-copy": "SKOPIUJ SKŁAD", "stock-copy": "SKOPIUJ SKŁAD",
"number-propositions": "PROPOZYCJE NUMERÓW I UWAG", "number-propositions": "ZAPROPONUJ NUMER",
"stock-clipboard-success": "Pomyślnie skopiowano skład w postaci tekstowej do schowka!", "stock-clipboard-success": "Pomyślnie skopiowano skład w postaci tekstowej do schowka!",
"stock-clipboard-failure": "Ups! Nie udało się skopiować składu do schowka! :/", "stock-clipboard-failure": "Ups! Nie udało się skopiować składu do schowka! :/",
"number-propositions-header": "Wygeneruj propozycje numerów dla pociągu kategorii:", "number-propositions-header": "Wygeneruj propozycje numerów dla kategorii pociągu:",
"number-propositions-third-number": "Trzecia cyfra:", "number-propositions-third-number": "Trzecia cyfra:",
"number-propositions-last-nums": "{count} ostatnie cyfry z przedziału:", "number-propositions-last-nums": "{count} ostatnie cyfry z przedziału:",
"number-propositions-title": "Propozycje:", "number-propositions-title": "Propozycje:",
"number-propositions-empty": "Brak propozycji dla wybranej kategorii! :/" "number-propositions-empty": "Brak propozycji dla wybranej kategorii! :/"
}, },
"cargo-warnings": {
"title": "Dodatkowe uwagi przewozowe:",
"pn-innofreight": "PN: Innofreight C45: przekroczona skrajnia",
"twr-un1965": "TWR: UN1965 (LPG)",
"tn-un1965": "TN: brudne cysterny po UN1965",
"tn-un1202": "TN: UN1202 (olej napędowy)",
"tn-un1202-empty": "TN: brudne cysterny po UN1202",
"pn-military": "PN: transport wojskowy",
"pn-edk80": "PN: żuraw kolejowy EDK80"
},
"train-stats": { "train-stats": {
"stats-button": "STATYSTYKI", "stats-button": "STATYSTYKI",
"title": "STATYSTYKI AKTYWNYCH POCIĄGÓW", "title": "STATYSTYKI AKTYWNYCH POCIĄGÓW",
@@ -556,7 +544,7 @@
"no-users": "BRAK AKTYWNYCH GRACZY", "no-users": "BRAK AKTYWNYCH GRACZY",
"no-spawns": "BRAK OTWARTYCH SPAWNÓW", "no-spawns": "BRAK OTWARTYCH SPAWNÓW",
"no-scenery": "Ups! Ta sceneria nie istnieje!", "no-scenery": "Ups! Ta sceneria nie istnieje!",
"return-btn": "POWRÓT DO SCENERII", "return-btn": "POWRÓT DO STRONY GŁÓWNEJ",
"history-btn": "Przejdź do widoku historii dyżurnych ruchu", "history-btn": "Przejdź do widoku historii dyżurnych ruchu",
"info-btn": "Wróć do widoku scenerii", "info-btn": "Wróć do widoku scenerii",
"authors-title": "Autor scenerii | Autorzy scenerii", "authors-title": "Autor scenerii | Autorzy scenerii",
@@ -566,12 +554,10 @@
"additional-tools-title": "Dodatkowe narzędzia", "additional-tools-title": "Dodatkowe narzędzia",
"one-way-routes": "Szlaki jednotorowe", "one-way-routes": "Szlaki jednotorowe",
"two-way-routes": "Szlaki dwutorowe", "two-way-routes": "Szlaki dwutorowe",
"routes-hidden": "Ukryto szlaki wewnętrzne",
"no-data": "Brak informacji o tej scenerii", "no-data": "Brak informacji o tej scenerii",
"option-active-timetables": "Aktywne rozkłady jazdy", "option-active-timetables": "Aktywne rozkłady jazdy",
"option-timetables-history": "Historia rozkładów PL1", "option-timetables-history": "Historia rozkładów PL1",
"option-dispatchers-history": "Historia dyżurów PL1", "option-dispatchers-history": "Historia dyżurów PL1",
"option-top-list": "Rekordy scenerii",
"btn-show-timetable-thumbnails": "Pokazuj podglądy składów", "btn-show-timetable-thumbnails": "Pokazuj podglądy składów",
"btn-hide-timetable-thumbnails": "Ukrywaj podglądy składów", "btn-hide-timetable-thumbnails": "Ukrywaj podglądy składów",
"timetable-includesScenery": "WSZYSTKIE RJ", "timetable-includesScenery": "WSZYSTKIE RJ",
@@ -591,23 +577,7 @@
"tablice-link": "Tablica informacyjna zbiorcza <br> (autorstwa Thundo)", "tablice-link": "Tablica informacyjna zbiorcza <br> (autorstwa Thundo)",
"bottom-info": "Pokaż pełną historię w zakładce Dziennika", "bottom-info": "Pokaż pełną historię w zakładce Dziennika",
"btn-show-internal-routes": "Pokazuj szlaki wewnętrzne", "btn-show-internal-routes": "Pokazuj szlaki wewnętrzne",
"btn-hide-internal-routes": "Ukrywaj szlaki wewnętrzne", "btn-hide-internal-routes": "Ukrywaj szlaki wewnętrzne"
"top-list": {
"header": "REKORDY NA SCENERII (PL1)",
"mode-dutyCount": "DYŻURY",
"mode-dispatcherRating": "OCENA",
"mode-dutyDuration": "CZAS DYŻURU",
"scope-name": "OGÓLNIE",
"scope-hash": "OBECNY HASH",
"place": "miejsce",
"dispatcher-rating": "Ocena: {n}",
"duty-count": "Brak dyżurów | 1 dyżur | Dyżury: {n}",
"duration": "Czas:",
"no-data-general": "Brak zapisanych rekordów scenerii na serwerze PL1!",
"no-data-current-hash": "Brak zapisanych rekordów scenerii z obecnym hashem na serwerze PL1!"
}
}, },
"availability": { "availability": {
"title": "Dostępność", "title": "Dostępność",
+26 -26
View File
@@ -94,14 +94,14 @@ export const initFilters = {
minTwoWayCatenary: 0, minTwoWayCatenary: 0,
minTwoWayInt: 0, minTwoWayInt: 0,
minTwoWayCatenaryInt: 0, minTwoWayCatenaryInt: 0,
maxOneWay: 10, maxOneWay: 5,
maxOneWayCatenary: 10, maxOneWayCatenary: 5,
maxOneWayInt: 20, maxOneWayInt: 5,
maxOneWayCatenaryInt: 20, maxOneWayCatenaryInt: 5,
maxTwoWay: 10, maxTwoWay: 5,
maxTwoWayCatenary: 10, maxTwoWayCatenary: 5,
maxTwoWayInt: 20, maxTwoWayInt: 5,
maxTwoWayCatenaryInt: 20, maxTwoWayCatenaryInt: 5,
authors: '', authors: '',
projects: '', projects: '',
lines: '' lines: ''
@@ -122,62 +122,62 @@ export const sliderGroups: SliderGroup[] = [
export const sliderGroupsOptions: Record<SliderGroup, SliderOptions[]> = { export const sliderGroupsOptions: Record<SliderGroup, SliderOptions[]> = {
vMax: [ vMax: [
{ id: 'minVmax', minRange: 0, maxRange: 200, step: 20 }, { id: 'minVmax', minRange: 0, maxRange: 200, step: 10 },
{ id: 'maxVmax', minRange: 0, maxRange: 200, step: 20 } { id: 'maxVmax', minRange: 0, maxRange: 200, step: 10 }
], ],
level: [ level: [
{ id: 'minLevel', minRange: 0, maxRange: 20, step: 1 }, { id: 'minLevel', minRange: 0, maxRange: 20, step: 1 },
{ id: 'maxLevel', minRange: 0, maxRange: 20, step: 1 } { id: 'maxLevel', minRange: 0, maxRange: 20, step: 1 }
], ],
routeOneWay: [ routeOneWay: [
{ id: 'minOneWay', minRange: 0, maxRange: 10, step: 1 }, { id: 'minOneWay', minRange: 0, maxRange: 5, step: 1 },
{ id: 'maxOneWay', minRange: 0, maxRange: 10, step: 1 } { id: 'maxOneWay', minRange: 0, maxRange: 5, step: 1 }
], ],
routeOneWayCatenary: [ routeOneWayCatenary: [
{ id: 'minOneWayCatenary', minRange: 0, maxRange: 10, step: 1 }, { id: 'minOneWayCatenary', minRange: 0, maxRange: 5, step: 1 },
{ id: 'maxOneWayCatenary', minRange: 0, maxRange: 10, step: 1 } { id: 'maxOneWayCatenary', minRange: 0, maxRange: 5, step: 1 }
], ],
routeOneWayInternal: [ routeOneWayInternal: [
{ id: 'minOneWayInt', minRange: 0, maxRange: 20, step: 1 }, { id: 'minOneWayInt', minRange: 0, maxRange: 5, step: 1 },
{ id: 'maxOneWayInt', minRange: 0, maxRange: 20, step: 1 } { id: 'maxOneWayInt', minRange: 0, maxRange: 5, step: 1 }
], ],
routeOneWayInternalCatenary: [ routeOneWayInternalCatenary: [
{ {
id: 'minOneWayCatenaryInt', id: 'minOneWayCatenaryInt',
minRange: 0, minRange: 0,
maxRange: 20, maxRange: 5,
step: 1 step: 1
}, },
{ {
id: 'maxOneWayCatenaryInt', id: 'maxOneWayCatenaryInt',
minRange: 0, minRange: 0,
maxRange: 20, maxRange: 5,
step: 1 step: 1
} }
], ],
routeTwoWay: [ routeTwoWay: [
{ id: 'minTwoWay', minRange: 0, maxRange: 10, step: 1 }, { id: 'minTwoWay', minRange: 0, maxRange: 5, step: 1 },
{ id: 'maxTwoWay', minRange: 0, maxRange: 10, step: 1 } { id: 'maxTwoWay', minRange: 0, maxRange: 5, step: 1 }
], ],
routeTwoWayCatenary: [ routeTwoWayCatenary: [
{ id: 'minTwoWayCatenary', minRange: 0, maxRange: 10, step: 1 }, { id: 'minTwoWayCatenary', minRange: 0, maxRange: 5, step: 1 },
{ id: 'maxTwoWayCatenary', minRange: 0, maxRange: 10, step: 1 } { id: 'maxTwoWayCatenary', minRange: 0, maxRange: 5, step: 1 }
], ],
routeTwoWayInternal: [ routeTwoWayInternal: [
{ id: 'minTwoWayInt', minRange: 0, maxRange: 20, step: 1 }, { id: 'minTwoWayInt', minRange: 0, maxRange: 5, step: 1 },
{ id: 'maxTwoWayInt', minRange: 0, maxRange: 20, step: 1 } { id: 'maxTwoWayInt', minRange: 0, maxRange: 5, step: 1 }
], ],
routeTwoWayInternalCatenary: [ routeTwoWayInternalCatenary: [
{ {
id: 'minTwoWayCatenaryInt', id: 'minTwoWayCatenaryInt',
minRange: 0, minRange: 0,
maxRange: 20, maxRange: 5,
step: 1 step: 1
}, },
{ {
id: 'maxTwoWayCatenaryInt', id: 'maxTwoWayCatenaryInt',
minRange: 0, minRange: 0,
maxRange: 20, maxRange: 5,
step: 1 step: 1
} }
] ]
+32 -24
View File
@@ -2,20 +2,7 @@ import { defineStore } from 'pinia';
import { API } from '../typings/api'; import { API } from '../typings/api';
import { Status } from '../typings/common'; import { Status } from '../typings/common';
import { StationJSONData } from './typings'; import { StationJSONData } from './typings';
import { HttpClient } from '../http'; import axios, { AxiosInstance } from 'axios';
let baseURL = 'https://stacjownik.spythere.eu';
switch (import.meta.env.VITE_API_MODE) {
case 'development':
baseURL = 'http://localhost:3001';
break;
case 'mocking':
baseURL = 'http://localhost:3123';
break;
default:
break;
}
export const useApiStore = defineStore('apiStore', { export const useApiStore = defineStore('apiStore', {
state: () => ({ state: () => ({
@@ -38,13 +25,30 @@ export const useApiStore = defineStore('apiStore', {
nextUpdateTime: 0, nextUpdateTime: 0,
nextDataCheckTime: 0, nextDataCheckTime: 0,
client: new HttpClient(baseURL), client: undefined as AxiosInstance | undefined,
activeDataScheduler: undefined as number | undefined activeDataScheduler: undefined as number | undefined
}), }),
actions: { actions: {
async setupAPIData() { async setupAPIData() {
let baseURL = 'https://stacjownik.spythere.eu';
switch (import.meta.env.VITE_API_MODE) {
case 'development':
baseURL = 'http://localhost:3001';
break;
case 'mocking':
baseURL = 'http://localhost:3123';
break;
default:
break;
}
this.client = axios.create({
baseURL
});
this.connectToAPI(); this.connectToAPI();
}, },
@@ -78,9 +82,9 @@ export const useApiStore = defineStore('apiStore', {
if (!this.activeData) this.dataStatuses.connection = Status.Data.Loading; if (!this.activeData) this.dataStatuses.connection = Status.Data.Loading;
try { try {
const response = await this.client.get<API.ActiveData.Response>('api/getActiveData'); const response = await this.client!.get<API.ActiveData.Response>('api/getActiveData');
this.activeData = response; this.activeData = response.data;
this.dataStatuses.connection = Status.Data.Loaded; this.dataStatuses.connection = Status.Data.Loaded;
} catch (error) { } catch (error) {
this.dataStatuses.connection = Status.Data.Error; this.dataStatuses.connection = Status.Data.Error;
@@ -90,9 +94,9 @@ export const useApiStore = defineStore('apiStore', {
async fetchDonatorsData() { async fetchDonatorsData() {
try { try {
const response = await this.client.get<API.Donators.Response>('api/getDonators'); const response = await this.client!.get<API.Donators.Response>('api/getDonators');
this.donatorsData = response; this.donatorsData = response.data;
} catch (error) { } catch (error) {
console.error('Ups! Wystąpił błąd podczas pobierania informacji o donatorach:', error); console.error('Ups! Wystąpił błąd podczas pobierania informacji o donatorach:', error);
} }
@@ -100,7 +104,9 @@ export const useApiStore = defineStore('apiStore', {
async fetchStationsGeneralInfo() { async fetchStationsGeneralInfo() {
try { try {
const sceneryData = await this.client.get<StationJSONData[]>(`api/getSceneries`); const sceneryData: StationJSONData[] = (
await this.client!.get<StationJSONData[]>(`api/getSceneries`)
).data;
this.dataStatuses.sceneries = Status.Data.Loaded; this.dataStatuses.sceneries = Status.Data.Loaded;
this.sceneryData = sceneryData; this.sceneryData = sceneryData;
@@ -112,10 +118,10 @@ export const useApiStore = defineStore('apiStore', {
async fetchVehiclesInfo() { async fetchVehiclesInfo() {
try { try {
const response = await this.client.get<API.VehiclesData.Response>('api/getVehiclesData'); const response = await this.client!.get<API.VehiclesData.Response>('api/getVehiclesData');
this.vehiclesData = response; this.vehiclesData = response.data;
this.dataStatuses.vehicles = response ? Status.Data.Loaded : Status.Data.Warning; this.dataStatuses.vehicles = response.data ? Status.Data.Loaded : Status.Data.Warning;
} catch (error) { } catch (error) {
this.dataStatuses.vehicles = Status.Data.Error; this.dataStatuses.vehicles = Status.Data.Error;
console.error('Ups! Wystąpił błąd podczas pobierania informacji o pojazdach:', error); console.error('Ups! Wystąpił błąd podczas pobierania informacji o pojazdach:', error);
@@ -124,7 +130,9 @@ export const useApiStore = defineStore('apiStore', {
async fetchDailyStats() { async fetchDailyStats() {
try { try {
const res = await this.client.get<API.DailyStats.Response>('api/getDailyStats'); const res: API.DailyStats.Response = await (
await this.client!.get('api/getDailyStats')
).data;
this.dailyStatsData = res; this.dailyStatsData = res;
+1 -2
View File
@@ -9,8 +9,7 @@ export const tooltipKeys = [
'SpawnsTooltip', 'SpawnsTooltip',
'UsersTooltip', 'UsersTooltip',
'HtmlTooltip', 'HtmlTooltip',
'TrainInfoTooltip', 'TrainInfoTooltip'
'CreatorTooltip'
] as const; ] as const;
export type TooltipType = (typeof tooltipKeys)[number]; export type TooltipType = (typeof tooltipKeys)[number];
+1
View File
@@ -85,6 +85,7 @@
padding: 0.1em 0.3em; padding: 0.1em 0.3em;
border-radius: 0.2em; border-radius: 0.2em;
font-weight: bold; font-weight: bold;
user-select: none;
&.twr { &.twr {
background-color: var(--clr-twr); background-color: var(--clr-twr);
+1 -1
View File
@@ -78,7 +78,7 @@ h1.option-title {
display: flex; display: flex;
gap: 0.5em; gap: 0.5em;
width: 100%; width: 100%;
margin-top: 1em; margin-top: 0.5em;
button { button {
width: 100%; width: 100%;
+2 -1
View File
@@ -26,7 +26,7 @@
.dropdown_wrapper { .dropdown_wrapper {
position: absolute; position: absolute;
left: 0; left: 0;
top: 0; top: calc(100% + 0.5em);
background-color: var(--clr-bg3); background-color: var(--clr-bg3);
box-shadow: 0 0 5px 1px var(--clr-primary); box-shadow: 0 0 5px 1px var(--clr-primary);
@@ -34,6 +34,7 @@
width: 100%; width: 100%;
max-width: 550px; max-width: 550px;
max-height: 750px;
overflow: auto; overflow: auto;
padding: 1em; padding: 1em;
-13
View File
@@ -217,19 +217,6 @@ ul {
text-shadow: #f050ff 0 0 10px; text-shadow: #f050ff 0 0 10px;
} }
&--creator {
color: var(--clr-primary);
color: transparent;
background: var(--clr-primary);
background: linear-gradient(90deg, gold 30%, #ffffff 70%);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: gold 0 0 10px;
}
&--discord { &--discord {
color: var(--clr-donator); color: var(--clr-donator);
color: transparent; color: transparent;
+2 -1
View File
@@ -24,8 +24,8 @@
width: 100%; width: 100%;
margin: 0 auto; margin: 0 auto;
padding: 1em 0; padding: 1em 0;
position: relative;
} }
.journal_refreshed-date { .journal_refreshed-date {
@@ -57,6 +57,7 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
gap: 0.5em; gap: 0.5em;
position: relative;
} }
.btn--load-data { .btn--load-data {
+1
View File
@@ -15,6 +15,7 @@
gap: 0.25em; gap: 0.25em;
min-width: 200px; min-width: 200px;
margin-right: 0.25em;
} }
&-input { &-input {
+1 -3
View File
@@ -253,10 +253,8 @@ export namespace API {
pn?: number; pn?: number;
tn?: number; tn?: number;
headUnitName?: string;
headUnitType?: string;
returnType?: 'all' | 'short' | 'detailed'; returnType?: 'all' | 'short' | 'detailed';
sortBy?: Journal.TimetableSorter['id']; sortBy?: Journal.TimetableSorter['id'];
} }
-3
View File
@@ -1,3 +0,0 @@
export function isCreator(name: string) {
return /(spythere|kowbojyt)/.test(name.toLowerCase());
}
+6 -8
View File
@@ -217,10 +217,9 @@ export default defineComponent({
this.scrollDataLoaded = false; this.scrollDataLoaded = false;
this.currentQueryParams['countFrom'] = this.historyList.length; this.currentQueryParams['countFrom'] = this.historyList.length;
const responseData: API.DispatcherHistory.Response = await await this.apiStore.client.get( const responseData: API.DispatcherHistory.Response = await (
`api/getDispatchers`, await this.apiStore.client!.get(`api/getDispatchers`, { params: this.currentQueryParams })
this.currentQueryParams ).data;
);
if (!responseData) return; if (!responseData) return;
@@ -277,10 +276,9 @@ export default defineComponent({
this.currentQueryParams = queryParams; this.currentQueryParams = queryParams;
try { try {
const responseData: API.DispatcherHistory.Response = await this.apiStore.client.get( const responseData: API.DispatcherHistory.Response = await (
`api/getDispatchers`, await this.apiStore.client!.get(`api/getDispatchers`, { params: this.currentQueryParams })
this.currentQueryParams ).data;
);
if (!responseData) { if (!responseData) {
this.dataStatus = Status.Data.Error; this.dataStatus = Status.Data.Error;
+14 -25
View File
@@ -173,9 +173,8 @@ export default defineComponent({
'search-issuedFrom': '', 'search-issuedFrom': '',
'search-via': '', 'search-via': '',
'search-terminatingAt': '', 'search-terminatingAt': '',
'search-headUnit': '', 'select-categoryCode': '',
'search-date-from': '', 'search-date-from': ''
'select-categoryCode': ''
} as Journal.TimetableSearchType); } as Journal.TimetableSearchType);
const countFromIndex = ref(0); const countFromIndex = ref(0);
@@ -278,10 +277,11 @@ export default defineComponent({
this.currentQueryParams['countFrom'] = this.timetableHistory.length; this.currentQueryParams['countFrom'] = this.timetableHistory.length;
const responseData: API.TimetableHistory.Response = await this.apiStore.client.get( const responseData: API.TimetableHistory.Response = await (
'api/getTimetables', await this.apiStore.client!.get('api/getTimetables', {
this.currentQueryParams params: this.currentQueryParams
); })
).data;
if (!responseData) return; if (!responseData) return;
@@ -297,8 +297,6 @@ export default defineComponent({
async fetchHistoryData() { async fetchHistoryData() {
this.extraInfoIndexes.length = 0; this.extraInfoIndexes.length = 0;
const queryParams: API.TimetableHistory.QueryParams = {};
const driverName = this.searchersValues['search-driver'].trim() || undefined; const driverName = this.searchersValues['search-driver'].trim() || undefined;
const trainNo = this.searchersValues['search-train'].trim() || undefined; const trainNo = this.searchersValues['search-train'].trim() || undefined;
const authorName = this.searchersValues['search-dispatcher'].trim() || undefined; const authorName = this.searchersValues['search-dispatcher'].trim() || undefined;
@@ -308,7 +306,6 @@ export default defineComponent({
const via = this.searchersValues['search-via'].trim() || undefined; const via = this.searchersValues['search-via'].trim() || undefined;
const terminatingAt = this.searchersValues['search-terminatingAt'].trim() || undefined; const terminatingAt = this.searchersValues['search-terminatingAt'].trim() || undefined;
const categoryCode = this.searchersValues['select-categoryCode'].trim() || undefined; const categoryCode = this.searchersValues['select-categoryCode'].trim() || undefined;
const headUnit = this.searchersValues['search-headUnit'].trim() || undefined;
let dateFromISO: string | undefined = undefined; let dateFromISO: string | undefined = undefined;
let dateToISO: string | undefined = undefined; let dateToISO: string | undefined = undefined;
@@ -324,6 +321,8 @@ export default defineComponent({
dateToISO = dateTo.toISOString(); dateToISO = dateTo.toISOString();
} }
const queryParams: API.TimetableHistory.QueryParams = {};
this.filterList this.filterList
.filter((f) => f.isActive) .filter((f) => f.isActive)
.forEach((f) => { .forEach((f) => {
@@ -395,27 +394,17 @@ export default defineComponent({
queryParams['sortBy'] = queryParams['sortBy'] =
this.sorterActive.id != 'timetableId' ? this.sorterActive.id : undefined; this.sorterActive.id != 'timetableId' ? this.sorterActive.id : undefined;
// Head unit params
if (headUnit) {
const [headUnitName, headUnitNumber] = headUnit.split('-');
if (headUnitNumber && !isNaN(Number(headUnitNumber))) {
queryParams['headUnitName'] = `${headUnitName}-${headUnitNumber}`;
} else {
queryParams['headUnitType'] = headUnitName;
}
}
if (JSON.stringify(this.currentQueryParams) != JSON.stringify(queryParams)) if (JSON.stringify(this.currentQueryParams) != JSON.stringify(queryParams))
this.dataStatus = Status.Data.Loading; this.dataStatus = Status.Data.Loading;
this.currentQueryParams = queryParams; this.currentQueryParams = queryParams;
try { try {
const responseData: API.TimetableHistory.ResponseShort = await this.apiStore.client.get( const responseData: API.TimetableHistory.ResponseShort = await (
'api/getTimetables', await this.apiStore.client!.get('api/getTimetables', {
this.currentQueryParams params: this.currentQueryParams
); })
).data;
if (!responseData) { if (!responseData) {
this.dataStatus = Status.Data.Error; this.dataStatus = Status.Data.Error;
+20 -16
View File
@@ -40,6 +40,7 @@ import Loading from '../components/Global/Loading.vue';
import ProfileSummary from '../components/PlayerProfileView/ProfileSummary.vue'; import ProfileSummary from '../components/PlayerProfileView/ProfileSummary.vue';
import ProfileRecentStats from '../components/PlayerProfileView/ProfileRecentStats.vue'; import ProfileRecentStats from '../components/PlayerProfileView/ProfileRecentStats.vue';
import ProfileHistoryList from '../components/PlayerProfileView/ProfileHistoryList.vue'; import ProfileHistoryList from '../components/PlayerProfileView/ProfileHistoryList.vue';
import axios from 'axios';
const { t } = useI18n(); const { t } = useI18n();
const router = useRouter(); const router = useRouter();
@@ -70,28 +71,29 @@ onDeactivated(() => {
}); });
async function fetchPlayerInfo(playerId: number) { async function fetchPlayerInfo(playerId: number) {
return apiStore.client.get<API.PlayerInfo.Data>('api/getPlayerInfo', { return apiStore.client!.get<API.PlayerInfo.Data>('api/getPlayerInfo', {
params: {
playerId playerId
}
}); });
} }
async function fetchPlayerJournal(playerId: number) { async function fetchPlayerJournal(playerId: number) {
return apiStore.client.get<API.PlayerJournal.Data>('api/getPlayerJournal', { return apiStore.client!.get<API.PlayerJournal.Data>('api/getPlayerJournal', {
params: {
playerId, playerId,
dateScope: '30d' dateScope: '30d'
}
}); });
} }
async function fetchPlayerTd2Info(playerName: string): Promise<Td2API.UsersInfoByName.Response> { async function fetchPlayerTd2Info(playerName: string) {
const response = await fetch( return axios.get<Td2API.UsersInfoByName.Response>('https://api.td2.info.pl', {
`https://api.td2.info.pl?method=getUsersInfoByName&name=${playerName}` params: {
); method: 'getUsersInfoByName',
name: playerName
if (!response.ok) {
throw new Error('fetchPlayerTd2Info: could not fetch data');
} }
});
return response.json();
} }
async function fetchPlayerData() { async function fetchPlayerData() {
@@ -114,21 +116,23 @@ async function fetchPlayerData() {
const playerInfoResp = await fetchPlayerInfo(playerId.value); const playerInfoResp = await fetchPlayerInfo(playerId.value);
playerName.value = playerName.value =
playerInfoResp.driverStats.driverName || playerInfoResp.dispatcherStats.dispatcherName || ''; playerInfoResp.data.driverStats.driverName ||
playerInfoResp.data.dispatcherStats.dispatcherName ||
'';
if (!playerName.value) { if (!playerName.value) {
router.push('/'); router.push('/');
return; return;
} }
playerInfo.value = playerName.value ? playerInfoResp : undefined; playerInfo.value = playerName.value ? playerInfoResp.data : undefined;
playerInfoStatus.value = Status.Data.Loaded; playerInfoStatus.value = Status.Data.Loaded;
if (playerName.value) { if (playerName.value) {
const playerTD2InfoResp = await fetchPlayerTd2Info(playerName.value); const playerTD2InfoResp = await fetchPlayerTd2Info(playerName.value);
if (playerTD2InfoResp.success && playerTD2InfoResp.message.length == 1) { if (playerTD2InfoResp.data.success && playerTD2InfoResp.data.message.length == 1) {
playerTD2Info.value = playerTD2InfoResp.message[0]; playerTD2Info.value = playerTD2InfoResp.data.message[0];
} }
} }
} catch (error) { } catch (error) {
@@ -140,7 +144,7 @@ async function fetchPlayerData() {
try { try {
const playerJournalResp = await fetchPlayerJournal(playerId.value); const playerJournalResp = await fetchPlayerJournal(playerId.value);
playerJournal.value = playerJournalResp; playerJournal.value = playerJournalResp.data;
playerJournalStatus.value = Status.Data.Loaded; playerJournalStatus.value = Status.Data.Loaded;
} catch (error) { } catch (error) {
playerJournal.value = undefined; playerJournal.value = undefined;
+1 -6
View File
@@ -58,7 +58,6 @@ import SceneryDispatchersHistory from '../components/SceneryView/SceneryDispatch
import { useApiStore } from '../store/apiStore'; import { useApiStore } from '../store/apiStore';
import { Status } from '../typings/common'; import { Status } from '../typings/common';
import SceneryTopList from '../components/SceneryView/SceneryTopList.vue';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
@@ -90,10 +89,6 @@ const viewModes = [
{ {
id: 'scenery.option-dispatchers-history', id: 'scenery.option-dispatchers-history',
component: SceneryDispatchersHistory component: SceneryDispatchersHistory
},
{
id: 'scenery.option-top-list',
component: SceneryTopList
} }
]; ];
@@ -189,7 +184,7 @@ function setViewMode(componentName: string) {
background-color: #181818; background-color: #181818;
border-radius: 0.5em; border-radius: 0.5em;
padding: 1em; padding: 0.5em;
} }
.scenery-left { .scenery-left {
+5 -1
View File
@@ -116,10 +116,13 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
@use '../styles/responsive'; @use '../styles/responsive';
.trains-view {
position: relative;
}
.trains_wrapper { .trains_wrapper {
margin: 1rem auto; margin: 1rem auto;
max-width: var(--max-container-width); max-width: var(--max-container-width);
position: relative;
} }
.trains_topbar { .trains_topbar {
@@ -127,6 +130,7 @@ export default defineComponent({
align-items: center; align-items: center;
gap: 0.5em; gap: 0.5em;
position: relative;
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
-15
View File
@@ -1,15 +0,0 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"noUncheckedIndexedAccess": false,
"verbatimModuleSyntax": null,
"paths": {
"@/*": ["./src/*"]
},
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo"
}
}
+17 -4
View File
@@ -1,11 +1,24 @@
{ {
"files": [], "compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"types": ["vite/client", "vite-plugin-pwa/client"],
"skipLibCheck": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [ "references": [
{ {
"path": "./tsconfig.node.json" "path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
} }
] ]
} }
+6 -9
View File
@@ -1,12 +1,9 @@
// TSConfig for modules that run in Node.js environment via either transpilation or type-stripping.
{ {
"extends": "@tsconfig/node24/tsconfig.json",
"include": ["vite.config.*", "eslint.config.*"],
"compilerOptions": { "compilerOptions": {
"module": "preserve", "composite": true,
"moduleResolution": "bundler", "module": "nodenext",
"types": ["node", "vite/client", "vite-plugin-pwa/client"], "moduleResolution": "nodenext",
"noEmit": true, "allowSyntheticDefaultImports": true
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo" },
} "include": ["vite.config.ts"]
} }
+4 -7
View File
@@ -1,7 +1,7 @@
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue'; import vue from '@vitejs/plugin-vue';
import { VitePWA } from 'vite-plugin-pwa'; import { VitePWA } from 'vite-plugin-pwa';
import { fileURLToPath } from 'node:url'; import path from 'path';
export default defineConfig({ export default defineConfig({
server: { port: 5123, open: false }, server: { port: 5123, open: false },
@@ -14,7 +14,7 @@ export default defineConfig({
}, },
resolve: { resolve: {
alias: { alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)) '@': path.resolve(__dirname, 'src')
} }
}, },
plugins: [ plugins: [
@@ -29,13 +29,10 @@ export default defineConfig({
{ {
urlPattern: urlPattern:
/^https:\/\/stacjownik.spythere.eu\/api\/(getVehiclesData|getDonators|getSceneries)/i, /^https:\/\/stacjownik.spythere.eu\/api\/(getVehiclesData|getDonators|getSceneries)/i,
handler: 'StaleWhileRevalidate', handler: 'CacheFirst',
options: { options: {
cacheName: 'stacjownik-api-cache', cacheName: 'stacjownik-api-cache',
cacheableResponse: { statuses: [0, 200] }, cacheableResponse: { statuses: [0, 200] }
expiration: {
maxAgeSeconds: 3600
}
} }
} }
] ]
+1500 -1501
View File
File diff suppressed because it is too large Load Diff