Compare commits

...

24 Commits

Author SHA1 Message Date
Spythere a44ad5c89d Merge pull request #128 from Spythere/development
v1.30.0
2025-04-26 15:11:48 +02:00
Spythere c30c2206ce chore: available proposition categories update 2025-04-26 15:06:08 +02:00
Spythere 128f3c32b4 fix: minor mistakes 2025-04-26 14:34:26 +02:00
Spythere 0fdcd82754 hotfix: available categories conditions 2025-04-26 14:33:07 +02:00
Spythere 1e75ff517f hotfix: missing translations 2025-04-26 14:24:25 +02:00
Spythere b278c20480 feat: filtering journal timetables by train category 2025-04-26 14:23:14 +02:00
Spythere fd28eb4609 fix: other driver's trains broken listing 2025-04-26 13:45:27 +02:00
Spythere a602358241 chore: obsolete logs 2025-04-25 15:18:11 +02:00
Spythere 5a09543a22 chore: updated stock speed limit calculation 2025-04-25 15:15:45 +02:00
Spythere f952a7c491 chore: translation fixes; formatting 2025-04-25 14:56:39 +02:00
Spythere adf4d88cb2 bump: v1.30.0 2025-04-22 15:56:34 +02:00
Spythere 34f2a69863 feat: displaying number propositions for selected category in driver view 2025-04-22 15:56:02 +02:00
Spythere b2930f6a9e restruct: added driver view components 2025-04-19 14:51:42 +02:00
Spythere edcaff2183 Merge pull request #127 from Spythere/development
fix: missing icons & translations
2025-04-15 16:11:13 +02:00
Spythere 010ab08701 fix: missing icons & translations 2025-04-15 16:10:44 +02:00
Spythere 16b3bb3683 Merge pull request #126 from Spythere/development
v1.29.2
2025-04-15 01:15:42 +02:00
Spythere 93e242c0f5 chore: additional styles 2025-04-15 01:10:31 +02:00
Spythere 861206a5ab chore: updated look of station stats; minor layout improvements 2025-04-14 21:37:59 +02:00
Spythere a47399fe1b chore: added driver stats percentage 2025-04-14 21:02:46 +02:00
Spythere 8e196c8279 chore: switched drivers filter from text to select input (TrainsView); updated inputs clear buttons 2025-04-14 19:24:28 +02:00
Spythere be55bac9fe chore: updated networking settings 2025-04-14 19:24:24 +02:00
Spythere c5e53057eb hotfix: post-upgrade adjustments 2025-03-29 16:08:34 +01:00
Spythere 4ba5d544af bump: v1.29.2 2025-03-26 18:04:19 +01:00
Spythere 22b6177560 restruct: updated sass version and rules 2025-03-26 18:04:00 +01:00
82 changed files with 3409 additions and 2871 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "stacjownik", "name": "stacjownik",
"version": "1.29.1", "version": "1.30.0",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -26,7 +26,7 @@
"vue-router": "^4.4.0" "vue-router": "^4.4.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.14.12", "@types/node": "^22.13.13",
"@types/showdown": "^2.0.6", "@types/showdown": "^2.0.6",
"@vite-pwa/assets-generator": "^0.2.4", "@vite-pwa/assets-generator": "^0.2.4",
"@vitejs/plugin-vue": "^5.1.0", "@vitejs/plugin-vue": "^5.1.0",
+2 -21
View File
@@ -167,32 +167,13 @@ export default defineComponent({
</script> </script>
<style lang="scss"> <style lang="scss">
@import './styles/global'; @use './styles/animations';
@import './styles/animations';
.route {
margin: 0 0.2em;
&-active,
&[data-active='true'] {
color: $accentCol;
font-weight: bold;
}
}
// APP // APP
#app { #app {
color: white; color: white;
font-size: 1rem;
overflow-x: hidden; overflow-x: hidden;
font-size: 1em;
@include smallScreen() {
font-size: calc(0.65rem + 0.85vw);
}
@include screenLandscape() {
font-size: calc(0.45rem + 0.8vw);
}
} }
// CONTAINER // CONTAINER
+1 -3
View File
@@ -36,6 +36,4 @@ export default defineComponent({
} }
} }
}); });
</script> </script>
<style scoped></style>
+9 -9
View File
@@ -45,17 +45,17 @@
</span> </span>
<span class="header_links"> <span class="header_links">
<router-link class="route" active-class="route-active" to="/" exact> <router-link class="route-link" active-class="route-link-active" to="/" exact>
{{ $t('app.sceneries') }} {{ $t('app.sceneries') }}
</router-link> </router-link>
/ /
<router-link class="route" active-class="route-active" to="/trains">{{ <router-link class="route-link" active-class="route-link-active" to="/trains">{{
$t('app.trains') $t('app.trains')
}}</router-link> }}</router-link>
/ /
<router-link <router-link
class="route" class="route-link"
active-class="route-active" active-class="route-link-active"
:data-active="$route.path.startsWith('/journal')" :data-active="$route.path.startsWith('/journal')"
to="/journal" to="/journal"
> >
@@ -116,9 +116,9 @@ export default defineComponent({
} }
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/variables.scss'; @use '../../styles/responsive';
@import '../../styles/responsive.scss';
// HEADER // HEADER
.app_header { .app_header {
@@ -126,7 +126,7 @@ export default defineComponent({
justify-content: center; justify-content: center;
position: relative; position: relative;
background-color: $primaryCol; background-color: #2c2c2c;
} }
.header { .header {
@@ -141,7 +141,7 @@ export default defineComponent({
border-radius: 0 0 1em 1em; border-radius: 0 0 1em 1em;
@include smallScreen { @include responsive.smallScreen{
position: relative; position: relative;
margin-top: 0.5em; margin-top: 0.5em;
} }
@@ -180,7 +180,7 @@ export default defineComponent({
padding: 0.5em; padding: 0.5em;
@include smallScreen { @include responsive.smallScreen{
transform: translateX(85%); transform: translateX(85%);
} }
} }
+1 -5
View File
@@ -6,9 +6,7 @@
import { computed, defineComponent, ref } from 'vue'; import { computed, defineComponent, ref } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'VueClock', name: 'VueClock',
data: () => ({ data: () => ({ timestamp: Date.now() }),
timestamp: Date.now()
}),
setup() { setup() {
let timestamp = ref(Date.now()); let timestamp = ref(Date.now());
@@ -28,8 +26,6 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/responsive.scss';
.clock { .clock {
display: flex; display: flex;
align-items: center; align-items: center;
+3 -3
View File
@@ -310,7 +310,7 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/responsive.scss'; @use '../../styles/responsive';
// INDICATOR TOOLTIP ANIMATION // INDICATOR TOOLTIP ANIMATION
.tooltip-anim { .tooltip-anim {
@@ -379,7 +379,7 @@ export default defineComponent({
content: ''; content: '';
} }
@include midScreen() { @include responsive.midScreen() {
left: auto; left: auto;
right: 200%; right: 200%;
@@ -393,7 +393,7 @@ export default defineComponent({
} }
} }
@include smallScreen() { @include responsive.smallScreen{
min-width: 8em; min-width: 8em;
} }
} }
+1 -3
View File
@@ -73,11 +73,9 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/variables';
::v-deep(h1) { ::v-deep(h1) {
text-align: center; text-align: center;
color: $accentCol; color: var(--clr-primary);
} }
::v-deep(h2) { ::v-deep(h2) {
@@ -0,0 +1,81 @@
<template>
<div class="driver-not-found">
<h2>&olcross; {{ $t('trains.driver-not-found-header') }}</h2>
<p class="text--grayed">
{{ $t('trains.driver-not-found-desc-1') }} <br />
{{ $t('trains.driver-not-found-desc-2') }}
<router-link to="/journal/timetables"
>{{ $t('trains.driver-not-found-journal') }} </router-link
>!
</p>
<p v-if="props.trainId && otherDriverTrains.length > 0">
<i18n-t keypath="trains.driver-not-found-others">
<template v-slot:driver>
<b>{{ otherDriverTrains[0].driverName }}</b>
</template>
</i18n-t>
</p>
<div class="other-driver-trains">
<template v-for="(train, i) in otherDriverTrains">
<router-link :to="`/driver?trainId=${train.id}`">
{{ train.trainNo }}
| {{ regions.find((r) => r.id == train.region)?.name ?? 'PL1' }}
</router-link>
</template>
</div>
<div style="margin-top: 1em">
<router-link to="/">&lt;&lt; {{ $t('trains.driver-not-found-return') }}</router-link>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { useMainStore } from '../../store/mainStore';
import { regions } from '../../data/options.json';
const mainStore = useMainStore();
const props = defineProps({
trainId: {
type: String
}
});
const otherDriverTrains = computed(() => {
return mainStore.trainList.filter(
(train) =>
train.driverId == Number(props.trainId?.split('|')[0]) &&
(train.timetableData || train.online || train.lastSeen >= Date.now() - 60000)
);
});
</script>
<style lang="scss" scoped>
.driver-not-found {
background-color: var(--clr-view-bg);
text-align: center;
padding: 1em;
border-radius: 0.5em 0.5em;
p {
padding: 0.5em 0;
}
a {
text-decoration: underline;
color: white;
}
}
.other-driver-trains {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 0.5em;
}
</style>
@@ -0,0 +1,85 @@
<template>
<div class="driver-top-actions">
<div class="actions-container">
<div class="actions actions-left">
<button class="a-button btn--filled btn--image" @click="routerReturn">
<img src="/images/icon-back.svg" alt="train icon" />
<span>
{{ t('trains.driver-return-link') }}
</span>
</button>
</div>
<div class="actions actions-right">
<a class="a-button btn--filled btn--image" :href="`https://srjp-td2.web.app/?id=${chosenTrain.id}`"
target="_blank">
<span class="hidable">
{{ t('trains.driver-srjp-link') }}
</span>
<img src="/images/icon-srjp.svg" alt="srjp icon" />
</a>
<router-link :to="`/journal/timetables?search-driver=${chosenTrain.driverName}`"
class="a-button btn--filled btn--image">
<span class="hidable">
{{ t('trains.driver-journal-link') }}
</span>
<img src="/images/icon-train.svg" alt="train icon" />
</router-link>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { Train } from '../../typings/common';
import { PropType } from 'vue';
const router = useRouter();
const { t } = useI18n();
defineProps({
chosenTrain: {
type: Object as PropType<Train>,
required: true
}
});
function routerReturn() {
router.back();
}
</script>
<style lang="scss" scoped>
@use '../../styles/responsive';
.actions-container {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 0.5em;
}
.actions {
display: flex;
gap: 0.5em;
}
.actions-container>.actions>.a-button {
padding: 0.5em;
border-radius: 0.5em 0.5em 0 0;
}
@include responsive.smallScreen {
span.hidable {
display: none;
}
}
</style>
@@ -0,0 +1,328 @@
<template>
<div class="driver-train-card">
<TrainInfo :train="chosenTrain" :extended="true" />
<!-- Train action buttons -->
<div class="train-stock-actions">
<button class="btn btn--action" style="margin: 1em 0" @click="copyStockToClipboard()">
<i class="fa-regular fa-copy"></i> {{ i18n.t('trains.stock-copy') }}
</button>
<button class="btn btn--action" style="margin: 1em 0" @click="toggleNumberPropositions()">
<i class="fa-regular fa-lightbulb"></i> {{ i18n.t('trains.number-propositions') }}
</button>
</div>
<!-- Proposed numbers container -->
<transition name="view-anim" class="propositions-container">
<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>
<StockList :trainStockList="chosenTrain.stockList" />
<TrainSchedule :train="chosenTrain" />
</div>
</template>
<script setup lang="ts">
import { PropType, ref } from 'vue';
import { Train } from '../../typings/common';
import { useI18n } from 'vue-i18n';
import { useApiStore } from '../../store/apiStore';
import StockList from '../Global/StockList.vue';
import TrainSchedule from '../TrainsView/TrainSchedule.vue';
import TrainInfo from '../TrainsView/TrainInfo.vue';
import rulesJSON from '../../data/trainNumberRules.json';
import { computed } from 'vue';
import { watch } from 'vue';
const apiStore = useApiStore();
const i18n = useI18n();
const arePropositionsVisible = ref(false);
const chosenCategoryIndex = ref(0);
const numberPropositions = ref<string[]>([]);
const chosenCategoryRules = ref<any[]>([]);
const props = defineProps({
chosenTrain: {
type: Object as PropType<Train>,
required: true
}
});
function copyStockToClipboard() {
const stockString = props.chosenTrain.stockList.join(';');
if (!stockString) {
alert(i18n.t('trains.stock-clipboard-failure'));
return;
}
navigator.clipboard
.writeText(stockString)
.then(() => {
prompt(i18n.t('trains.stock-clipboard-success'), stockString);
})
.catch(() => {
alert(i18n.t('trains.stock-clipboard-failure'));
});
}
function toggleNumberPropositions() {
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?.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>
<style lang="scss" scoped>
@use '../../styles/responsive';
.driver-train-card {
padding: 1em;
background-color: var(--clr-view-bg);
border-radius: 0 0 0.5em 0.5em;
}
.train-stock-actions {
display: flex;
gap: 0.5em;
}
.propositions-container {
margin-bottom: 1em;
padding: 0.5em;
background-color: #111;
}
.categories-select {
display: inline-flex;
flex-wrap: wrap;
gap: 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;
}
@include responsive.smallScreen {
.propositions-container {
text-align: center;
}
.categories-select {
justify-content: center;
}
}
</style>
+1 -2
View File
@@ -13,8 +13,7 @@ export default defineComponent({});
</script> </script>
<style lang="scss"> <style lang="scss">
@import '../../styles/variables'; @use '../../styles/responsive';
@import '../../styles/responsive';
.button_content { .button_content {
display: flex; display: flex;
-2
View File
@@ -38,5 +38,3 @@ export default defineComponent({
} }
}); });
</script> </script>
<style scoped></style>
+2 -2
View File
@@ -43,7 +43,7 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/responsive.scss'; @use '../../styles/responsive';
.card { .card {
position: fixed; position: fixed;
@@ -85,7 +85,7 @@ export default defineComponent({
overflow: auto; overflow: auto;
} }
@include smallScreen { @include responsive.smallScreen{
.card { .card {
align-items: flex-start; align-items: flex-start;
} }
+6 -12
View File
@@ -150,8 +150,6 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/responsive.scss';
.body { .body {
display: grid; display: grid;
grid-template-rows: 1fr auto; grid-template-rows: 1fr auto;
@@ -200,30 +198,26 @@ a.discord {
.actions-container > .action { .actions-container > .action {
&.paypal { &.paypal {
$btnColor: #254069; background-color: #254069;
background-color: $btnColor;
&:hover { &:hover {
background-color: lighten($btnColor, 5%); background-color: #2f5185;
} }
} }
&.coffee { &.coffee {
$btnColor: #009255; background-color: #009255;
background-color: $btnColor;
&:hover { &:hover {
background-color: lighten($btnColor, 5%); background-color: #00a35f;
} }
} }
&.exit { &.exit {
$btnColor: #686868; background-color: #686868;
background-color: $btnColor;
&:hover { &:hover {
background-color: lighten($btnColor, 5%); background-color: #8d8d8d;
} }
} }
} }
+1 -3
View File
@@ -120,8 +120,6 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/variables.scss';
.region-dropdown { .region-dropdown {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -182,7 +180,7 @@ li.option {
background: none; background: none;
&:focus + span { &:focus + span {
color: $accentCol; color: var(--clr-primary);
font-weight: 800; font-weight: 800;
} }
} }
+13 -23
View File
@@ -7,7 +7,12 @@
@keypress="updateValue" @keypress="updateValue"
/> />
<img class="search-exit" src="/images/icon-exit.svg" alt="exit-icon" @click="clearSearchValue" /> <img
class="search-exit"
src="/images/icon-exit.svg"
alt="exit-icon"
@click="clearSearchValue"
/>
</div> </div>
</template> </template>
@@ -17,21 +22,10 @@ import { defineComponent, ref, watch } from 'vue';
export default defineComponent({ export default defineComponent({
emits: ['update:searchedValue', 'clearValue'], emits: ['update:searchedValue', 'clearValue'],
props: { props: {
searchedValue: { searchedValue: { type: String, required: true },
type: String, updateOnInput: { type: Boolean, default: true },
required: true titleToTranslate: { type: String, required: true },
}, clearValue: { type: Function }
updateOnInput: {
type: Boolean,
default: true
},
titleToTranslate: {
type: String,
required: true
},
clearValue: {
type: Function
}
}, },
setup(props, { emit }) { setup(props, { emit }) {
@@ -56,17 +50,13 @@ export default defineComponent({
emit('update:searchedValue', compSearchedValue.value); emit('update:searchedValue', compSearchedValue.value);
}; };
return { return { compSearchedValue, updateValue, clearSearchValue };
compSearchedValue,
updateValue,
clearSearchValue
};
} }
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/responsive'; @use '../../styles/responsive';
.search { .search {
&-box { &-box {
@@ -78,7 +68,7 @@ export default defineComponent({
margin: 0.5em 0 0.5em 0.5em; margin: 0.5em 0 0.5em 0.5em;
@include smallScreen() { @include responsive.smallScreen{
width: 85%; width: 85%;
} }
} }
+3 -9
View File
@@ -20,15 +20,9 @@ import { Status } from '../../typings/common';
export default defineComponent({ export default defineComponent({
props: { props: {
dispatcherStatus: { dispatcherStatus: { type: Number as PropType<Status.ActiveDispatcher | number> },
type: Number as PropType<Status.ActiveDispatcher | number> dispatcherTimestamp: { type: Number as PropType<number | null> },
}, isOnline: { type: Boolean }
dispatcherTimestamp: {
type: Number as PropType<number | null>
},
isOnline: {
type: Boolean
}
}, },
mixins: [dateMixin], mixins: [dateMixin],
+3 -12
View File
@@ -22,20 +22,12 @@ export default defineComponent({
components: { VehicleThumbnail }, components: { VehicleThumbnail },
props: { props: {
trainStockList: { trainStockList: { type: Array as PropType<string[]>, required: true },
type: Array as PropType<string[]>, tractionOnly: { type: Boolean, required: false }
required: true
},
tractionOnly: {
type: Boolean,
required: false
}
}, },
data() { data() {
return { return { apiStore: useApiStore() };
apiStore: useApiStore()
};
}, },
computed: { computed: {
@@ -151,7 +143,6 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.list-wrapper { .list-wrapper {
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -51,7 +51,6 @@ function onImageLoad() {
<style lang="scss" scoped> <style lang="scss" scoped>
.vehicle-thumbnail { .vehicle-thumbnail {
position: relative; position: relative;
opacity: 0; opacity: 0;
transition: opacity 100ms ease-in-out; transition: opacity 100ms ease-in-out;
@@ -234,9 +234,9 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/responsive.scss'; @use '../../styles/animations';
@import '../../styles/JournalStats.scss'; @use '../../styles/journal-stats';
@import '../../styles/badge.scss'; @use '../../styles/responsive';
.daily-stats { .daily-stats {
text-align: left; text-align: left;
@@ -265,7 +265,7 @@ ul.stats-list {
gap: 0.5em; gap: 0.5em;
} }
@include smallScreen { @include responsive.smallScreen{
h3 { h3 {
text-align: center; text-align: center;
} }
@@ -121,14 +121,8 @@ import StationStatusBadge from '../../Global/StationStatusBadge.vue';
export default defineComponent({ export default defineComponent({
props: { props: {
entry: { entry: { type: Object as PropType<API.DispatcherHistory.Data>, required: true },
type: Object as PropType<API.DispatcherHistory.Data>, showExtraInfo: { type: Boolean, required: true }
required: true
},
showExtraInfo: {
type: Boolean,
required: true
}
}, },
components: { StationStatusBadge }, components: { StationStatusBadge },
@@ -136,10 +130,7 @@ export default defineComponent({
emits: ['toggleShowExtraInfo'], emits: ['toggleShowExtraInfo'],
data() { data() {
return { return { regions, apiStore: useApiStore() };
regions,
apiStore: useApiStore()
};
}, },
methods: { methods: {
@@ -151,8 +142,8 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../../styles/responsive.scss'; @use '../../../styles/responsive';
@import '../../../styles/badge.scss'; @use '../../../styles/badge';
.region-badge { .region-badge {
padding: 0 0.25em; padding: 0 0.25em;
@@ -207,7 +198,7 @@ export default defineComponent({
border-radius: 1em; border-radius: 1em;
} }
@include smallScreen { @include responsive.smallScreen{
.entry-info { .entry-info {
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
@@ -32,25 +32,25 @@
</span> </span>
</div> </div>
<hr class="section-separator" /> <hr class="section-separator" v-if="stats.issuedTimetables" />
<div class="info-stats"> <div class="info-stats" v-if="stats.issuedTimetables">
<span class="badge stat-badge" v-if="stats.issuedTimetables"> <span class="badge stat-badge">
<span>{{ $t('journal.dispatcher-stats.timetables-count') }}</span> <span>{{ $t('journal.dispatcher-stats.timetables-count') }}</span>
<span>{{ stats.issuedTimetables.count }}</span> <span>{{ stats.issuedTimetables.count }}</span>
</span> </span>
<span class="badge stat-badge" v-if="stats.issuedTimetables"> <span class="badge stat-badge">
<span>{{ $t('journal.dispatcher-stats.timetables-sum') }}</span> <span>{{ $t('journal.dispatcher-stats.timetables-sum') }}</span>
<span>{{ stats.issuedTimetables.distanceSum.toFixed(2) }}km</span> <span>{{ stats.issuedTimetables.distanceSum.toFixed(2) }}km</span>
</span> </span>
<span class="badge stat-badge" v-if="stats.issuedTimetables"> <span class="badge stat-badge">
<span>{{ $t('journal.dispatcher-stats.timetables-max') }}</span> <span>{{ $t('journal.dispatcher-stats.timetables-max') }}</span>
<span>{{ stats.issuedTimetables.distanceMax.toFixed(2) }}km</span> <span>{{ stats.issuedTimetables.distanceMax.toFixed(2) }}km</span>
</span> </span>
<span class="badge stat-badge" v-if="stats.issuedTimetables"> <span class="badge stat-badge">
<span>{{ $t('journal.dispatcher-stats.timetables-avg') }}</span> <span>{{ $t('journal.dispatcher-stats.timetables-avg') }}</span>
<span>{{ stats.issuedTimetables.distanceAvg.toFixed(2) }}km</span> <span>{{ stats.issuedTimetables.distanceAvg.toFixed(2) }}km</span>
</span> </span>
@@ -81,5 +81,5 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../../styles/JournalStats.scss'; @use '../../../styles/journal-stats';
</style> </style>
@@ -104,6 +104,5 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../../styles/variables.scss'; @use '../../../styles/journal-section';
@import '../../../styles/JournalSection.scss';
</style> </style>
+7 -6
View File
@@ -1,11 +1,16 @@
<template> <template>
<section class="journal-header"> <section class="journal-header">
<div class="journal-type-options"> <div class="journal-type-options">
<router-link class="router-link" active-class="route-active" to="/journal/timetables" exact> <router-link
class="route-link"
active-class="route-link-active"
to="/journal/timetables"
exact
>
{{ $t('journal.section-timetables') }} {{ $t('journal.section-timetables') }}
</router-link> </router-link>
&nbsp;&bull;&nbsp; &nbsp;&bull;&nbsp;
<router-link class="router-link" active-class="route-active" to="/journal/dispatchers"> <router-link class="route-link" active-class="route-link-active" to="/journal/dispatchers">
{{ $t('journal.section-dispatchers') }} {{ $t('journal.section-dispatchers') }}
</router-link> </router-link>
</div> </div>
@@ -39,8 +44,4 @@ export default defineComponent({});
display: flex; display: flex;
justify-content: center; justify-content: center;
} }
.router-link.active {
color: gold;
}
</style> </style>
+53 -25
View File
@@ -33,31 +33,56 @@
<h1 class="option-title">{{ $t('options.search-title') }}</h1> <h1 class="option-title">{{ $t('options.search-title') }}</h1>
<div class="search_content"> <div class="search_content">
<div class="search" v-for="(_, propName) in searchersValues" :key="propName"> <div class="search" v-for="(_, propName) in searchersValues" :key="propName">
<label v-if="propName == 'search-date-from'" for="search-date">{{ <!-- Train category select -->
$t(`options.search-${optionsType}-date`) <div v-if="propName.toString() == 'select-categoryCode'">
}}</label> <label for="journalCategoryCode">{{ $t(`options.${propName}`) }}</label>
<div class="search-box"> <div class="search-box">
<input <select
class="search-input" class="search-input"
v-model="searchersValues[propName]" name="journalCategoryCode"
@keydown.enter="searchConfirm" id="journalCategoryCode"
@focus="preventKeyDown = true" v-model="searchersValues[propName]"
@blur="preventKeyDown = false" >
:placeholder="$t(`options.${propName}`)" <option value="">...</option>
:type="propName.toString().startsWith('search-date') ? 'date' : 'text'" <option v-for="categoryName in allCategories" :value="categoryName">
:min="propName.toString().startsWith('search-date') ? '2022-02-01' : undefined" {{ categoryName }} - {{ getCategoryExplanation(categoryName) }}
:id="`${propName}`" </option>
:list="propName.toString()" </select>
/> </div>
</div>
<button class="search-exit" v-if="!propName.toString().startsWith('search-date')"> <!-- Other inputs -->
<img <div v-else>
src="/images/icon-exit.svg" <label v-if="propName == 'search-date-from'" for="search-date">{{
alt="exit-icon" $t(`options.search-${optionsType}-date`)
@click="onInputClear(propName)" }}</label>
<div class="search-box">
<input
class="search-input"
v-model="searchersValues[propName]"
@keydown.enter="searchConfirm"
@focus="preventKeyDown = true"
@blur="preventKeyDown = false"
:placeholder="$t(`options.${propName}`)"
:type="propName.toString().startsWith('search-date') ? 'date' : 'text'"
:min="propName.toString().startsWith('search-date') ? '2022-02-01' : undefined"
:id="`${propName}`"
:list="propName.toString()"
/> />
</button>
<button
class="btn btn--action search-exit"
v-if="!propName.toString().startsWith('search-date')"
>
<img
src="/images/icon-exit.svg"
alt="exit-icon"
@click="onInputClear(propName)"
/>
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -117,10 +142,12 @@ import { useMainStore } from '../../store/mainStore';
import { Journal } from './typings'; import { Journal } from './typings';
import { Status } from '../../typings/common'; import { Status } from '../../typings/common';
import { useApiStore } from '../../store/apiStore'; import { useApiStore } from '../../store/apiStore';
import { allCategories } from '../../data/trainNumberRules.json';
import trainCategoryMixin from '../../mixins/trainCategoryMixin';
export default defineComponent({ export default defineComponent({
emits: ['onSearchConfirm', 'onOptionsReset', 'onRefreshData'], emits: ['onSearchConfirm', 'onOptionsReset', 'onRefreshData'],
mixins: [keyMixin], mixins: [keyMixin, trainCategoryMixin],
props: { props: {
sorterOptionIds: { sorterOptionIds: {
@@ -152,6 +179,7 @@ export default defineComponent({
data() { data() {
return { return {
showOptions: false, showOptions: false,
allCategories,
driverSuggestions: [] as string[], driverSuggestions: [] as string[],
dispatcherSuggestions: [] as string[], dispatcherSuggestions: [] as string[],
@@ -300,6 +328,6 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/dropdown'; @use '../../styles/dropdown';
@import '../../styles/dropdown_filters'; @use '../../styles/dropdown-filters';
</style> </style>
+2 -4
View File
@@ -79,14 +79,12 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/dropdown.scss'; @use '../../styles/dropdown';
@import '../../styles/dropdown_filters.scss'; @use '../../styles/dropdown-filters';
@import '../../styles/variables.scss';
.dropdown_wrapper.dropdown-align-right { .dropdown_wrapper.dropdown-align-right {
left: auto; left: auto;
right: 0; right: 0;
max-width: 700px; max-width: 700px;
// max-width: 100%;
} }
</style> </style>
@@ -225,9 +225,8 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../../styles/variables.scss'; @use '../../../styles/responsive';
@import '../../../styles/responsive.scss'; @use '../../../styles/badge';
@import '../../../styles/badge.scss';
.details-body { .details-body {
margin-top: 0.5em; margin-top: 0.5em;
@@ -250,7 +249,7 @@ export default defineComponent({
margin-top: 1em; margin-top: 1em;
button[data-checked='true'] { button[data-checked='true'] {
color: $accentCol; color: var(--clr-primary);
} }
} }
@@ -272,7 +271,7 @@ export default defineComponent({
span:last-child { span:last-child {
color: black; color: black;
background-color: $accentCol; background-color: var(--clr-primary);
border-radius: 0 0.25em 0.25em 0; border-radius: 0 0.25em 0.25em 0;
} }
} }
@@ -300,7 +299,7 @@ hr {
} }
} }
@include smallScreen() { @include responsive.smallScreen{
.timetable-specs { .timetable-specs {
justify-content: center; justify-content: center;
} }
@@ -130,8 +130,8 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../../styles/responsive'; @use '../../../styles/responsive';
@import '../../../styles/badge'; @use '../../../styles/badge';
.item-general { .item-general {
display: flex; display: flex;
@@ -191,7 +191,7 @@ export default defineComponent({
} }
} }
@include smallScreen { @include responsive.smallScreen{
.item-general { .item-general {
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
@@ -59,7 +59,7 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../../styles/responsive.scss'; @use '../../../styles/responsive';
.entry-status { .entry-status {
display: flex; display: flex;
@@ -67,7 +67,7 @@ export default defineComponent({
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.5em; gap: 0.5em;
@include smallScreen() { @include responsive.smallScreen{
justify-content: center; justify-content: center;
} }
} }
@@ -187,7 +187,7 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../../styles/badge.scss'; @use '../../../styles/badge';
.entry-stops { .entry-stops {
word-wrap: break-word; word-wrap: break-word;
@@ -12,14 +12,6 @@
<hr class="header-separator" /> <hr class="header-separator" />
<div class="info-stats"> <div class="info-stats">
<span class="badge stat-badge">
<span>{{ $t('journal.driver-stats.timetables') }}</span>
<span
>{{ store.driverStatsData._count.fulfilled }} /
{{ store.driverStatsData._count._all }}</span
>
</span>
<span class="badge stat-badge"> <span class="badge stat-badge">
<span>{{ $t('journal.driver-stats.longest-timetable') }}</span> <span>{{ $t('journal.driver-stats.longest-timetable') }}</span>
<span> {{ store.driverStatsData._max.routeDistance.toFixed(2) }}km </span> <span> {{ store.driverStatsData._max.routeDistance.toFixed(2) }}km </span>
@@ -29,12 +21,43 @@
<span>{{ $t('journal.driver-stats.avg-timetable') }}</span> <span>{{ $t('journal.driver-stats.avg-timetable') }}</span>
<span> {{ store.driverStatsData._avg.routeDistance.toFixed(2) }}km </span> <span> {{ store.driverStatsData._avg.routeDistance.toFixed(2) }}km </span>
</span> </span>
</div>
<hr class="section-separator" />
<div class="info-stats">
<span class="badge stat-badge">
<span>{{ $t('journal.driver-stats.timetables') }}</span>
<span>
{{ store.driverStatsData._count.fulfilled }} /
{{ store.driverStatsData._count._all }}
<template v-if="store.driverStatsData._count._all > 0">
({{
(
(store.driverStatsData._count.fulfilled / store.driverStatsData._count._all) *
100
).toFixed(2)
}}%)
</template>
</span>
</span>
<span class="badge stat-badge"> <span class="badge stat-badge">
<span>{{ $t('journal.driver-stats.distance') }}</span> <span>{{ $t('journal.driver-stats.distance') }}</span>
<span> <span>
{{ store.driverStatsData._sum.currentDistance.toFixed(2) }} / {{ store.driverStatsData._sum.currentDistance.toFixed(2) }} /
{{ store.driverStatsData._sum.routeDistance.toFixed(2) }}km {{ store.driverStatsData._sum.routeDistance.toFixed(2) }}km
<template v-if="store.driverStatsData._sum.routeDistance > 0">
({{
(
(store.driverStatsData._sum.currentDistance /
store.driverStatsData._sum.routeDistance) *
100
).toFixed(2)
}}%)
</template>
</span> </span>
</span> </span>
@@ -43,6 +66,16 @@
<span> <span>
{{ store.driverStatsData._sum.confirmedStopsCount }} / {{ store.driverStatsData._sum.confirmedStopsCount }} /
{{ store.driverStatsData._sum.allStopsCount }} {{ store.driverStatsData._sum.allStopsCount }}
<template v-if="store.driverStatsData._sum.allStopsCount > 0">
({{
(
(store.driverStatsData._sum.confirmedStopsCount /
store.driverStatsData._sum.allStopsCount) *
100
).toFixed(2)
}}%)
</template>
</span> </span>
</span> </span>
</div> </div>
@@ -68,5 +101,5 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../../styles/JournalStats.scss'; @use '../../../styles/journal-stats';
</style> </style>
@@ -3,14 +3,14 @@
<!-- General --> <!-- General -->
<EntryGeneral :timetable="timetableEntry" /> <EntryGeneral :timetable="timetableEntry" />
<!-- Route -->
<div class="entry-route">
<b>{{ timetableEntry.route.replace('|', ' - ') }}</b>
</div>
<hr />
<div @click="toggleExtraInfo" style="cursor: pointer"> <div @click="toggleExtraInfo" style="cursor: pointer">
<!-- Route -->
<div class="entry-route">
<b>{{ timetableEntry.route.replace('|', ' - ') }}</b>
</div>
<hr />
<!-- Status --> <!-- Status -->
<EntryStatus :timetable="timetableEntry" /> <EntryStatus :timetable="timetableEntry" />
</div> </div>
@@ -134,15 +134,20 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../../styles/responsive.scss'; @use '../../../styles/responsive';
.timetable-history-entry { .timetable-history-entry {
background-color: #1a1a1a; background-color: #1a1a1a;
padding: 1em; padding: 1em;
} }
@include smallScreen { .entry-route {
display: flex;
}
@include responsive.smallScreen{
.entry-route { .entry-route {
justify-content: center;
text-align: center; text-align: center;
} }
} }
@@ -107,10 +107,11 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../../styles/JournalSection.scss'; @use '../../../styles/animations';
@import '../../../styles/animations.scss'; @use '../../../styles/journal-section';
@use '../../../styles/responsive';
@include smallScreen { @include responsive.smallScreen{
.journal_item-info { .journal_item-info {
text-align: center; text-align: center;
} }
+2 -1
View File
@@ -12,7 +12,8 @@ export namespace Journal {
| 'search-dispatcher' | 'search-dispatcher'
| 'search-issuedFrom' | 'search-issuedFrom'
| 'search-terminatingAt' | 'search-terminatingAt'
| 'search-via'; | 'search-via'
| 'select-categoryCode';
export type TimetableSearchType = { export type TimetableSearchType = {
[key in TimetableSearchKey]: string; [key in TimetableSearchKey]: string;
@@ -148,8 +148,8 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/responsive.scss'; @use '../../styles/responsive';
@import '../../styles/sceneryViewTables.scss'; @use '../../styles/scenery-history-table';
.scenery-dispatchers-history { .scenery-dispatchers-history {
height: 100%; height: 100%;
@@ -194,7 +194,7 @@ export default defineComponent({
color: springgreen; color: springgreen;
} }
@include smallScreen { @include responsive.smallScreen{
.journal-list > div { .journal-list > div {
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
+1 -2
View File
@@ -35,8 +35,7 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/variables.scss'; @use '../../styles/responsive';
@import '../../styles/responsive.scss';
.info-header { .info-header {
margin-top: 1em; margin-top: 1em;
+2 -2
View File
@@ -59,8 +59,8 @@ export default defineComponent({
</script> </script>
<style lang="scss"> <style lang="scss">
@import '../../styles/responsive.scss'; @use '../../styles/responsive';
@import '../../styles/badge.scss'; @use '../../styles/badge';
h3.section-header { h3.section-header {
margin: 0.5em 0; margin: 0.5em 0;
@@ -27,6 +27,4 @@ export default defineComponent({
} }
} }
}); });
</script> </script>
<style scoped></style>
@@ -102,7 +102,7 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../../styles/icons.scss'; @use '../../../styles/icons';
.info-icons { .info-icons {
display: flex; display: flex;
@@ -53,8 +53,6 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../../styles/variables.scss';
ul { ul {
position: relative; position: relative;
} }
@@ -18,7 +18,7 @@
:key="train.id" :key="train.id"
:data-status="status" :data-status="status"
> >
<router-link :to="train.driverRouteLocation" class="a-block"> <router-link :to="train.driverRouteLocation">
<span class="user_train"> {{ train.trainNo }}</span> <span class="user_train"> {{ train.trainNo }}</span>
<span class="user_name"> <span class="user_name">
{{ train.driverName }} {{ train.driverName }}
@@ -64,7 +64,7 @@
</div> </div>
<router-link <router-link
class="timetable-item a-block" class="timetable-item"
v-else v-else
v-for="(row, i) in sceneryTimetables" v-for="(row, i) in sceneryTimetables"
:key="row.train.id + i" :key="row.train.id + i"
@@ -327,9 +327,8 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/responsive.scss'; @use '../../styles/responsive';
@import '../../styles/variables.scss'; @use '../../styles/animations';
@import '../../styles/animations.scss';
.scenery-timetable { .scenery-timetable {
height: 100%; height: 100%;
@@ -436,7 +435,7 @@ export default defineComponent({
&.current { &.current {
font-weight: bold; font-weight: bold;
color: $accentCol; color: var(--clr-primary);
} }
} }
@@ -449,7 +448,7 @@ export default defineComponent({
flex-wrap: wrap; flex-wrap: wrap;
.info-number { .info-number {
color: $accentCol; color: var(--clr-primary);
} }
.info-route { .info-route {
@@ -485,7 +484,7 @@ export default defineComponent({
align-self: center; align-self: center;
font-size: 0.9em; font-size: 0.9em;
color: $accentCol; color: var(--clr-primary);
&::after { &::after {
content: '\027F6'; content: '\027F6';
@@ -502,7 +501,7 @@ export default defineComponent({
font-size: 0.85em; font-size: 0.85em;
} }
@include smallScreen { @include responsive.smallScreen {
.timetable-item { .timetable-item {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
@@ -188,8 +188,8 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/responsive.scss'; @use '../../styles/responsive';
@import '../../styles/sceneryViewTables.scss'; @use '../../styles/scenery-history-table';
.scenery-timetables-history { .scenery-timetables-history {
height: 100%; height: 100%;
+1 -3
View File
@@ -66,8 +66,6 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/variables.scss';
label { label {
position: relative; position: relative;
user-select: none; user-select: none;
@@ -98,7 +96,7 @@ label {
} }
&:focus-visible + span { &:focus-visible + span {
outline: 1px solid $accentCol; outline: 1px solid var(--clr-primary);
} }
} }
} }
@@ -379,10 +379,9 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/responsive'; @use '../../styles/responsive';
@import '../../styles/card'; @use '../../styles/card';
@import '../../styles/animations'; @use '../../styles/animations';
@import '../../styles/variables';
h3.section-header { h3.section-header {
text-align: center; text-align: center;
@@ -432,7 +431,7 @@ h3.section-header {
.card_title { .card_title {
font-size: 2em; font-size: 2em;
font-weight: 700; font-weight: 700;
color: $accentCol; color: var(--clr-primary);
text-align: center; text-align: center;
} }
@@ -453,7 +452,7 @@ h3.section-header {
span { span {
min-width: 120px; min-width: 120px;
font-weight: bold; font-weight: bold;
color: $accentCol; color: var(--clr-primary);
} }
button { button {
@@ -527,7 +526,7 @@ h3.section-header {
} }
&:focus-visible + span { &:focus-visible + span {
outline: 1px solid $accentCol; outline: 1px solid var(--clr-primary);
} }
} }
} }
@@ -575,7 +574,7 @@ h3.section-header {
margin-bottom: 1em; margin-bottom: 1em;
&-value { &-value {
color: $accentCol; color: var(--clr-primary);
padding: 0.1em 0.2em; padding: 0.1em 0.2em;
text-align: center; text-align: center;
} }
@@ -604,14 +603,14 @@ h3.section-header {
border-radius: 50%; border-radius: 50%;
background: white; background: white;
border: 3px solid $accentCol; border: 3px solid var(--clr-primary);
background-color: #333; background-color: #333;
@include smallScreen() { @include responsive.smallScreen{
width: 15px; width: 15px;
height: 15px; height: 15px;
margin-top: -5px; margin-top: -5px;
border: 3px solid $accentCol; border: 3px solid var(--clr-primary);
} }
} }
@@ -622,14 +621,14 @@ h3.section-header {
border-radius: 50%; border-radius: 50%;
background: white; background: white;
border: 4px solid $accentCol; border: 4px solid var(--clr-primary);
cursor: pointer; cursor: pointer;
@include smallScreen() { @include responsive.smallScreen{
width: 1em; width: 1em;
height: 1em; height: 1em;
border: 3px solid $accentCol; border: 3px solid var(--clr-primary);
} }
} }
@@ -659,7 +658,7 @@ h3.section-header {
} }
} }
@include smallScreen { @include responsive.smallScreen{
.slider { .slider {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
+112 -63
View File
@@ -8,58 +8,106 @@
<button class="filter-button btn--filled btn--image" @click="toggleDropdown" ref="button"> <button class="filter-button btn--filled btn--image" @click="toggleDropdown" ref="button">
<img src="/images/icon-stats.svg" alt="Open filters icon" /> <img src="/images/icon-stats.svg" alt="Open filters icon" />
<!-- {{ $t('train-stats.stats-button') }} --> {{ $t('station-stats.stats-button') }}
<span>STATYSTYKI</span>
</button> </button>
<transition name="dropdown-anim"> <transition name="dropdown-anim">
<div class="dropdown_wrapper" v-if="showDropdown"> <div class="dropdown_wrapper" v-if="showDropdown">
<div> <div>
<h1 class="text--primary"> <h1 class="stats-title text--primary">
<img src="/images/icon-stats.svg" alt="Open filters icon" /> <img src="/images/icon-stats.svg" alt="Open filters icon" />
{{ $t('train-stats.title') }} {{ $t('station-stats.title') }}
</h1> </h1>
<hr style="margin: 0.5em 0" /> <hr style="margin: 0.5em 0" />
<ul class="stats-list"> <div v-if="uFactor > -1 || avgTimetableCount > -1 || trackCount.all > 0">
<li> <div class="badges-container">
<span> <div class="badge stat-badge">
{{ $t('station-stats.u-factor') }} <span>
<a {{ $t('station-stats.u-factor') }}
href="https://td2.info.pl/dyskusje/wspolczynnik-ugla-czy-to-ma-sens/msg81011/#msg81011" <a
target="_blank" href="https://td2.info.pl/dyskusje/wspolczynnik-ugla-czy-to-ma-sens/msg81011/#msg81011"
:data-tooltip="$t('station-stats.u-factor-tooltip')" target="_blank"
>(?)</a :data-tooltip="$t('station-stats.u-factor-tooltip')"
>: >(?)</a
</span> >:
<b class="u-factor" :style="calculateFactorStyle()"> </span>
{{ uFactor.toFixed(2) }}
</b> <span>
</li> <b class="u-factor" :style="calculateFactorStyle()">
<li> {{ uFactor >= 0 ? uFactor.toFixed(2) : '---' }}
{{ $t('station-stats.avg-timetable-count') }} </b>
<b>{{ avgTimetableCount.toFixed(2) }}</b> </span>
</li> </div>
<li>
{{ $t('station-stats.single-track-count') }} <div class="badge stat-badge">
<b>{{ trackCount.oneWay }}</b> (<b>{{ trackCount.oneWayElectric }} </b>) <span>{{ $t('station-stats.avg-timetable-count') }}</span>
</li> <span>
<li> <b>{{ avgTimetableCount >= 0 ? avgTimetableCount.toFixed(2) : '---' }}</b>
{{ $t('station-stats.double-track-count') }} </span>
<b>{{ trackCount.twoWay }}</b> </div>
(<b>{{ trackCount.twoWayElectric }} </b>) </div>
</li>
<li> <hr style="margin: 0.5em 0" />
{{ $t('station-stats.cross-sceneries') }}
<b>{{ trackCount.crossTrack }}</b> (<b>{{ trackCount.crossTrackElectric }} </b>) <div class="badges-container">
</li> <div class="badge stat-badge">
<li> <span>{{ $t('station-stats.single-track-count') }}</span>
{{ $t('station-stats.open-spawns') }} <b>{{ spawnCount.passenger }}</b> - PAS / <span>
<b>{{ spawnCount.freight }}</b> - TOW / <b>{{ spawnCount.loco }}</b> - LUZ / <b> {{ trackCount.oneWay }}</b> (<b>{{ trackCount.oneWayElectric }} </b>)
<b>{{ spawnCount.all }}</b> - ALL </span>
</li> </div>
</ul>
<div class="badge stat-badge">
<span>{{ $t('station-stats.double-track-count') }}</span>
<span>
<b>{{ trackCount.twoWay }}</b> (<b>{{ trackCount.twoWayElectric }} </b>)
</span>
</div>
<div class="badge stat-badge">
<span> {{ $t('station-stats.cross-sceneries') }}</span>
<span>
<b>{{ trackCount.crossTrack }}</b> (<b>{{ trackCount.crossTrackElectric }} </b>)
</span>
</div>
</div>
<hr style="margin: 0.5em 0" />
<div class="badges-container">
<div class="badge stat-badge">
<span> {{ $t('station-stats.open-spawns-all') }}</span>
<span>
<b>{{ spawnCount.all }}</b>
</span>
</div>
<div class="badge stat-badge">
<span> {{ $t('station-stats.open-spawns-pas') }}</span>
<span>
<b>{{ spawnCount.passenger }}</b>
</span>
</div>
<div class="badge stat-badge">
<span> {{ $t('station-stats.open-spawns-freight') }}</span>
<span>
<b>{{ spawnCount.freight }}</b>
</span>
</div>
<div class="badge stat-badge">
<span> {{ $t('station-stats.open-spawns-loco') }}</span>
<span>
<b>{{ spawnCount.loco }}</b>
</span>
</div>
</div>
</div>
<div class="no-data" v-else>{{ $t('station-stats.no-stats') }}</div>
</div> </div>
<div tabindex="0" @focus="() => (showDropdown = false)"></div> <div tabindex="0" @focus="() => (showDropdown = false)"></div>
@@ -86,9 +134,9 @@ export default defineComponent({
}, },
calculateFactorStyle() { calculateFactorStyle() {
if (this.uFactor == 0) return ''; if (this.uFactor <= 0) return '';
const norm = this.uFactor == 0 ? 1 : Math.max(Math.min(this.uFactor / 2, 1), 0); const norm = Math.max(Math.min(this.uFactor / 2, 1), 0);
const lerp = 120 * norm; const lerp = 120 * norm;
return `color: hsl(${lerp}, 100%, 60%)`; return `color: hsl(${lerp}, 100%, 60%)`;
@@ -105,7 +153,7 @@ export default defineComponent({
(train) => train.region == this.mainStore.region.id (train) => train.region == this.mainStore.region.id
); );
return activeDispatchers.length != 0 ? activeTrains.length / activeDispatchers.length : 0; return activeDispatchers.length != 0 ? activeTrains.length / activeDispatchers.length : -1;
}, },
avgTimetableCount() { avgTimetableCount() {
@@ -118,7 +166,7 @@ export default defineComponent({
return acc; return acc;
}, 0); }, 0);
if (regionSceneries.length == 0) return 0; if (regionSceneries.length == 0) return -1;
return timetableCountSum / regionSceneries.length; return timetableCountSum / regionSceneries.length;
}, },
@@ -135,6 +183,8 @@ export default defineComponent({
(acc, st) => { (acc, st) => {
const { routes } = st.generalInfo!; const { routes } = st.generalInfo!;
acc.all++;
if ( if (
routes.single.filter((r) => !r.isInternal).length > 0 && routes.single.filter((r) => !r.isInternal).length > 0 &&
routes.double.filter((r) => !r.isInternal).length > 0 routes.double.filter((r) => !r.isInternal).length > 0
@@ -163,7 +213,8 @@ export default defineComponent({
twoWay: 0, twoWay: 0,
twoWayElectric: 0, twoWayElectric: 0,
crossTrack: 0, crossTrack: 0,
crossTrackElectric: 0 crossTrackElectric: 0,
all: 0
} }
); );
}, },
@@ -190,15 +241,18 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/dropdown.scss'; @use '../../styles/dropdown';
@import '../../styles/badge.scss'; @use '../../styles/badge';
@use '../../styles/responsive';
h1 img { h1.stats-title img {
vertical-align: text-bottom; vertical-align: text-bottom;
} }
h3 { .badges-container {
margin: 0.5em 0; display: flex;
flex-wrap: wrap;
gap: 0.5em;
} }
.u-factor { .u-factor {
@@ -219,19 +273,14 @@ h3 {
} }
} }
ul.stats-list { .no-data {
list-style: disc; font-size: 1.1em;
padding-left: 1em; color: #ccc;
margin-top: 1em;
& > li {
margin: 0.25em 0;
}
} }
@include smallScreen { @include responsive.smallScreen {
.filter-button span { h1.stats-title {
display: none; text-align: center;
} }
} }
</style> </style>
+10 -9
View File
@@ -400,9 +400,10 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/responsive.scss'; @use '../../styles/responsive';
@import '../../styles/variables.scss'; @use '../../styles/icons';
@import '../../styles/icons.scss';
@use 'sass:color';
$rowCol: #424242; $rowCol: #424242;
@@ -435,7 +436,7 @@ table {
} }
thead tr { thead tr {
background-color: $bgCol; background-color: var(--clr-bg3);
} }
thead th { thead th {
@@ -477,7 +478,7 @@ table {
} }
padding: 0.5em 0.25em; padding: 0.5em 0.25em;
background-color: $bgCol; background-color: var(--clr-bg3);
white-space: pre-wrap; white-space: pre-wrap;
cursor: pointer; cursor: pointer;
@@ -504,13 +505,13 @@ tr,
vertical-align: middle; vertical-align: middle;
&:nth-child(even) { &:nth-child(even) {
background-color: lighten($rowCol, 5); background-color: color.adjust($rowCol, $lightness: 5%);
color: white; color: white;
} }
&:hover, &:hover,
&:focus { &:focus {
background-color: lighten($rowCol, 20); background-color: color.adjust($rowCol, $lightness: 15%);
} }
td { td {
@@ -524,7 +525,7 @@ tr,
opacity: 0.2; opacity: 0.2;
} }
@include smallScreen() { @include responsive.smallScreen{
margin: 0; margin: 0;
padding: 0.3em 0.5em; padding: 0.3em 0.5em;
font-size: 1em; font-size: 1em;
@@ -537,7 +538,7 @@ tr,
max-width: 200px; max-width: 200px;
&.default { &.default {
color: $accentCol; color: var(--clr-primary);
} }
&.nonPublic { &.nonPublic {
+15 -7
View File
@@ -219,15 +219,22 @@ export default defineComponent({
stockSpeedLimit() { stockSpeedLimit() {
let isPassenger = true; let isPassenger = true;
const vehicleMaxSpeed = this.train.stockList.reduce((acc, stockName) => { const vehicleMaxSpeed = this.train.stockList.reduce((acc, stockName, i) => {
const vehicleData = this.apiStore.vehiclesData?.find( const [vehicleName, vehicleCargo] = stockName.split(':');
(v) => v.name == stockName.split(':')[0]
); const vehicleData = this.apiStore.vehiclesData?.find((v) => v.name == vehicleName);
if (!vehicleData) return acc; if (!vehicleData) return acc;
if (vehicleData.type == 'wagon-freight') isPassenger = false;
const vehicleSpeed = vehicleData.group.speed; let vehicleSpeed = vehicleData.group.speed;
if (vehicleData.type == 'wagon-freight') {
isPassenger = false;
if (vehicleCargo !== undefined && vehicleData.group.speedLoaded) {
vehicleSpeed = vehicleData.group.speedLoaded;
}
}
return Math.min(vehicleSpeed, acc); return Math.min(vehicleSpeed, acc);
}, Infinity); }, Infinity);
@@ -264,7 +271,7 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/badge.scss'; @use '../../styles/badge';
.image-warning { .image-warning {
height: 1em; height: 1em;
@@ -297,6 +304,7 @@ export default defineComponent({
.train-info { .train-info {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
font-size: 1em;
gap: 0.25em; gap: 0.25em;
background-color: #1a1a1a; background-color: #1a1a1a;
+29 -22
View File
@@ -24,31 +24,26 @@
@blur="preventKeyDown = false" @blur="preventKeyDown = false"
:placeholder="$t(`options.search-train`)" :placeholder="$t(`options.search-train`)"
/> />
<button class="search-exit"> <button class="btn btn--action search-exit" @click="onInputClear('train')">
<img <img src="/images/icon-exit.svg" alt="Trains search clear icon" />
src="/images/icon-exit.svg"
alt="Trains search clear icon"
@click="onInputClear('train')"
/>
</button> </button>
</div> </div>
<div class="search-box"> <div class="search-box">
<input <select
v-model="searchedDriver"
class="search-input" class="search-input"
id="driver-search" name="active-trains"
name="driver-search" id="active-trains"
@focus="preventKeyDown = true" v-model="searchedDriver"
@blur="preventKeyDown = false" >
:placeholder="$t(`options.search-driver`)" <option value="">{{ $t('options.select-driver') }}</option>
/> <option v-for="driverName in activeDriverNames" :value="driverName">
<button class="search-exit"> {{ driverName }}
<img </option>
src="/images/icon-exit.svg" </select>
alt="Driver search clear icon"
@click="onInputClear('driver')" <button class="btn btn--action search-exit" @click="onInputClear('driver')">
/> <img src="/images/icon-exit.svg" alt="Trains search clear icon" />
</button> </button>
</div> </div>
</div> </div>
@@ -101,6 +96,7 @@
import { defineComponent, inject, PropType } from 'vue'; import { defineComponent, inject, PropType } from 'vue';
import keyMixin from '../../mixins/keyMixin'; import keyMixin from '../../mixins/keyMixin';
import { TrainFilter, TrainFilterSection } from './typings'; import { TrainFilter, TrainFilterSection } from './typings';
import { useMainStore } from '../../store/mainStore';
export default defineComponent({ export default defineComponent({
mixins: [keyMixin], mixins: [keyMixin],
@@ -120,6 +116,7 @@ export default defineComponent({
data() { data() {
return { return {
showOptions: false, showOptions: false,
store: useMainStore(),
lastSelectedFilter: null as TrainFilter | null, lastSelectedFilter: null as TrainFilter | null,
TrainFilterSection TrainFilterSection
}; };
@@ -141,6 +138,16 @@ export default defineComponent({
id, id,
value: this.$t(`options.sort-${id}`) value: this.$t(`options.sort-${id}`)
})); }));
},
activeDriverNames() {
const driverNameSet = new Set<string>();
this.store.trainList.forEach((train) => {
driverNameSet.add(train.driverName);
});
return [...driverNameSet].sort((a, b) => a.localeCompare(b));
} }
}, },
@@ -195,8 +202,8 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/dropdown.scss'; @use '../../styles/dropdown';
@import '../../styles/dropdown_filters.scss'; @use '../../styles/dropdown-filters';
.search_content > div { .search_content > div {
margin: 0.5em auto; margin: 0.5em auto;
+1 -1
View File
@@ -367,7 +367,7 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/responsive.scss'; @use '../../styles/responsive';
$barClr: #b1b1b1; $barClr: #b1b1b1;
$confirmedClr: #4ae24a; $confirmedClr: #4ae24a;
+9 -5
View File
@@ -80,7 +80,11 @@
<h3>{{ $t('train-stats.top-units') }}</h3> <h3>{{ $t('train-stats.top-units') }}</h3>
<transition-group tag="ul" name="stats-anim"> <transition-group tag="ul" name="stats-anim">
<li class="badge stat-badge" v-for="top in stats.topUnits.slice(0, 7)" :key="top.name"> <li
class="badge stat-badge"
v-for="top in stats.topUnits.slice(0, 7)"
:key="top.name"
>
<span>{{ top.name }}</span> <span>{{ top.name }}</span>
<span>{{ top.count }}</span> <span>{{ top.count }}</span>
</li> </li>
@@ -221,9 +225,9 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/dropdown.scss'; @use '../../styles/dropdown';
@import '../../styles/badge.scss'; @use '../../styles/badge';
@import '../../styles/responsive.scss'; @use '../../styles/responsive';
h1 img { h1 img {
vertical-align: text-bottom; vertical-align: text-bottom;
@@ -248,7 +252,7 @@ h3 {
max-width: 600px; max-width: 600px;
} }
@include smallScreen { @include responsive.smallScreen{
.no-data { .no-data {
text-align: center; text-align: center;
} }
+7 -3
View File
@@ -19,7 +19,7 @@
>) >)
</div> </div>
<transition-group name="list-anim" tag="ul"> <transition-group name="list-anim" tag="div" class="list_wrapper">
<TrainTableItem v-for="train in trains" :key="train.id" :train="train" /> <TrainTableItem v-for="train in trains" :key="train.id" :train="train" />
</transition-group> </transition-group>
</div> </div>
@@ -93,8 +93,8 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/responsive.scss'; @use '../../styles/responsive';
@import '../../styles/animations.scss'; @use '../../styles/animations';
.train-table { .train-table {
height: calc(100vh - 11em); height: calc(100vh - 11em);
@@ -105,6 +105,10 @@ export default defineComponent({
overflow-x: hidden; overflow-x: hidden;
} }
.list_wrapper {
padding: 2px; // ensures focused items outline visibility
}
.table-warning { .table-warning {
text-align: center; text-align: center;
+19 -20
View File
@@ -1,29 +1,27 @@
<template> <template>
<li class="train-item"> <router-link :to="train.driverRouteLocation" class="train-item">
<router-link class="a-block" :to="train.driverRouteLocation"> <div class="item-wrapper">
<div class="item-wrapper"> <TrainInfo :train="train" />
<TrainInfo :train="train" />
<div class="train-stats"> <div class="train-stats">
<StockList :trainStockList="train.stockList" :tractionOnly="true" /> <StockList :trainStockList="train.stockList" :tractionOnly="true" />
<div>
<span>{{ train.speed }}km/h</span>
<div> <div>
<span>{{ train.speed }}km/h</span> <span> {{ train.length }}m</span>
&bull;
<div> <span> {{ (train.mass / 1000).toFixed(1) }}t</span>
<span> {{ train.length }}m</span> <span v-if="train.stockList.length > 1">
&bull; &bull;
<span> {{ (train.mass / 1000).toFixed(1) }}t</span> {{ $t('trains.cars') }}: {{ train.stockList.length - 1 }}
<span v-if="train.stockList.length > 1"> </span>
&bull;
{{ $t('trains.cars') }}: {{ train.stockList.length - 1 }}
</span>
</div>
</div> </div>
</div> </div>
</div> </div>
</router-link> </div>
</li> </router-link>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -41,9 +39,10 @@ defineProps({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/responsive.scss'; @use '../../styles/responsive';
.train-item { .train-item {
display: block;
background-color: #1a1a1a; background-color: #1a1a1a;
margin-bottom: 1em; margin-bottom: 1em;
width: 100%; width: 100%;
@@ -67,7 +66,7 @@ defineProps({
line-height: 1.5em; line-height: 1.5em;
} }
@include smallScreen() { @include responsive.smallScreen {
.item-wrapper { .item-wrapper {
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: 1em 0; gap: 1em 0;
+10
View File
@@ -4,6 +4,9 @@ export const speedLimits: Record<string, any> = {
'650000': 125 '650000': 125
}, },
cargo: { cargo: {
'750000': 100,
'1000000': 90,
'1500000': 80,
'2000000': 70 '2000000': 70
}, },
none: 110 none: 110
@@ -13,6 +16,9 @@ export const speedLimits: Record<string, any> = {
'650000': 125 '650000': 125
}, },
cargo: { cargo: {
'750000': 100,
'1000000': 90,
'1500000': 80,
'2000000': 70 '2000000': 70
}, },
none: 110 none: 110
@@ -22,6 +28,9 @@ export const speedLimits: Record<string, any> = {
'650000': 125 '650000': 125
}, },
cargo: { cargo: {
'750000': 100,
'1000000': 90,
'1500000': 80,
'2000000': 70 '2000000': 70
}, },
none: 110 none: 110
@@ -65,6 +74,7 @@ export const speedLimits: Record<string, any> = {
}, },
cargo: { cargo: {
'1200000': 100, '1200000': 100,
'2000000': 80,
'3100000': 70 '3100000': 70
}, },
none: 125 none: 125
+95
View File
@@ -0,0 +1,95 @@
{
"allCategories": [
"ROE", "ROJ", "ROS", "ROM",
"RPE", "RPJ", "RPS", "RPM",
"RME", "RMJ", "RMS", "RMM",
"RAE", "RAJ", "RAS", "RAM",
"MPE", "MPJ", "MPS", "MPM",
"MME", "MMJ", "MMS", "MMM",
"MOE", "MOJ", "MOS", "MOM",
"MHE", "MHJ", "MHS",
"EIE", "EIS",
"ENE", "ENS",
"ECE", "ECS",
"PWE", "PWM", "PWJ", "PWS",
"PXE", "PXM", "PXJ", "PXS",
"TCE", "TCS",
"TDE", "TDS",
"TGE", "TGS",
"TKE", "TKS",
"TME", "TMS",
"TNE", "TNS",
"TRE", "TRS",
"TSE", "TSS",
"THE", "THS",
"LPE",
"LTE", "LTS",
"LSS",
"LZE", "LZS",
"ZNE", "ZNS",
"ZUE", "ZUS"
],
"regionNumbers": {
"Warszawa (1)": 1,
"Lublin (2)": 2,
"Kraków (3)": 3,
"Sosnowiec (4)": 4,
"Gdańsk (5)": 5,
"Wrocław (6)": 6,
"Poznań (7)": 7,
"Szczecin (8)": 8,
"Rezerwa (9)": 9
},
"sameRegions": {
"Losowy": [
10, 11, 19, 91, 93, 97, 99, 20, 22, 29, 30, 33, 39, 40, 44, 49, 94, 50, 55, 59, 90, 95, 96,
66, 60, 69, 77, 70, 79, 88, 80, 89, 92, 98
],
"Warszawa (1)": [10, 11, 19, 91, 93, 97, 99],
"Lublin (2)": [20, 22, 29],
"Kraków (3)": [30, 33, 39],
"Sosnowiec (4)": [40, 44, 49, 94],
"Gdańsk (5)": [50, 55, 59, 90, 95, 96],
"Wrocław (6)": [66, 60, 69],
"Poznań (7)": [77, 70, 79],
"Szczecin (8)": [88, 80],
"Rezerwa (9)": [89, 92, 98]
},
"categoriesRules": {
"EI": [null, "00", "99"],
"EC": [null, "000", "049"],
"EN": [null, "000", "049"],
"RO": [null, "200", "999"],
"RP": [null, "050", "169"],
"RM": [null, "200", "999"],
"RA": [null, "200", "999"],
"MO": [null, "200", "999"],
"MP": [null, "050", "169"],
"MM": [null, "001", "049"],
"MH": [null, "170", "199"],
"PW": ["6", "000", "899"],
"PX": ["6", "000", "899"],
"TM": ["4", "000", "899"],
"TN": ["3", "000", "899"],
"TK": ["3", "000", "899"],
"TD": ["2", "000", "899"],
"TG": ["1", "000", "899"],
"TR": ["1", "000", "899"],
"TC": ["0", "000", "899"],
"TS": ["5", "000", "899"],
"TH": ["5", "000", "899"],
"LT": ["5", "000", "899"],
"LP": ["6", "000", "899"],
"LS": ["9", "000", "899"],
"LZ": ["9", "000", "899"],
"ZN": ["9", "000", "899"],
"ZU": ["9", "000", "899"]
}
}
+21 -7
View File
@@ -157,8 +157,8 @@
"sort-title": "SORT BY:", "sort-title": "SORT BY:",
"filter-title": "FILTER BY:", "filter-title": "FILTER BY:",
"search-title": "SEARCH:", "search-title": "SEARCH:",
"search-train-no": "Train no. / #", "search-train": "Train no. / #",
"search-train": "Train no.", "select-driver": "Choose a driver...",
"search-driver": "Driver name", "search-driver": "Driver name",
"search-dispatcher": "Dispatcher name", "search-dispatcher": "Dispatcher name",
"search-station": "Scenery name / #", "search-station": "Scenery name / #",
@@ -170,6 +170,7 @@
"search-dispatchers-date": "Service date (from / to)", "search-dispatchers-date": "Service date (from / to)",
"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",
"sort-mass": "mass", "sort-mass": "mass",
"sort-speed": "speed", "sort-speed": "speed",
"sort-length": "length", "sort-length": "length",
@@ -332,13 +333,19 @@
"active-filters": "Attention! You got active filters!" "active-filters": "Attention! You got active filters!"
}, },
"station-stats": { "station-stats": {
"title": "ONLINE SCENERIES STATS",
"stats-button": "STATISTICS",
"u-factor": "U-factor", "u-factor": "U-factor",
"u-factor-tooltip": "(?) Current server traffic factor (driver count divided by dispatcher count)", "u-factor-tooltip": "(?) Current server traffic factor (driver count divided by dispatcher count)",
"avg-timetable-count": "Average count of scenery timetables:", "avg-timetable-count": "TT average:",
"single-track-count": "Single track routes:", "single-track-count": "Single track routes:",
"double-track-count": "Double track routes:", "double-track-count": "Double track routes:",
"cross-sceneries": "Cross-track sceneries (1-track <-> 2-track)", "cross-sceneries": "Cross-track sceneries",
"open-spawns": "Open spawns:" "open-spawns-all": "Spawns (ALL):",
"open-spawns-pas": "Spawns (PAS):",
"open-spawns-freight": "Spawns (TOW):",
"open-spawns-loco": "Spawns (LOK):",
"no-stats": "No statistics available for the current region!"
}, },
"trains": { "trains": {
"no-trains": "No trains to show here!", "no-trains": "No trains to show here!",
@@ -389,12 +396,19 @@
"driver-not-found-others": "Player {driver} is online as:", "driver-not-found-others": "Player {driver} is online as:",
"driver-not-found-return": "GO BACK TO THE MAIN SITE", "driver-not-found-return": "GO BACK TO THE MAIN SITE",
"stock-copy": "COPY THE STOCK", "stock-copy": "COPY THE STOCK",
"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 selected category:",
"number-propositions-third-number": "Third digit:",
"number-propositions-last-nums": "{count} last digits from the range of:",
"number-propositions-title": "Propositions:",
"number-propositions-empty": "No propositions available for the chosen category! :/"
}, },
"train-stats": { "train-stats": {
"stats-button": "STATISTICS", "stats-button": "STATISTICS",
"title": "STATISTICS ONLINE", "title": "ONLINE TRAINS STATS",
"timetable-count": "ACTIVE TIMETABLES", "timetable-count": "ACTIVE TIMETABLES",
"avg-speed": "AVG SPEED", "avg-speed": "AVG SPEED",
"avg-timetable": "AVG TIMETABLE", "avg-timetable": "AVG TIMETABLE",
+21 -6
View File
@@ -154,9 +154,9 @@
"sort-title": "SORTUJ WG:", "sort-title": "SORTUJ WG:",
"filter-title": "FILTRUJ WG:", "filter-title": "FILTRUJ WG:",
"search-title": "SZUKAJ:", "search-title": "SZUKAJ:",
"search-train-no": "Nr pociągu",
"search-train": "Nr pociągu / #", "search-train": "Nr pociągu / #",
"search-driver": "Nick maszynisty", "search-driver": "Nick maszynisty",
"select-driver": "Wybierz maszynistę...",
"search-dispatcher": "Nick dyżurnego", "search-dispatcher": "Nick dyżurnego",
"search-station": "Nazwa scenerii / #", "search-station": "Nazwa scenerii / #",
"search-author": "Nick autora rozkładu jazdy", "search-author": "Nick autora rozkładu jazdy",
@@ -167,6 +167,7 @@
"search-dispatchers-date": "Data służby (od / do)", "search-dispatchers-date": "Data służby (od / do)",
"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",
"sort-routeDistance": "kilometraż", "sort-routeDistance": "kilometraż",
"sort-allStopsCount": "stacje", "sort-allStopsCount": "stacje",
"sort-beginDate": "data", "sort-beginDate": "data",
@@ -328,13 +329,19 @@
"active-filters": "Uwaga! Masz obecnie aktywne filtry!" "active-filters": "Uwaga! Masz obecnie aktywne filtry!"
}, },
"station-stats": { "station-stats": {
"title": "STATYSTYKI AKTYWNYCH SCENERII",
"stats-button": "STATYSTYKI",
"u-factor": "Współczynnik Ugla", "u-factor": "Współczynnik Ugla",
"u-factor-tooltip": "(?) Współczynnik ruchu na serwerze (liczba maszynistów online dzielona na liczbę dyżurnych ruchu)", "u-factor-tooltip": "(?) Współczynnik ruchu na serwerze (liczba maszynistów online dzielona na liczbę dyżurnych ruchu)",
"avg-timetable-count": "Średnia liczba rozkładów jazdy na sceneriach:", "avg-timetable-count": "Średnia RJ:",
"single-track-count": "Szlaki jednotorowe:", "single-track-count": "Szlaki jednotorowe:",
"double-track-count": "Szlaki dwutorowe:", "double-track-count": "Szlaki dwutorowe:",
"cross-sceneries": "Scenerie przejściowe (1-tor <-> 2-tor):", "cross-sceneries": "Scenerie przejściowe:",
"open-spawns": "Otwarte spawny:" "open-spawns-all": "Spawny (ALL):",
"open-spawns-pas": "Spawny (PAS):",
"open-spawns-freight": "Spawny (TOW):",
"open-spawns-loco": "Spawny (LOK):",
"no-stats": "Brak statystyk online dla wybranego serwera!"
}, },
"trains": { "trains": {
"no-trains": "Brak pociągów do wyświetlenia!", "no-trains": "Brak pociągów do wyświetlenia!",
@@ -376,12 +383,20 @@
"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": "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 kategorii pociągu:",
"number-propositions-third-number": "Trzecia cyfra:",
"number-propositions-last-nums": "{count} ostatnie cyfry z przedziału:",
"number-propositions-title": "Propozycje:",
"number-propositions-empty": "Brak propozycji dla wybranej kategorii! :/"
}, },
"train-stats": { "train-stats": {
"stats-button": "STATYSTYKI", "stats-button": "STATYSTYKI",
"title": "STATYSTYKI ONLINE", "title": "STATYSTYKI AKTYWNYCH POCIĄGÓW",
"timetable-count": "AKTYWNE RJ", "timetable-count": "AKTYWNE RJ",
"avg-speed": "ŚREDNIA PRĘDKOŚĆ", "avg-speed": "ŚREDNIA PRĘDKOŚĆ",
"avg-timetable": "ŚREDNI RJ", "avg-timetable": "ŚREDNI RJ",
-2
View File
@@ -53,8 +53,6 @@ export const useApiStore = defineStore('apiStore', {
}, },
updateTick(t: number) { updateTick(t: number) {
if (this.dataStatuses.connection == Status.Data.Offline) return;
// Static data refresh // Static data refresh
if (t >= this.nextDataCheckTime) { if (t >= this.nextDataCheckTime) {
this.fetchDonatorsData(); this.fetchDonatorsData();
@@ -1,5 +1,4 @@
@import 'variables.scss'; @use 'responsive';
@import 'responsive.scss';
.badge { .badge {
font-weight: 600; font-weight: 600;
@@ -9,7 +8,8 @@
margin: 0.25em; margin: 0.25em;
span { & > span,
& > a > span {
display: inline-block; display: inline-block;
background: #585858; background: #585858;
padding: 0.2em 0.4em; padding: 0.2em 0.4em;
@@ -23,7 +23,7 @@
text-align: center; text-align: center;
@include smallScreen() { @include responsive.smallScreen {
font-size: 1em; font-size: 1em;
} }
} }
@@ -131,7 +131,7 @@
color: white; color: white;
& > span:first-child { & > span:first-child {
background-color: $accentCol; background-color: var(--clr-primary);
color: black; color: black;
} }
} }
@@ -1,5 +1,4 @@
@import './variables.scss'; @use 'responsive';
@import './responsive.scss';
.card-dimmer { .card-dimmer {
position: fixed; position: fixed;
@@ -46,7 +45,7 @@
} }
} }
@include smallScreen { @include responsive.smallScreen{
.card { .card {
max-height: 85vh; max-height: 85vh;
} }
@@ -1,6 +1,5 @@
@import 'search_box.scss'; @use 'search-box';
@import 'responsive.scss'; @use 'responsive';
@import 'variables.scss';
.actions-bar { .actions-bar {
display: flex; display: flex;
@@ -51,7 +50,7 @@ h1.option-title {
} }
.sort-option[data-selected='true'] { .sort-option[data-selected='true'] {
color: $accentCol; color: var(--clr-primary);
font-weight: bold; font-weight: bold;
} }
@@ -73,15 +72,6 @@ h1.option-title {
.search { .search {
margin: 0.5em auto; margin: 0.5em auto;
} }
.search-box {
.search-exit {
position: absolute;
transform: translateY(-50%);
top: 50%;
right: 0;
}
}
} }
.options_actions { .options_actions {
@@ -95,7 +85,7 @@ h1.option-title {
} }
} }
@include smallScreen() { @include responsive.smallScreen{
h1 { h1 {
text-align: center; text-align: center;
@@ -1,5 +1,4 @@
@import 'responsive.scss'; @use 'responsive';
@import 'variables.scss';
.dropdown-anim { .dropdown-anim {
&-enter-from, &-enter-from,
@@ -29,18 +28,20 @@
left: 0; left: 0;
top: calc(100% + 0.5em); top: calc(100% + 0.5em);
background-color: $bgCol; background-color: var(--clr-bg3);
// box-shadow: 0 5px 10px 2px #0f0f0f; box-shadow: 0 0 5px 1px var(--clr-primary);
box-shadow: 0 0 5px 1px $accentCol;
width: 100%; width: 100%;
max-width: 550px; max-width: 550px;
max-height: 750px;
overflow: auto;
padding: 1em; padding: 1em;
z-index: 100; z-index: 100;
} }
@include smallScreen { @include responsive.smallScreen {
.dropdown_wrapper { .dropdown_wrapper {
font-size: 1.1em; font-size: 1.1em;
max-width: 100%; max-width: 100%;
@@ -1,6 +1,5 @@
@import 'fonts'; @use 'fonts';
@import 'variables'; @use 'responsive';
@import 'responsive';
:root { :root {
--clr-primary: #ffc014; --clr-primary: #ffc014;
@@ -8,6 +7,8 @@
--clr-bg: #4d4d4d; --clr-bg: #4d4d4d;
--clr-bg2: #1b1b1b; --clr-bg2: #1b1b1b;
--clr-bg3: #1d1d1d;
--clr-view-bg: #1a1a1a;
--clr-accent: #1085b3; --clr-accent: #1085b3;
--clr-accent2: #ff3d5d; --clr-accent2: #ff3d5d;
@@ -18,10 +19,11 @@
--clr-pn: #ffd000; --clr-pn: #ffd000;
--clr-error: #fa3636; --clr-error: #fa3636;
--clr-warning: #c59429; --clr-warning: #ffe15b;
--clr-donator: #f7a4ff; --clr-donator: #f7a4ff;
--no-scroll-padding: 17px; --no-scroll-padding: 17px;
--max-container-width: 1700px; --max-container-width: 1700px;
@@ -60,11 +62,21 @@ body {
overflow-x: hidden; overflow-x: hidden;
position: relative; position: relative;
font-size: 16px;
@include responsive.smallScreen {
font-size: calc(0.65rem + 0.85vw);
}
@include responsive.screenLandscape {
font-size: calc(0.45rem + 0.8vw);
}
&.no-scroll { &.no-scroll {
overflow-y: hidden; overflow-y: hidden;
padding-right: var(--no-scroll-padding); padding-right: var(--no-scroll-padding);
@include smallScreen() { @include responsive.smallScreen {
padding: 0; padding: 0;
} }
} }
@@ -108,11 +120,11 @@ input {
} }
*:focus-visible { *:focus-visible {
outline: 1px solid $accentCol; outline: 1px solid var(--clr-primary);
} }
.title { .title {
color: $accentCol; color: var(--clr-primary);
font-weight: 600; font-weight: 600;
padding: 0.35em 0; padding: 0.35em 0;
@@ -130,26 +142,29 @@ a {
color: inherit; color: inherit;
} }
a:not(.a-block):not(.a-button):not(.a-row) {
display: inline-block;
transition: color 0.3s;
&:hover,
&:focus {
color: $accentCol;
border: none;
}
}
a.a-block {
display: block;
}
a.a-row { a.a-row {
display: table-row; display: table-row;
} }
a:focus-visible {
outline: 1px solid var(--clr-primary);
}
.route-link {
margin: 0 0.2em;
transition: color 100ms;
&-active,
&[data-active='true'] {
color: var(--clr-primary);
font-weight: bold;
}
&:hover {
color: var(--clr-primary);
}
}
ul { ul {
padding: 0; padding: 0;
list-style: none; list-style: none;
@@ -292,7 +307,7 @@ a.a-button {
width: 1.3em; width: 1.3em;
} }
@include smallScreen() { @include responsive.smallScreen {
bottom: 1em; bottom: 1em;
right: 0; right: 0;
left: 50%; left: 50%;
@@ -330,7 +345,7 @@ a.a-button {
cursor: help; cursor: help;
} }
@include smallScreen { @include responsive.smallScreen {
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 0.5em; width: 0.5em;
height: 0.5em; height: 0.5em;
@@ -1,5 +1,5 @@
@import 'responsive.scss'; @use 'responsive';
@import 'animations.scss'; @use 'animations';
.journal-list { .journal-list {
display: flex; display: flex;
@@ -12,7 +12,7 @@
.list_wrapper { .list_wrapper {
overflow-y: auto; overflow-y: auto;
height: calc(100vh - 12.5em); height: calc(100vh - 12.5em);
min-height: 500px; min-height: 700px;
margin-top: 0.5em; margin-top: 0.5em;
position: relative; position: relative;
@@ -68,7 +68,7 @@
font-size: 1.2em; font-size: 1.2em;
} }
@include smallScreen() { @include responsive.smallScreen{
.journal_top-bar { .journal_top-bar {
justify-content: center; justify-content: center;
flex-wrap: wrap; flex-wrap: wrap;
@@ -1,6 +1,5 @@
@import 'variables.scss'; @use 'responsive';
@import 'responsive.scss'; @use 'badge';
@import 'badge.scss';
.stats-tab { .stats-tab {
position: absolute; position: absolute;
@@ -11,7 +10,7 @@
width: 100%; width: 100%;
background-color: #1a1a1a; background-color: #1a1a1a;
box-shadow: 0 0 5px 1px $accentCol; box-shadow: 0 0 5px 1px var(--clr-primary);
padding: 1em; padding: 1em;
display: flex; display: flex;
@@ -33,7 +32,7 @@ hr.section-separator {
gap: 0.5em; gap: 0.5em;
} }
@include smallScreen { @include responsive.smallScreen{
.journal-stats { .journal-stats {
text-align: center; text-align: center;
} }
@@ -1,4 +1,4 @@
@import 'responsive.scss'; @use 'responsive';
.search { .search {
label { label {
@@ -11,8 +11,9 @@
position: relative; position: relative;
display: flex; display: flex;
align-items: center;
gap: 0.25em;
border-radius: 0.5em;
min-width: 200px; min-width: 200px;
margin-right: 0.25em; margin-right: 0.25em;
} }
@@ -20,6 +21,8 @@
&-input { &-input {
border: none; border: none;
background-color: #424242; background-color: #424242;
color: white;
border-radius: 0.5em;
padding: 0.35em 0.5em; padding: 0.35em 0.5em;
width: 100%; width: 100%;
@@ -27,6 +30,7 @@
&-exit { &-exit {
background-color: #424242; background-color: #424242;
border-radius: 0.5em;
img { img {
vertical-align: middle; vertical-align: middle;
@@ -39,7 +43,7 @@
max-width: 300px; max-width: 300px;
} }
@include smallScreen { @include responsive.smallScreen {
&-box, &-box,
&-button { &-button {
margin: 0.5em 0 0 0; margin: 0.5em 0 0 0;
@@ -50,3 +54,15 @@
} }
} }
} }
.search-box > i {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
padding-right: 0.5em;
}
select.search-input {
}
-11
View File
@@ -1,11 +0,0 @@
$primaryCol: #2c2c2c;
$secondaryCol: #01e733;
$bgCol: #1d1d1d;
$bgLigtherCol: #5b5b5b;
$errorCol: #ff1919;
$warningCol: #ffe15b;
$accentCol: #ffc014;
$accent2Col: #ff3d5d;
+2
View File
@@ -229,6 +229,8 @@ export interface VehiclesGroup {
id: number; id: number;
name: string; name: string;
speed: number; speed: number;
speedLoaded?: number;
speedLoco?: number;
length: number; length: number;
weight: number; weight: number;
cargoTypes: VehicleCargo[] | null; cargoTypes: VehicleCargo[] | null;
+7 -172
View File
@@ -2,103 +2,27 @@
<section class="driver-view"> <section class="driver-view">
<div class="view-wrapper"> <div class="view-wrapper">
<div v-if="chosenTrain"> <div v-if="chosenTrain">
<div class="actions-container"> <DriverTopActions :chosenTrain="chosenTrain" />
<div class="actions actions-left"> <DriverTrainCard :chosenTrain="chosenTrain" />
<a class="a-button btn--image" @click="$router.back()">
<img src="/images/icon-back.svg" alt="train icon" />
<span>
{{ $t('trains.driver-return-link') }}
</span>
</a>
</div>
<div class="actions actions-right">
<a
class="a-button btn--image"
:href="`https://srjp-td2.web.app/?id=${chosenTrain.id}`"
target="_blank"
>
<span class="hidable">
{{ $t('trains.driver-srjp-link') }}
</span>
<img src="/images/icon-srjp.svg" alt="srjp icon" />
</a>
<router-link
:to="`/journal/timetables?search-driver=${chosenTrain.driverName}`"
class="a-button btn--image"
>
<span class="hidable">
{{ $t('trains.driver-journal-link') }}
</span>
<img src="/images/icon-train.svg" alt="train icon" />
</router-link>
</div>
</div>
<div class="train-card">
<TrainInfo :train="chosenTrain" :extended="true" />
<button class="btn btn--action" style="margin: 1em 0" @click="copyStockToClipboard()">
<i class="fa-regular fa-copy"></i> {{ $t('trains.stock-copy') }}
</button>
<StockList :trainStockList="chosenTrain.stockList" />
<TrainSchedule :train="chosenTrain" />
</div>
</div> </div>
<Loading v-else-if="apiStore.dataStatuses.connection == Status.Data.Loading" /> <Loading v-else-if="apiStore.dataStatuses.connection == Status.Data.Loading" />
<div v-else class="driver-not-found"> <DriverNotFound v-else :trainId="props.trainId" />
<h2>&olcross; {{ $t('trains.driver-not-found-header') }}</h2>
<p class="text--grayed">
{{ $t('trains.driver-not-found-desc-1') }} <br />
{{ $t('trains.driver-not-found-desc-2') }}
<router-link to="/journal/timetables"
>{{ $t('trains.driver-not-found-journal') }} </router-link
>!
</p>
<p v-if="props.trainId && otherDriverTrains.length > 0">
<i18n-t keypath="trains.driver-not-found-others">
<template v-slot:driver>
<b>{{ otherDriverTrains[0].driverName }}</b>
</template>
</i18n-t>
</p>
<div class="other-driver-trains">
<template v-for="(train, i) in otherDriverTrains">
<router-link :to="`/driver?trainId=${train.id}`">
{{ train.trainNo }}
| {{ regionsJSON.find((r) => r.id == train.region)?.name ?? 'PL1' }}
</router-link>
</template>
</div>
<div style="margin-top: 1em">
<router-link to="/">&lt;&lt; {{ $t('trains.driver-not-found-return') }}</router-link>
</div>
</div>
</div> </div>
</section> </section>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed } from 'vue';
import TrainInfo from '../components/TrainsView/TrainInfo.vue';
import TrainSchedule from '../components/TrainsView/TrainSchedule.vue';
import StockList from '../components/Global/StockList.vue';
import Loading from '../components/Global/Loading.vue'; import Loading from '../components/Global/Loading.vue';
import { useMainStore } from '../store/mainStore'; import { useMainStore } from '../store/mainStore';
import { useApiStore } from '../store/apiStore'; import { useApiStore } from '../store/apiStore';
import { Status } from '../typings/common'; import { Status } from '../typings/common';
import { regions as regionsJSON } from '../data/options.json';
import { useI18n } from 'vue-i18n'; import DriverTopActions from '../components/DriverView/DriverTopActions.vue';
import DriverTrainCard from '../components/DriverView/DriverTrainCard.vue';
import DriverNotFound from '../components/DriverView/DriverNotFound.vue';
const props = defineProps({ const props = defineProps({
trainId: { trainId: {
@@ -113,105 +37,16 @@ const props = defineProps({
const mainStore = useMainStore(); const mainStore = useMainStore();
const apiStore = useApiStore(); const apiStore = useApiStore();
const i18n = useI18n();
const chosenTrain = computed(() => const chosenTrain = computed(() =>
mainStore.trainList.find((train) => train.id == props.trainId || train.modalId == props.modalId) mainStore.trainList.find((train) => train.id == props.trainId || train.modalId == props.modalId)
); );
const otherDriverTrains = computed(() => {
return mainStore.trainList.filter(
(train) =>
train.driverId == Number(props.trainId?.split('|')[0]) &&
(train.timetableData || train.online || train.lastSeen >= Date.now() - 60000)
);
});
function copyStockToClipboard() {
const stockString = chosenTrain.value?.stockList.join(';');
if (!stockString) {
alert(i18n.t('trains.stock-clipboard-failure'));
return;
}
navigator.clipboard
.writeText(stockString)
.then(() => {
prompt(i18n.t('trains.stock-clipboard-success'), stockString);
})
.catch(() => {
alert(i18n.t('trains.stock-clipboard-failure'));
});
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../styles/responsive';
$viewBgCol: #1a1a1a;
.driver-view { .driver-view {
margin: 0 auto; margin: 0 auto;
padding: 1em 0; padding: 1em 0;
max-width: var(--max-container-width); max-width: var(--max-container-width);
min-height: calc(100vh - 7em); min-height: calc(100vh - 7em);
} }
.actions-container {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 0.5em;
}
.actions {
display: flex;
gap: 0.5em;
}
.actions-container > .actions > a {
background-color: $viewBgCol;
padding: 0.5em;
border-radius: 0.5em 0.5em 0 0;
&:hover {
background-color: lighten($viewBgCol, 10);
}
}
.train-card {
padding: 1em;
background-color: $viewBgCol;
border-radius: 0 0 0.5em 0.5em;
}
.driver-not-found {
background-color: $viewBgCol;
text-align: center;
padding: 1em;
border-radius: 0.5em 0.5em;
p {
padding: 0.5em 0;
}
a {
text-decoration: underline;
color: white;
}
}
.other-driver-trains {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 0.5em;
}
@include smallScreen {
span.hidable {
display: none;
}
}
</style> </style>
+1 -1
View File
@@ -332,5 +332,5 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../styles/JournalSection.scss'; @use '../styles/journal-section';
</style> </style>
+6 -3
View File
@@ -122,6 +122,7 @@ interface TimetablesQueryParams {
driverName?: string; driverName?: string;
trainNo?: string; trainNo?: string;
timetableId?: string; timetableId?: string;
categoryCode?: string;
authorName?: string; authorName?: string;
@@ -215,6 +216,7 @@ export default defineComponent({
'search-issuedFrom': '', 'search-issuedFrom': '',
'search-via': '', 'search-via': '',
'search-terminatingAt': '', 'search-terminatingAt': '',
'select-categoryCode': '',
'search-date-from': '' 'search-date-from': ''
} as Journal.TimetableSearchType); } as Journal.TimetableSearchType);
@@ -230,6 +232,7 @@ export default defineComponent({
return { return {
sorterActive, sorterActive,
searchersValues, searchersValues,
filterList, filterList,
initFilters, initFilters,
@@ -356,6 +359,7 @@ export default defineComponent({
const issuedFrom = this.searchersValues['search-issuedFrom'].trim() || undefined; const issuedFrom = this.searchersValues['search-issuedFrom'].trim() || undefined;
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;
let dateTo: string | undefined = undefined; let dateTo: string | undefined = undefined;
@@ -365,8 +369,6 @@ export default defineComponent({
dateTo = d.toISOString().split('T')[0]; dateTo = d.toISOString().split('T')[0];
} }
// const timestampFrom = dateString ? Date.parse(new Date(dateString).toISOString()) : undefined;
// const timestampTo = timestampFrom ? timestampFrom + 86400000 : undefined;
const queryParams: TimetablesQueryParams = {}; const queryParams: TimetablesQueryParams = {};
@@ -433,6 +435,7 @@ export default defineComponent({
queryParams['issuedFrom'] = issuedFrom; queryParams['issuedFrom'] = issuedFrom;
queryParams['terminatingAt'] = terminatingAt; queryParams['terminatingAt'] = terminatingAt;
queryParams['via'] = via; queryParams['via'] = via;
queryParams['categoryCode'] = categoryCode;
queryParams['issuedFrom'] = issuedFrom; queryParams['issuedFrom'] = issuedFrom;
queryParams['sortBy'] = queryParams['sortBy'] =
@@ -482,5 +485,5 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../styles/JournalSection.scss'; @use '../styles/journal-section';
</style> </style>
+6 -7
View File
@@ -13,7 +13,7 @@
:station="stationInfo" :station="stationInfo"
:onlineScenery="onlineSceneryInfo" :onlineScenery="onlineSceneryInfo"
/> />
<SceneryInfo :station="stationInfo" :onlineScenery="onlineSceneryInfo" /> <SceneryInfo :station="stationInfo" :onlineScenery="onlineSceneryInfo" />
</div> </div>
@@ -173,8 +173,7 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../styles/responsive.scss'; @use '../styles/responsive';
@import '../styles/variables.scss';
button.back-btn { button.back-btn {
img { img {
@@ -193,7 +192,7 @@ button.back-btn {
text-align: center; text-align: center;
padding: 2em 1em; padding: 2em 1em;
color: $warningCol; color: var(--clr-warning);
display: inline-block; display: inline-block;
@@ -275,7 +274,7 @@ button.back-btn {
.checkpoint_item { .checkpoint_item {
&.current { &.current {
font-weight: bold; font-weight: bold;
color: $accentCol; color: var(--clr-primary);
} }
&:not(:last-child)::after { &:not(:last-child)::after {
@@ -286,7 +285,7 @@ button.back-btn {
} }
} }
@include midScreen { @include responsive.midScreen {
.scenery-wrapper { .scenery-wrapper {
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: 0; gap: 0;
@@ -304,7 +303,7 @@ button.back-btn {
} }
} }
@include smallScreen { @include responsive.smallScreen{
.scenery-left { .scenery-left {
max-height: 100vh; max-height: 100vh;
} }
+5 -8
View File
@@ -4,7 +4,7 @@
<div class="stations-options"> <div class="stations-options">
<StationFilterCard <StationFilterCard
:showCard="filterCardOpen" :showCard="filterCardOpen"
:exit="(filterCardOpen = false)" :exit="filterCardOpen = false"
ref="filterCardRef" ref="filterCardRef"
/> />
@@ -78,8 +78,7 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../styles/variables.scss'; @use '../styles/responsive';
@import '../styles/responsive.scss';
.stations-view { .stations-view {
position: relative; position: relative;
@@ -107,15 +106,13 @@ export default defineComponent({
button.btn-donation { button.btn-donation {
margin-left: auto; margin-left: auto;
$btnColor: #254069; background-color: #254069;
background-color: $btnColor;
&:hover { &:hover {
background-color: lighten($btnColor, 5%); background-color: #2e4f81;
} }
@include smallScreen { @include responsive.smallScreen {
span { span {
display: none; display: none;
} }
+2 -2
View File
@@ -114,7 +114,7 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../styles/responsive.scss'; @use '../styles/responsive';
.trains-view { .trains-view {
position: relative; position: relative;
@@ -134,7 +134,7 @@ export default defineComponent({
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
@include smallScreen { @include responsive.smallScreen {
.trains_topbar { .trains_topbar {
justify-content: space-between; justify-content: space-between;
} }
+17 -17
View File
@@ -1,17 +1,22 @@
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 path from 'path';
export default defineConfig({ export default defineConfig({
server: { server: { port: 5123, open: true },
port: 5001, preview: { port: 4001, open: true },
open: true
},
preview: {
port: 4001,
open: true
},
publicDir: 'public', publicDir: 'public',
css: {
preprocessorOptions: {
scss: { additionalData: `@use '@/styles/global';`, silenceDeprecations: ['legacy-js-api'] }
}
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
},
plugins: [ plugins: [
vue(), vue(),
VitePWA({ VitePWA({
@@ -26,20 +31,15 @@ export default defineConfig({
{ {
urlPattern: urlPattern:
/^https:\/\/stacjownik.spythere.eu\/api\/(getVehicles|getDonators|getSceneries)/i, /^https:\/\/stacjownik.spythere.eu\/api\/(getVehicles|getDonators|getSceneries)/i,
handler: 'StaleWhileRevalidate', handler: 'NetworkFirst',
options: { options: {
cacheName: 'stacjownik-api-cache', cacheName: 'stacjownik-api-cache',
cacheableResponse: { cacheableResponse: { statuses: [0, 200] }
statuses: [0, 200]
}
} }
}, }
] ]
}, },
devOptions: { devOptions: { enabled: true, suppressWarnings: true }
enabled: true,
suppressWarnings: true
}
}) })
], ],
build: { build: {
+2201 -2189
View File
File diff suppressed because it is too large Load Diff