revamp: order train picker

This commit is contained in:
2025-02-07 00:11:34 +01:00
parent fab3cef1f3
commit b69c44c55a
9 changed files with 163 additions and 138 deletions
-5
View File
@@ -36,16 +36,11 @@ export default defineComponent({
data() { data() {
return { return {
appVersion: packageInfo.version, appVersion: packageInfo.version,
needRefreshTest: false
}; };
}, },
created() { created() {
document.title = `GeneraTOR ${this.appVersion}`; document.title = `GeneraTOR ${this.appVersion}`;
setTimeout(() => {
this.needRefreshTest = true;
}, 500);
} }
}); });
</script> </script>
+1 -1
View File
@@ -1,5 +1,5 @@
<template> <template>
<div class="order dark"> <div class="order" :class="{ dark: store.orderDarkMode }">
<div class="order_content"> <div class="order_content">
<transition name="order-anim" mode="out-in"> <transition name="order-anim" mode="out-in">
<keep-alive> <keep-alive>
+19 -2
View File
@@ -24,6 +24,17 @@
</div> </div>
<div class="message_checkboxes"> <div class="message_checkboxes">
<label for="dark-mode" class="g-checkbox">
<input
type="checkbox"
name="dark-mode"
id="dark-mode"
v-model="store.orderDarkMode"
@change="onCheckboxChange"
/>
<span>Ciemny motyw rozkazu</span>
</label>
<label for="copy-increment" class="g-checkbox"> <label for="copy-increment" class="g-checkbox">
<input <input
type="checkbox" type="checkbox"
@@ -34,6 +45,7 @@
/> />
<span>Aktualizuj numer rozkazu po skopiowaniu</span> <span>Aktualizuj numer rozkazu po skopiowaniu</span>
</label> </label>
<label for="save-increment" class="g-checkbox"> <label for="save-increment" class="g-checkbox">
<input <input
type="checkbox" type="checkbox"
@@ -44,6 +56,7 @@
/> />
<span>Aktualizuj numer rozkazu po zapisaniu</span> <span>Aktualizuj numer rozkazu po zapisaniu</span>
</label> </label>
<label for="update-date" class="g-checkbox"> <label for="update-date" class="g-checkbox">
<input <input
type="checkbox" type="checkbox"
@@ -98,6 +111,8 @@ export default defineComponent({
this.incrementOnSave = this.getOrderSetting('save-increment') === 'true'; this.incrementOnSave = this.getOrderSetting('save-increment') === 'true';
this.incrementOnCopy = this.getOrderSetting('copy-increment') === 'true'; this.incrementOnCopy = this.getOrderSetting('copy-increment') === 'true';
this.updateDate = this.getOrderSetting('update-date') === 'true'; this.updateDate = this.getOrderSetting('update-date') === 'true';
this.store.orderDarkMode = this.getOrderSetting('dark-mode') === 'true';
}, },
computed: { computed: {
@@ -239,7 +254,7 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "../styles/global.scss"; @import '../styles/global.scss';
.order-message { .order-message {
h3 { h3 {
@@ -256,7 +271,7 @@ export default defineComponent({
.message_body { .message_body {
height: 250px; height: 250px;
overflow: auto; overflow: auto;
background-color: $bgColLighter; background-color: $bgColLighter;
color: white; color: white;
text-align: justify; text-align: justify;
@@ -292,6 +307,8 @@ export default defineComponent({
} }
.message_checkboxes { .message_checkboxes {
display: flex;
flex-direction: column;
margin-top: 1em; margin-top: 1em;
} }
+110 -123
View File
@@ -2,28 +2,21 @@
<div class="order-train-picker"> <div class="order-train-picker">
<div class="options"> <div class="options">
<label for="dispatcher-select"> <label for="dispatcher-select">
<select name="dispatcher-select" id="dispatcher-select" v-model="selectedDispatcherName">
<option :value="null" disabled>Nick dyżurnego</option>
<option
v-for="dispatcherName in dispatcherNameList"
:value="dispatcherName"
:key="dispatcherName"
>
{{ dispatcherName }}
</option>
</select>
</label>
<label for="scenery-select">
<select <select
name="scenery-select" name="dispatcher-select"
id="scenery-select" id="dispatcher-select"
v-model="selectedSceneryName" v-model="selectedSceneryId"
:disabled="!sceneryNameList || sceneryNameList.length == 0" @change="selectSceneryOption"
> >
<option :value="null" disabled>Sceneria</option> <option :value="null" disabled>Sceneria</option>
<option :value="sceneryName" v-for="sceneryName in sceneryNameList" :key="sceneryName"> <option
{{ sceneryName }} v-for="scenery in filteredSceneries"
:value="`${scenery.stationName}|${scenery.stationHash}|${scenery.dispatcherName}|${scenery.region}`"
:key="scenery.dispatcherName + scenery.stationName"
>
{{ scenery.stationName }} &bull; {{ scenery.dispatcherName }} [{{
getRegionNameById(scenery.region)
}}]
</option> </option>
</select> </select>
</label> </label>
@@ -33,7 +26,7 @@
name="checkpoint-select" name="checkpoint-select"
id="checkpoint-select" id="checkpoint-select"
v-model="selectedCheckpointName" v-model="selectedCheckpointName"
:disabled="!sceneryNameList || sceneryNameList.length == 0" :disabled="!selectedScenery"
> >
<option :value="null" disabled>Posterunek</option> <option :value="null" disabled>Posterunek</option>
<option :value="cp" v-for="cp in checkpointNameList" :key="cp"> <option :value="cp" v-for="cp in checkpointNameList" :key="cp">
@@ -49,47 +42,47 @@
id="fill-checkpoint" id="fill-checkpoint"
v-model="fillCheckpointName" v-model="fillCheckpointName"
/> />
<span> Uzupełniaj skrót post.</span> <span> Uzupełniaj skrót wybranego posterunku</span>
</label> </label>
</div> </div>
<div class="content"> <div class="content">
<b v-if="!selectedSceneryName" class="text--accent"> <b v-if="!selectedSceneryId" class="text--accent">
Wybierz dyżurnego oraz scenerię, aby zobaczyć pociągi Wybierz dyżurnego oraz scenerię, aby zobaczyć pociągi
</b> </b>
<div v-else> <div v-else>
<b class="text--accent">Kliknij na gracza, aby wypełnić obecny rozkaz jego danymi</b> <div style="margin-bottom: 0.5em">
<h3 style="margin-bottom: 0.5em">Aktywne RJ i gracze na scenerii</h3>
<b class="text--accent">Kliknij na gracza, aby wypełnić obecny rozkaz jego danymi</b>
</div>
<p>Gracze online bez RJ</p>
<ul class="train-list"> <ul class="train-list">
<li <li
v-for="train in sceneryTrains" v-for="train in sceneryTrains"
:key="train.trainNo + train.driverName" :key="train.trainNo + train.driverName"
@click="fillOrder(train.trainNo)" @click="fillOrder(train.trainNo)"
> >
<b>{{ train.trainNo }} | {{ train.driverName }}</b> <button class="g-button">
<span
v-if="train.currentStationName == selectedScenery?.stationName"
class="online-indicator"
></span>
<b>
{{ train.driverName }} &bull;
<span v-if="train.timetable" style="color: gold">{{
train.timetable.category
}}</span>
{{ train.trainNo }}
</b>
</button>
</li> </li>
<li class="no-trains" v-if="sceneryTrains?.length == 0 && selectedSceneryName"> <li class="no-trains" v-if="sceneryTrains?.length == 0 && selectedSceneryId">
Brak graczy Brak graczy
</li> </li>
</ul> </ul>
<p>Aktywne rozkłady jazdy</p>
<ul class="train-list">
<li
v-for="train in sceneryScheduledTrains"
:key="train.trainNo + train.driverName"
@click="fillOrder(train.trainNo)"
>
<b>{{ train.trainNo }} | {{ train.driverName }}</b>
</li>
<li class="no-trains" v-if="sceneryScheduledTrains?.length == 0">
Brak aktywnych rozkładów
</li>
</ul>
</div> </div>
</div> </div>
</div> </div>
@@ -106,6 +99,7 @@ import {
import http from '../http'; import http from '../http';
import { ISceneryData } from '../types/dataTypes'; import { ISceneryData } from '../types/dataTypes';
import { API } from '../types/apiTypes'; import { API } from '../types/apiTypes';
import { getRegionNameById } from '../utils/sceneryUtils';
export default defineComponent({ export default defineComponent({
name: 'order-train-picker', name: 'order-train-picker',
@@ -115,8 +109,7 @@ export default defineComponent({
sceneriesData: undefined as ISceneryData[] | undefined, sceneriesData: undefined as ISceneryData[] | undefined,
activeData: undefined as API.ActiveData.Response | undefined, activeData: undefined as API.ActiveData.Response | undefined,
selectedSceneryName: null as string | null, selectedSceneryId: null as string | null,
selectedDispatcherName: null as string | null,
selectedCheckpointName: null as string | null, selectedCheckpointName: null as string | null,
fillCheckpointName: false, fillCheckpointName: false,
@@ -137,7 +130,7 @@ export default defineComponent({
this.refreshInterval = window.setInterval(() => { this.refreshInterval = window.setInterval(() => {
this.fetchActiveData(); this.fetchActiveData();
}, 35 * 1000); }, 25 * 1000);
}, },
deactivated() { deactivated() {
@@ -145,82 +138,64 @@ export default defineComponent({
}, },
watch: { watch: {
selectedDispatcherName() {
if (!this.sceneryNameList) return null;
this.selectedSceneryName = this.sceneryNameList.length == 0 ? null : this.sceneryNameList[0];
},
selectedSceneryName() {
this.selectedCheckpointName =
this.checkpointNameList.length == 0 ? null : this.checkpointNameList[0];
},
fillCheckpointName(val: boolean) { fillCheckpointName(val: boolean) {
window.localStorage.setItem('fill-checkpoint', `${val}`); window.localStorage.setItem('fill-checkpoint', `${val}`);
} }
}, },
computed: { computed: {
selectedSceneryHash() { selectedScenery() {
return this.activeData?.activeSceneries?.find( return this.activeData?.activeSceneries?.find(
(s) => this.selectedSceneryName == s.stationName (scenery) =>
)?.stationHash; this.selectedSceneryId ==
}, `${scenery.stationName}|${scenery.stationHash}|${scenery.dispatcherName}|${scenery.region}`
sceneriesOnlinePL1() {
return this.activeData?.activeSceneries?.filter((s) => s.region == 'eu' && s.isOnline);
},
dispatcherNameList() {
return [...new Set(this.sceneriesOnlinePL1?.map((s) => s.dispatcherName))].sort((a, b) =>
a.toLocaleLowerCase() < b.toLocaleLowerCase() ? -1 : 1
); );
}, },
sceneryNameList() { filteredSceneries() {
if (!this.sceneriesOnlinePL1) return []; return this.activeData?.activeSceneries
?.filter((s) => s.isOnline)
return this.sceneriesOnlinePL1 .sort((s1, s2) => s1.stationName.localeCompare(s2.stationName));
.filter((s) => s.dispatcherName == this.selectedDispatcherName)
.map((s) => s.stationName)
.sort((a, b) => (a < b ? -1 : 1));
}, },
checkpointNameList() { checkpointNameList() {
if (!this.selectedSceneryName) return []; if (!this.selectedScenery) return [];
const name = this.selectedSceneryName; const checkpoints =
const checkpoints = this.sceneriesData?.find( this.sceneriesData?.find((s) => s.name == this.selectedScenery?.stationName)?.checkpoints ??
(s) => s.name.toLocaleLowerCase() == name.toLocaleLowerCase() '';
)?.checkpoints;
if (!checkpoints || checkpoints.length == 0) return [name]; if (checkpoints.length == 0) return [this.selectedScenery.stationName];
return checkpoints.split(';'); return checkpoints.split(';');
}, },
sceneryTrains() { sceneryTrains() {
return this.activeData?.trains?.filter( if (!this.selectedScenery || !this.activeData?.trains) return [];
(t) =>
t.online &&
t.currentStationName == this.selectedSceneryName &&
this.selectedSceneryName &&
!t.timetable
);
},
sceneryScheduledTrains() { const scenery = this.selectedScenery;
if (!this.selectedSceneryHash) return [];
const hash = this.selectedSceneryHash;
return this.activeData?.trains return this.activeData.trains
?.filter((t) => t.timetable?.sceneries.includes(hash)) ?.filter(
.sort((t1, t2) => t1.trainNo - t2.trainNo); (t) =>
(t.currentStationName == scenery.stationName &&
t.region == scenery.region &&
(t.online || t.lastSeen < Date.now() - 60000)) ||
t.timetable?.path.includes(`${scenery.stationName} ${scenery.stationHash}.sc`)
)
.sort((t1, t2) => {
return (
(t2.currentStationName == scenery.stationName ? 1 : -1) -
(t1.currentStationName == scenery.stationName ? 1 : -1) ||
t1.driverName.localeCompare(t2.driverName)
);
});
} }
}, },
methods: { methods: {
getRegionNameById,
async fetchSceneriesData() { async fetchSceneriesData() {
const data: ISceneryData[] = (await http.get<ISceneryData[]>('api/getSceneries')).data; const data: ISceneryData[] = (await http.get<ISceneryData[]>('api/getSceneries')).data;
@@ -233,22 +208,27 @@ export default defineComponent({
this.activeData = data; this.activeData = data;
}, },
selectSceneryOption() {
this.selectedCheckpointName =
this.checkpointNameList.length == 0 ? null : this.checkpointNameList[0];
},
fillOrder(trainNo: number) { fillOrder(trainNo: number) {
if (!this.selectedDispatcherName || !this.selectedSceneryName) return; if (!this.selectedScenery) return;
const chosenOrder = this.store[this.store.chosenOrderType]; const chosenOrder = this.store[this.store.chosenOrderType];
chosenOrder.header.trainNo = trainNo.toString(); chosenOrder.header.trainNo = trainNo.toString();
chosenOrder.header.date = currentFormattedDate(); chosenOrder.header.date = currentFormattedDate();
this.store.orderFooter.dispatcherName = this.selectedDispatcherName; this.store.orderFooter.dispatcherName = this.selectedScenery.stationName;
this.store.orderFooter.stationName = this.store.orderFooter.stationName =
this.selectedCheckpointName?.split(',')[0] || this.selectedSceneryName; this.selectedCheckpointName?.split(',')[0] || this.selectedScenery.stationName;
this.store.orderFooter.hour = currentFormattedHours(); this.store.orderFooter.hour = currentFormattedHours();
this.store.orderFooter.minutes = currentFormattedMinutes(); this.store.orderFooter.minutes = currentFormattedMinutes();
if (this.fillCheckpointName) { if (this.fillCheckpointName) {
const sceneryAbbrev = this.sceneriesData?.find( const sceneryAbbrev = this.sceneriesData?.find(
({ name }) => name === this.selectedSceneryName ({ name }) => name === this.selectedSceneryId
)?.abbr; )?.abbr;
this.store.orderFooter.checkpointName = this.store.orderFooter.checkpointName =
sceneryAbbrev || this.store.orderFooter.stationName.slice(0, 2); sceneryAbbrev || this.store.orderFooter.stationName.slice(0, 2);
@@ -264,27 +244,22 @@ export default defineComponent({
@import '../styles/global.scss'; @import '../styles/global.scss';
.order-train-picker { .order-train-picker {
height: 90vh;
overflow-y: auto;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
overflow: auto;
padding: 0 0.5em;
} }
.options { .options {
display: flex; display: flex;
width: 100%;
justify-content: center;
gap: 1em;
flex-wrap: wrap; flex-wrap: wrap;
width: 100%;
gap: 0.5em;
label { label {
width: 45%; width: 100%;
text-align: center;
display: flex;
align-items: center;
} }
select { select {
@@ -293,8 +268,6 @@ export default defineComponent({
font-size: 1em; font-size: 1em;
width: 100%; width: 100%;
&[disabled] { &[disabled] {
color: gray; color: gray;
} }
@@ -305,27 +278,41 @@ export default defineComponent({
margin-top: 1em; margin-top: 1em;
width: 100%; width: 100%;
text-align: center; text-align: center;
p {
margin: 1em 0;
font-weight: bold;
font-size: 1.1em;
}
} }
ul.train-list { ul.train-list {
li { padding: 1px;
li.no-trains {
font-weight: bold;
background-color: $bgColDarker;
padding: 0.5em;
margin-top: 0.5em;
}
li > button {
width: 100%;
background-color: $bgColDarker; background-color: $bgColDarker;
padding: 0.5em; padding: 0.5em;
margin-top: 0.5em; margin-top: 0.5em;
cursor: pointer; &:hover {
background-color: $bgColLighter;
}
&.no-trains { &:focus-visible {
font-weight: bold; outline: 1px solid $accentCol;
cursor: default;
} }
} }
} }
.online-indicator {
display: inline-block;
width: 9px;
height: 9px;
vertical-align: middle;
background-color: greenyellow;
border-radius: 100%;
margin: 0 5px;
}
</style> </style>
+1
View File
@@ -10,6 +10,7 @@ export const useStore = defineStore('store', {
state: () => { state: () => {
return { return {
helperModalOpen: false, helperModalOpen: false,
orderDarkMode: false,
chosenOrderType: 'orderN' as TOrder, chosenOrderType: 'orderN' as TOrder,
chosenLocalOrderId: '', chosenLocalOrderId: '',
+7 -5
View File
@@ -194,17 +194,18 @@ label.g-checkbox {
cursor: pointer; cursor: pointer;
color: #aaa; color: #aaa;
user-select: none;
-moz-user-select: none;
span { span {
transition: color 125ms ease; transition: color 125ms ease;
} }
span::before { span::before {
content: ''; content: '\2717';
display: inline-block; display: inline-block;
width: 1ch;
height: 1ch;
background-color: #aaa; // background-color: #aaa;
border-radius: 50%; border-radius: 50%;
margin-right: 0.25em; margin-right: 0.25em;
@@ -223,7 +224,8 @@ label.g-checkbox {
color: greenyellow; color: greenyellow;
&::before { &::before {
background-color: greenyellow; content: '\2713';
// background-color: greenyellow;
} }
} }
} }
+1
View File
@@ -100,6 +100,7 @@ export declare module API {
TWR: boolean; TWR: boolean;
SKR: boolean; SKR: boolean;
sceneries: string[]; sceneries: string[];
path: string;
} }
} }
} }
+21
View File
@@ -0,0 +1,21 @@
export const getRegionNameById = (id: string) => {
switch (id) {
case 'eu':
return 'PL1';
case 'cae':
return 'PL2';
case 'us':
return 'CZE';
case 'usw':
return 'DE';
case 'ru':
return 'ENG';
default:
return 'PL1';
}
};
+3 -2
View File
@@ -132,13 +132,14 @@ export default defineComponent({
} }
.message_container { .message_container {
width: 500px; width: 100%;
max-width: 500px;
display: grid; display: grid;
grid-template-rows: auto 1fr; grid-template-rows: auto 1fr;
overflow: hidden;
height: 95vh; height: 95vh;
overflow: auto;
} }
.message_nav { .message_nav {