Wersja 1.3.0

Merge do wersji 1.3.0
This commit is contained in:
Spythere
2023-03-02 01:00:12 +01:00
committed by GitHub
30 changed files with 932 additions and 732 deletions
+1 -2
View File
@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="pl"> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
@@ -7,7 +7,6 @@
<title>Pojazdownik</title> <title>Pojazdownik</title>
<meta name="description" content="Edytor pociągów online do symulatora Train Driver 2" /> <meta name="description" content="Edytor pociągów online do symulatora Train Driver 2" />
<meta name="keywords" content="pojazdownik td2 train driver stacjownik spythere" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" /> <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "pojazdownik", "name": "pojazdownik",
"version": "1.2.3", "version": "1.3.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
Binary file not shown.

After

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 953 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1020 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

+14 -17
View File
@@ -34,6 +34,7 @@
regulaminem symulatora Train Driver 2</a regulaminem symulatora Train Driver 2</a
>! >!
</div> </div>
<div class="text--grayed" style="margin-bottom: 0.25em">Strona jest kompletna dla wersji 2022.2.2 symulatora TD2</div>
&copy; &copy;
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a> <a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
{{ new Date().getUTCFullYear() }} | v{{ VERSION }} {{ new Date().getUTCFullYear() }} | v{{ VERSION }}
@@ -46,13 +47,13 @@ import packageInfo from '.././package.json';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import InputsSection from './components/InputsSection.vue'; import InputsSection from './components/sections/InputsSection.vue';
import { useStore } from './store'; import { useStore } from './store';
import TrainImageSection from './components/TrainImageSection.vue'; import TrainImageSection from './components/sections/TrainImageSection.vue';
import LogoSection from './components/LogoSection.vue'; import LogoSection from './components/sections/LogoSection.vue';
import RealStockCard from './components/cards/RealStockCard.vue'; import RealStockCard from './components/cards/RealStockCard.vue';
import StockSection from './components/StockSection.vue'; import StockSection from './components/sections/StockSection.vue';
export default defineComponent({ export default defineComponent({
components: { components: {
@@ -70,7 +71,7 @@ export default defineComponent({
async created() { async created() {
const stockData = await ( const stockData = await (
await fetch(`https://spythere.github.io/api/td2/data/stockData.json?t=${Math.floor(Date.now() / 60000)}`) await fetch(`https://spythere.github.io/api/td2/data/stockInfo.json?t=${Math.floor(Date.now() / 60000)}`)
).json(); ).json();
this.store.stockData = stockData; this.store.stockData = stockData;
@@ -86,7 +87,7 @@ export default defineComponent({
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 1em; align-items: center;
} }
/* APP */ /* APP */
@@ -95,9 +96,7 @@ export default defineComponent({
color: $textColor; color: $textColor;
font-size: 1em; font-size: 1em;
padding: 1em 0.5em;
display: flex;
justify-content: center;
} }
/* HEADER SECTION */ /* HEADER SECTION */
@@ -145,14 +144,14 @@ main {
display: grid; display: grid;
gap: 1em 3em; gap: 1em 3em;
width: 100vw; width: 100%;
max-width: 1300px; max-width: 1300px;
min-height: 75vh; min-height: 75vh;
grid-template-columns: 1fr 2fr; grid-template-columns: 1fr 2fr;
grid-template-rows: auto 360px minmax(400px, 1fr); grid-template-rows: auto 360px minmax(400px, 1fr);
padding: 0 1em; // padding: 0 1em;
margin-bottom: 2em; margin-bottom: 2em;
} }
@@ -167,6 +166,10 @@ footer {
/* MOBILE VIEWS */ /* MOBILE VIEWS */
@media screen and (max-width: $breakpointMd) { @media screen and (max-width: $breakpointMd) {
#app {
font-size: calc(0.7rem + 0.75vw);
}
main { main {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -174,11 +177,5 @@ footer {
grid-template-rows: 1fr; grid-template-rows: 1fr;
} }
} }
// @media screen and (max-width: $breakpointSm) {
// header {
// font-size: 0.75em;
// }
// }
</style> </style>
-76
View File
@@ -1,76 +0,0 @@
<template>
<div class="stock-section">
<transition name="tab-change" mode="out-in">
<keep-alive>
<component :is="chosenSectionComponent" :key="chosenSectionComponent"></component>
</keep-alive>
</transition>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { useStore } from '../store';
import StockListTab from './StockListTab.vue';
import StockGeneratorTab from './StockGeneratorTab.vue';
export default defineComponent({
setup() {
return {
store: useStore(),
};
},
computed: {
chosenSectionComponent() {
switch (this.store.stockSectionMode) {
case 'stock-list':
return StockListTab;
case 'stock-generator':
return StockGeneratorTab;
default:
return StockListTab;
}
},
},
});
</script>
<style lang="scss">
// Tab change animation
.tab-change {
&-enter-from,
&-leave-to {
opacity: 0;
}
&-enter-active,
&-leave-active {
transition: all 100ms ease-in-out;
}
}
// Section styles
.stock-section {
grid-row: 1 / 4;
grid-column: 2;
}
// Stock tabs styles
.stock_actions {
display: flex;
gap: 0.5em;
@media only screen and (max-width: 450px) {
flex-wrap: wrap;
button {
width: 100%;
}
}
}
</style>
+5 -51
View File
@@ -41,18 +41,18 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { Vehicle, IStock, IReadyStockList } from '../../types'; import { Vehicle, IReadyStockList } from '../../types';
import { useStore } from '../../store'; import { useStore } from '../../store';
import { isLocomotive } from '../../utils/vehicleUtils';
import imageMixin from '../../mixins/imageMixin'; import imageMixin from '../../mixins/imageMixin';
import stockMixin from '../../mixins/stockMixin';
interface ResponseJSONData { interface ResponseJSONData {
[key: string]: string; [key: string]: string;
} }
export default defineComponent({ export default defineComponent({
mixins: [imageMixin], mixins: [imageMixin, stockMixin],
setup() { setup() {
return { return {
@@ -88,60 +88,14 @@ export default defineComponent({
}, },
choseStock(name: string, type: string, number: string, stockString: string) { choseStock(name: string, type: string, number: string, stockString: string) {
const stockArray = stockString.split(';'); this.loadStockFromString(stockString);
this.store.stockList.length = 0;
this.store.chosenVehicle = null;
this.store.chosenCar = null;
this.store.chosenCargo = null;
this.store.chosenLoco = null;
this.store.chosenStockListIndex = -1;
this.store.swapVehicles = false;
stockArray.forEach((type, i) => {
let vehicle: Vehicle | null = null;
if (i == 0) vehicle = this.store.locoDataList.find((loco) => loco.type == stockArray[0]) || null;
else vehicle = this.store.carDataList.find((car) => car.type == type) || null;
this.addVehicle(vehicle);
});
this.store.isRealStockListCardOpen = false; this.store.isRealStockListCardOpen = false;
}, },
addVehicle(vehicle: Vehicle | null) {
if (!vehicle) return;
const stockObj: IStock = {
id: `${Date.now() + this.store.stockList.length}`,
type: vehicle.type,
length: vehicle.length,
mass: vehicle.mass,
maxSpeed: vehicle.maxSpeed,
isLoco: isLocomotive(vehicle),
cargo: undefined,
count: 1,
imgSrc: vehicle.imageSrc,
useType: isLocomotive(vehicle) ? vehicle.power : vehicle.useType,
supportersOnly: vehicle.supportersOnly,
};
const previousStock =
this.store.stockList.length > 0 ? this.store.stockList[this.store.stockList.length - 1] : null;
if (previousStock && previousStock.type == vehicle.type) {
this.store.stockList[this.store.stockList.length - 1].count++;
return;
}
this.store.stockList.push(stockObj);
},
}, },
async mounted() { async mounted() {
const readyStockJSONData: ResponseJSONData = await ( const readyStockJSONData: ResponseJSONData = await (
await fetch(`https://spythere.github.io/api/readyStock.json?t=${Math.floor(Date.now() / 60000)}`) await fetch(`https://spythere.github.io/api/td2/data/readyStock.json?t=${Math.floor(Date.now() / 60000)}`)
).json(); ).json();
if (!readyStockJSONData) { if (!readyStockJSONData) {
@@ -7,7 +7,7 @@
<div class="vehicle-types locos"> <div class="vehicle-types locos">
<button <button
v-for="locoType in locomotiveTypeList" v-for="locoType in locomotiveTypeList"
class="btn--choice" class="btn btn--choice"
:data-selected="locoType.id == store.chosenLocoPower" :data-selected="locoType.id == store.chosenLocoPower"
@click="selectLocoType(locoType.id)" @click="selectLocoType(locoType.id)"
> >
@@ -34,7 +34,7 @@
<div class="vehicle-types carwagons"> <div class="vehicle-types carwagons">
<button <button
v-for="carType in carTypeList" v-for="carType in carTypeList"
class="btn--choice" class="btn btn--choice"
:data-selected="carType.id == store.chosenCarUseType" :data-selected="carType.id == store.chosenCarUseType"
@click="selectCarWagonType(carType.id)" @click="selectCarWagonType(carType.id)"
> >
@@ -85,7 +85,7 @@
</div> </div>
<div class="input_actions"> <div class="input_actions">
<button class="btn" @click="addVehicle">DODAJ NOWY</button> <button class="btn" @click="addVehicle(store.chosenVehicle, store.chosenCargo)">DODAJ NOWY</button>
<button <button
class="btn" class="btn"
@click="switchVehicles" @click="switchVehicles"
@@ -107,11 +107,12 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { IStock } from '../types'; import { IStock } from '../../types';
import imageMixin from '../mixins/imageMixin'; import imageMixin from '../../mixins/imageMixin';
import { useStore } from '../store'; import { useStore } from '../../store';
import { isLocomotive } from '../utils/vehicleUtils'; import { isLocomotive } from '../../utils/vehicleUtils';
import stockPreviewMixin from '../mixins/stockPreviewMixin'; import stockPreviewMixin from '../../mixins/stockPreviewMixin';
import stockMixin from '../../mixins/stockMixin';
interface ILocoType { interface ILocoType {
id: string; id: string;
@@ -120,7 +121,7 @@ interface ILocoType {
} }
export default defineComponent({ export default defineComponent({
mixins: [imageMixin, stockPreviewMixin], mixins: [imageMixin, stockPreviewMixin, stockMixin],
data: () => ({ data: () => ({
locomotiveTypeList: [ locomotiveTypeList: [
@@ -174,7 +175,9 @@ export default defineComponent({
}, },
addOrSwitchVehicle() { addOrSwitchVehicle() {
if (this.store.chosenStockListIndex == -1) this.addVehicle(); if (!this.store.chosenVehicle) return;
if (this.store.chosenStockListIndex == -1) this.addVehicle(this.store.chosenVehicle, this.store.chosenCargo);
else this.switchVehicles(); else this.switchVehicles();
}, },
@@ -211,60 +214,16 @@ export default defineComponent({
this.store.stockList[this.store.chosenStockListIndex] = stockObj; this.store.stockList[this.store.chosenStockListIndex] = stockObj;
}, },
addVehicle() {
const vehicle = this.store.chosenVehicle;
if (!vehicle) return;
const stockObj: IStock = {
id: `${Date.now()}`,
useType: isLocomotive(vehicle) ? vehicle.power : vehicle.useType,
type: vehicle.type,
length: vehicle.length,
mass: vehicle.mass,
maxSpeed: vehicle.maxSpeed,
isLoco: isLocomotive(vehicle),
cargo:
!isLocomotive(vehicle) && vehicle.loadable && this.store.chosenCargo ? this.store.chosenCargo : undefined,
count: 1,
imgSrc: vehicle.imageSrc,
supportersOnly: vehicle.supportersOnly,
};
const previousStock =
this.store.stockList.length > 0 ? this.store.stockList[this.store.stockList.length - 1] : null;
if (isLocomotive(vehicle) && previousStock && previousStock.type == vehicle.type) {
this.store.stockList[this.store.stockList.length - 1].count++;
return;
}
if (
!isLocomotive(vehicle) &&
previousStock &&
previousStock.type == vehicle.type &&
previousStock.cargo?.id == this.store.chosenCargo?.id
) {
this.store.stockList[this.store.stockList.length - 1].count++;
return;
}
if (isLocomotive(vehicle) && this.store.stockList.length > 0 && !this.store.stockList[0].isLoco)
this.store.stockList.unshift(stockObj);
else this.store.stockList.push(stockObj);
},
}, },
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../styles/global'; @import '../../styles/global';
.inputs-section { .inputs-section {
display: flex; display: flex;
justify-content: space-between; justify-content: center;
grid-row: 2; grid-row: 2;
grid-column: 1; grid-column: 1;
@@ -274,11 +233,9 @@ export default defineComponent({
margin-bottom: 1em; margin-bottom: 1em;
} }
.btn--choice { button.btn--choice {
margin-right: 0.5em; font-size: 0.9em;
font-weight: bold; padding: 0.3em 0.6em;
background-color: #444;
&[data-selected='true'] { &[data-selected='true'] {
background-color: $accentColor; background-color: $accentColor;
@@ -305,15 +262,19 @@ export default defineComponent({
} }
.input_actions { .input_actions {
display: flex; display: grid;
flex-wrap: wrap; grid-template-columns: repeat(2, 1fr);
gap: 0.5em;
button { button:nth-child(3) {
margin: 0.5em 0.5em 0 0; grid-column: 1 / 3;
} }
} }
.vehicle-types { .vehicle-types {
display: flex;
gap: 0.25em;
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
@@ -323,7 +284,7 @@ export default defineComponent({
text-align: center; text-align: center;
} }
.input_actions { .vehicle-types {
justify-content: center; justify-content: center;
} }
} }
+114
View File
@@ -0,0 +1,114 @@
<template>
<section class="stock-section">
<div class="section_modes">
<button
class="btn"
v-for="(id, name) in sectionModes"
@click="chooseSection(id)"
:data-selected="store.stockSectionMode == id"
>
{{ name }}
</button>
</div>
<transition name="tab-change" mode="out-in">
<keep-alive>
<component :is="chosenSectionComponent" :key="chosenSectionComponent"></component>
</keep-alive>
</transition>
</section>
</template>
<script lang="ts" setup>
import { computed, KeepAlive } from 'vue';
import { useStore } from '../../store';
import StockListTab from '../tabs/StockListTab.vue';
import StockGeneratorTab from '../tabs/StockGeneratorTab.vue';
import NumberGeneratorTab from '../tabs/NumberGeneratorTab.vue';
const store = useStore();
type SectionMode = typeof store.stockSectionMode;
const sectionModes: { [key: string]: SectionMode } = {
SKŁAD: 'stock-list',
'GNR. NUMERU': 'number-generator',
'GNR. SKŁADU': 'stock-generator',
};
const chosenSectionComponent = computed(() => {
switch (store.stockSectionMode) {
case 'stock-list':
return StockListTab;
case 'stock-generator':
return StockGeneratorTab;
case 'number-generator':
return NumberGeneratorTab;
default:
return StockListTab;
}
});
function chooseSection(sectionId: SectionMode) {
store.stockSectionMode = sectionId;
}
</script>
<style lang="scss">
@import '../../styles/global.scss';
// Tab change animation
.tab-change {
&-enter-from,
&-leave-to {
opacity: 0;
}
&-enter-active,
&-leave-active {
transition: all 100ms ease-in-out;
}
}
// Section styles
.stock-section {
grid-row: 1 / 4;
grid-column: 2;
overflow: hidden;
padding: 1px;
}
.section_modes {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.5em;
margin-bottom: 0.5em;
button {
position: relative;
border-radius: 0.5em 0.5em 0 0;
&::after {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
content: '';
width: 0;
height: 2px;
transition: all 100ms;
background-color: $accentColor;
}
&[data-selected='true']::after {
width: 100%;
}
}
}
</style>
@@ -54,9 +54,9 @@
<script lang="ts"> <script lang="ts">
import { computed, defineComponent } from 'vue'; import { computed, defineComponent } from 'vue';
import { useStore } from '../store'; import { useStore } from '../../store';
import { isLocomotive } from '../utils/vehicleUtils'; import { isLocomotive } from '../../utils/vehicleUtils';
import { ILocomotive, Vehicle } from '../types'; import { ILocomotive, Vehicle } from '../../types';
export default defineComponent({ export default defineComponent({
setup() { setup() {
@@ -83,8 +83,6 @@ export default defineComponent({
watch: { watch: {
chosenVehicle(vehicle: Vehicle, prevVehicle: Vehicle) { chosenVehicle(vehicle: Vehicle, prevVehicle: Vehicle) {
console.log(vehicle);
if (vehicle && vehicle.type != prevVehicle?.type) { if (vehicle && vehicle.type != prevVehicle?.type) {
this.store.imageLoading = true; this.store.imageLoading = true;
} }
@@ -112,7 +110,7 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../styles/global.scss'; @import '../../styles/global.scss';
.train-image-section { .train-image-section {
grid-row: 3; grid-row: 3;
+133
View File
@@ -0,0 +1,133 @@
<template>
<div class="number-generator tab">
<div class="tab_header">
<h2>GENERATOR NUMERU POCIĄGU</h2>
</div>
<div class="tab_content">
<div class="options">
<select v-model="beginRegionName" @change="randomizeTrainNumber">
<option :value="null" disabled>Początkowy obszar konstrukcyjny</option>
<option v-for="(_, name) in genData.regionNumbers" :value="name">{{ name }}</option>
</select>
<select v-model="endRegionName" @change="randomizeTrainNumber">
<option :value="null" disabled>Końcowy obszar konstrukcyjny</option>
<option v-for="(_, name) in genData.regionNumbers" :value="name">{{ name }}</option>
</select>
<select v-model="categoryRules" @change="randomizeTrainNumber">
<option :value="null" disabled>Kategoria pociągu</option>
<option v-for="(rules, category) in genData.categories" :value="rules">{{ category }}</option>
</select>
</div>
<div class="generated-number">
<span v-if="trainNumber">Wygenerowany numer pociągu: <b class="text--accent">{{ trainNumber }}</b></span>
<span v-else>Wybierz obszary konstrukcyjne i kategorię!</span>
</div>
<hr>
<div class="tab_actions">
<button class="btn" @click="randomizeTrainNumber">PRZELOSUJ</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { Ref, ref } from 'vue';
import genData from '../../constants/numberGeneratorData.json';
type RegionName = keyof typeof genData.regionNumbers;
const beginRegionName = ref(null) as Ref<RegionName | null>;
const endRegionName = ref(null) as Ref<RegionName | null>;
const categoryRules = ref(null) as Ref<string | null>;
const trainNumber = ref(null) as Ref<string | null>;
const randomizeTrainNumber = () => {
if (beginRegionName.value == null || endRegionName.value == null || categoryRules.value == null) return '';
let number = '';
if (beginRegionName.value == endRegionName.value) {
const sameRegionsNumbers = genData.sameRegions[beginRegionName.value];
const randRegionNumber = sameRegionsNumbers[Math.floor(Math.random() * sameRegionsNumbers.length)];
number += randRegionNumber.toString();
} else {
const beginRegionNumber = genData.regionNumbers[beginRegionName.value];
const endRegionNumber = genData.regionNumbers[endRegionName.value];
number += `${beginRegionNumber}${endRegionNumber}`;
}
const rulesArray = categoryRules.value.split(';').map((r) => ({
index: r.split(':')[0],
rule: r.split(':')[1],
nums: Number(r.split(':')[2] || '1'),
}));
rulesArray.forEach((r) => {
const range = r.rule.split('-');
if (range.length == 1) number += r.rule;
else {
const [minRange, maxRange] = range;
const randRange = Math.floor(Math.random() * (Number(maxRange) - Number(minRange)) + Number(minRange)).toString();
number += new Array(Math.abs(randRange.length - r.nums)).fill('0').join('') + randRange;
}
});
trainNumber.value = number;
};
</script>
<style lang="scss" scoped>
@import '../../styles/tab.scss';
@import '../../styles/global.scss';
.options {
display: flex;
flex-wrap: wrap;
gap: 0.5em;
select {
width: calc(50% - 0.5em);
}
}
.generated-number {
font-size: 1.3em;
font-weight: bold;
margin: 0.5em 0;
padding: 0.5em;
background-color: $secondaryColor;
}
.tab_actions {
margin-top: 0.5em;
button {
grid-column: 3;
}
}
@media screen and (max-width: $breakpointMd) {
.number-generator {
min-height: 100vh;
}
}
@media screen and (max-width: $breakpointSm) {
.options select {
width: 100%;
}
}
</style>
@@ -1,58 +1,67 @@
<template> <template>
<div class="stock-generator"> <div class="stock-generator tab">
<div class="stock_actions"> <div class="tab_header">
<h2>GENERATOR SKŁADU TOWAROWEGO</h2> <h2>GENERATOR SKŁADU TOWAROWEGO</h2>
<button class="btn" @click="() => (store.stockSectionMode = 'stock-list')">POWRÓT DO LISTY &gt;</button>
</div> </div>
<div class="generator_content"> <div class="tab_content">
<h2>WŁAŚCIWOŚCI SKŁADU</h2> <div>
<h2>WŁAŚCIWOŚCI SKŁADU</h2>
<div class="generator_attributes"> <b class="text--accent">
<label> &lArr; Dodaj lokomotywę na pierwsze miejsce listy, aby uwzględnić przy losowaniu składu!
Maksymalna masa (t) </b>
<input type="number" v-model="maxMass" step="100" max="4000" min="0" />
</label>
<label> <div class="tab_attributes">
Maks. długość (m) <label>
<input type="number" v-model="maxLength" step="25" max="650" min="0" /> Maksymalna masa (t)
</label> <input type="number" v-model="maxMass" step="100" max="4000" min="0" />
</label>
<label> <label>
Maks. liczba wagonów Maks. długość (m)
<input type="number" v-model="maxCarCount" step="1" max="60" min="1" /> <input type="number" v-model="maxLength" step="25" max="650" min="0" />
</label> </label>
<label>
Maks. liczba wagonów
<input type="number" v-model="maxCarCount" step="1" max="60" min="1" />
</label>
</div>
</div> </div>
<h2>ŁADUNEK</h2> <div>
<p>Wybierz ładunki, którymi chcesz wypełnić dostępne wagony:</p> <h2>ŁADUNEK</h2>
<b>Wybierz ładunki, którymi chcesz wypełnić dostępne wagony:</b>
</div>
<div class="generator_cargo"> <div class="generator_cargo">
<button <button
class="btn" class="btn"
:data-chosen="chosenCargoTypes.includes(k as string)" :data-chosen="chosenCargoTypes.includes(k.toString())"
v-for="(v, k) in store.stockData?.generator.cargo" v-for="(v, k) in store.stockData?.generator.cargo"
@click="toggleCargoChosen(k as string, v)" @click="toggleCargoChosen(k.toString(), v)"
> >
{{ k }} {{ k }}
</button> </button>
</div> </div>
<h2>WAGONY Z WYBRANYMI ŁADUNKAMI</h2> <div>
<h2>WAGONY Z WYBRANYMI ŁADUNKAMI</h2>
<div class="warning"> <div class="generator_warning">
<span v-if="computedChosenCarTypes.size == 0"> <span v-if="computedChosenCarTypes.size == 0">
Wybierz co najmniej jeden ładunek, aby zobaczyć wagony, które go posiadają! Wybierz co najmniej jeden ładunek, aby zobaczyć wagony, które go posiadają!
</span> </span>
<span v-else> <span v-else>
Wagony posiadające wybrane ładunki. Najedź na nazwę, aby zobaczyć podgląd wagonu. Kliknij, aby wyłączyć z Wagony posiadające wybrane ładunki. Najedź na nazwę, aby zobaczyć podgląd wagonu. Kliknij, aby wyłączyć z
losowania (tylko podświetlone nazwy będą uwzględnione). losowania (tylko podświetlone nazwy będą uwzględnione).
</span> </span>
</div>
</div> </div>
<div class="generator_vehicles"> <div class="generator_vehicles" v-if="computedChosenCarTypes.size != 0">
<button <button
:data-chosen="true" :data-chosen="true"
:data-excluded="excludedCarTypes.includes(carType)" :data-excluded="excludedCarTypes.includes(carType)"
@@ -64,14 +73,12 @@
@click="toggleCarExclusion(carType)" @click="toggleCarExclusion(carType)"
> >
{{ carType }} {{ carType }}
<!-- <span>X</span> -->
</button> </button>
</div> </div>
<hr /> <hr />
<div class="generator_actions"> <div class="tab_actions">
<button class="btn" :data-disabled="computedChosenCarTypes.size == 0" @click="generateStock()"> <button class="btn" :data-disabled="computedChosenCarTypes.size == 0" @click="generateStock()">
WYGENERUJ WYGENERUJ
</button> </button>
@@ -89,15 +96,16 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { useStore } from '../store'; import { useStore } from '../../store';
import stockMixin from '../mixins/stockMixin'; import stockMixin from '../../mixins/stockMixin';
import { ICargo, ICarWagon } from '../types'; import { ICargo, ICarWagon } from '../../types';
import warningsMixin from '../../mixins/warningsMixin';
export default defineComponent({ export default defineComponent({
name: 'stock-generator', name: 'stock-generator',
mixins: [stockMixin], mixins: [stockMixin, warningsMixin],
data() { data() {
return { return {
@@ -172,13 +180,16 @@ export default defineComponent({
return acc; return acc;
}, [] as { constructionType: string; carPool: { carWagon: ICarWagon; cargo?: ICargo }[] }[]); }, [] as { constructionType: string; carPool: { carWagon: ICarWagon; cargo?: ICargo }[] }[]);
this.store.stockList.length = this.store.stockList[0]?.isLoco ? 1 : 0; const headingLoco = this.store.stockList[0]?.isLoco ? this.store.stockList[0] : undefined;
this.store.stockList.length = headingLoco ? 1 : 0;
const maxMass = this.store.acceptableMass || this.maxMass;
new Array(this.maxCarCount).fill(0).forEach(() => { new Array(this.maxCarCount).fill(0).forEach(() => {
const randomStockType = generatedChosenStockList[~~(Math.random() * generatedChosenStockList.length)]; const randomStockType = generatedChosenStockList[~~(Math.random() * generatedChosenStockList.length)];
const {carWagon, cargo} = randomStockType.carPool[~~(Math.random() * randomStockType.carPool.length)]; const { carWagon, cargo } = randomStockType.carPool[~~(Math.random() * randomStockType.carPool.length)];
if (this.store.totalMass + (cargo?.totalMass || carWagon.mass) > this.maxMass) return; if (this.store.totalMass + (cargo?.totalMass || carWagon.mass) > maxMass) return;
if (this.store.totalLength + carWagon.length > this.maxLength) return; if (this.store.totalLength + carWagon.length > this.maxLength) return;
this.addCarWagon(carWagon, cargo); this.addCarWagon(carWagon, cargo);
@@ -226,51 +237,8 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../styles/global.scss'; @import '../../styles/global.scss';
@import '../../styles/tab.scss';
.stock_actions {
align-items: center;
h2 {
margin: 0;
color: white;
font-size: 1.35em;
text-align: center;
}
button {
margin-left: auto;
}
}
.stock-generator {
height: 100%;
}
.generator_content {
margin-top: 1em;
height: 100%;
}
h2 {
margin: 1em 0;
}
.generator_attributes {
display: flex;
flex-wrap: wrap;
gap: 1em;
label {
display: flex;
flex-direction: column;
}
input {
max-width: 250px;
margin-top: 0.5em;
}
}
.generator_cargo, .generator_cargo,
.generator_vehicles { .generator_vehicles {
@@ -313,36 +281,13 @@ h2 {
} }
} }
.generator_vehicles { .tab_content {
margin-top: 1em; display: flex;
flex-direction: column;
gap: 1em;
} }
hr { .generator_warning {
height: 3px;
background-color: white;
outline: none;
margin: 15px 0;
}
.generator_actions {
display: grid;
gap: 0.5em;
grid-template-columns: repeat(3, 1fr);
button {
background-color: #131313;
padding: 0.5em;
font-weight: bold;
}
&[data-disabled] button {
opacity: 0.75;
}
}
.warning {
background-color: $accentColor; background-color: $accentColor;
padding: 0.5em; padding: 0.5em;
text-align: justify; text-align: justify;
@@ -355,17 +300,6 @@ hr {
.generator_vehicles { .generator_vehicles {
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
} }
.generator_attributes {
label {
width: 100%;
}
input {
max-width: 100%;
width: 100%;
}
}
} }
</style> </style>
@@ -1,78 +1,53 @@
<template> <template>
<section class="stock-list"> <section class="stock-list">
<div class="stock_actions">
<button class="btn" @click="downloadStock">POBIERZ POCIĄG</button>
<button class="btn" @click="resetStock">ZRESETUJ LISTĘ</button>
<button class="btn" style="margin-left: auto" @click="shuffleCars">TASUJ WAGONY</button>
<button class="btn" @click="store.stockSectionMode = 'stock-generator'">LOSUJ SKŁAD</button>
</div>
<div class="stock_controls" :data-disabled="store.chosenStockListIndex == -1"> <div class="stock_controls" :data-disabled="store.chosenStockListIndex == -1">
<b class="no"> <b class="no">
POJAZD NR <span class="text--accent">{{ store.chosenStockListIndex + 1 }}</span> &nbsp; POJAZD NR <span class="text--accent">{{ store.chosenStockListIndex + 1 }}</span> &nbsp;
</b> </b>
<div class="count">
<button
class="action-btn"
:tabindex="store.chosenStockListIndex == -1 ? -1 : 0"
@click="subStock(store.chosenStockListIndex)"
>
<img :src="getIconURL('sub')" alt="subtract vehicle count" />
1
</button>
<input
v-if="chosenStockVehicle"
v-model="chosenStockVehicle.count"
type="number"
min="1"
name="stock-count"
id="stock-count"
/>
<input v-else id="stock-count" type="number" value="0" :tabindex="store.chosenStockListIndex == -1 ? -1 : 0" />
<button
class="action-btn"
:tabindex="store.chosenStockListIndex == -1 ? -1 : 0"
@click="addStock(store.chosenStockListIndex)"
>
<img :src="getIconURL('add')" alt="add vehicle count" />
1
</button>
</div>
<button <button
class="action-btn" class="btn"
:tabindex="store.chosenStockListIndex == -1 ? -1 : 0" :tabindex="store.chosenStockListIndex == -1 ? -1 : 0"
@click="moveUpStock(store.chosenStockListIndex)" @click="moveUpStock(store.chosenStockListIndex)"
> >
<img :src="getIconURL('higher')" alt="move up vehicle" /> <img :src="getIconURL('higher')" alt="move up vehicle" />
Przenieś wyżej PRZENIEŚ WYŻEJ
</button> </button>
<button <button
class="action-btn" class="btn"
:tabindex="store.chosenStockListIndex == -1 ? -1 : 0" :tabindex="store.chosenStockListIndex == -1 ? -1 : 0"
@click="moveDownStock(store.chosenStockListIndex)" @click="moveDownStock(store.chosenStockListIndex)"
> >
<img :src="getIconURL('lower')" alt="move down vehicle" /> <img :src="getIconURL('lower')" alt="move down vehicle" />
Przenieś niżej PRZENIEŚ NIŻEJ
</button> </button>
<button <button
class="action-btn" class="btn"
:tabindex="store.chosenStockListIndex == -1 ? -1 : 0" :tabindex="store.chosenStockListIndex == -1 ? -1 : 0"
@click="removeStock(store.chosenStockListIndex)" @click="removeStock(store.chosenStockListIndex)"
> >
<img :src="getIconURL('remove')" alt="remove vehicle" /> <img :src="getIconURL('remove')" alt="remove vehicle" />
Usuń USUŃ
</button> </button>
</div> </div>
<div class="stock_clipboard-text" v-if="store.stockList.length > 0"> <div class="stock_actions">
<button class="btn" @click="copyToClipboard">Skopiuj tekst składu do schowka</button> <label class="file-label">
<div class="btn">WCZYTAJ</div>
<input type="file" @change="uploadStock" ref="conFile" accept=".con,.txt" />
</label>
<button class="btn" :data-disabled="stockIsEmpty" :disabled="stockIsEmpty" @click="downloadStock">POBIERZ</button>
<button class="btn" :data-disabled="stockIsEmpty" :disabled="stockIsEmpty" @click="copyToClipboard">
SKOPIUJ
</button>
<button class="btn" :data-disabled="stockIsEmpty" :disabled="stockIsEmpty" @click="resetStock">ZRESETUJ</button>
<button class="btn" :data-disabled="stockIsEmpty" :disabled="stockIsEmpty" @click="shuffleCars">PRZETASUJ</button>
</div> </div>
<div class="stock_specs"> <div class="stock_specs">
@@ -85,9 +60,11 @@
</b> </b>
<span> <span>
Masa: <span class="text--accent">{{ store.totalMass }}t</span> - Długość: Masa: <span class="text--accent">{{ store.totalMass }}t</span> (dopuszczalna:
<span class="text--accent">{{ store.acceptableMass ? store.acceptableMass + 't' : '-' }}</span
>) - Długość:
<span class="text--accent">{{ store.totalLength }}m</span> <span class="text--accent">{{ store.totalLength }}m</span>
- Vmax pociągu: <span class="text--accent">{{ store.maxStockSpeed }} km/h</span> - vMax: <span class="text--accent">{{ store.maxStockSpeed }} km/h</span>
</span> </span>
</div> </div>
@@ -117,13 +94,15 @@
<div class="warning" v-if="tooManyLocomotives">Ten skład posiada za dużo pojazdów trakcyjnych!</div> <div class="warning" v-if="tooManyLocomotives">Ten skład posiada za dużo pojazdów trakcyjnych!</div>
</div> </div>
<StockThumbnails :onListItemClick="onListItemClick" :onStockImageError="stockImageError" />
<!-- Stock list --> <!-- Stock list -->
<ul ref="list"> <ul ref="stock_list">
<li v-if="store.stockList.length == 0" class="list-empty"> <li v-if="stockIsEmpty" class="list-empty">
<div class="stock-info">Lista pojazdów jest pusta!</div> <div class="stock-info">Lista pojazdów jest pusta!</div>
</li> </li>
<transition-group name="stock-list-anim"> <TransitionGroup name="stock-list-anim">
<li <li
v-for="(stock, i) in store.stockList" v-for="(stock, i) in store.stockList"
:key="stock.id" :key="stock.id"
@@ -156,29 +135,30 @@
<span class="stock-info__length"> {{ stock.length }}m </span> <span class="stock-info__length"> {{ stock.length }}m </span>
<span class="stock-info__mass">{{ stock.cargo ? stock.cargo.totalMass : stock.mass }}t </span> <span class="stock-info__mass">{{ stock.cargo ? stock.cargo.totalMass : stock.mass }}t </span>
<span class="stock-info__speed"> {{ stock.maxSpeed }}km/h </span> <span class="stock-info__speed"> {{ stock.maxSpeed }}km/h </span>
<span class="stock-info__count"> x{{ stock.count }} </span>
</div> </div>
</li> </li>
</transition-group> </TransitionGroup>
</ul> </ul>
</section> </section>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import TrainImage from './TrainImageSection.vue'; import TrainImage from '../sections/TrainImageSection.vue';
import { useStore } from '../store'; import { useStore } from '../../store';
import warningsMixin from '../mixins/warningsMixin'; import warningsMixin from '../../mixins/warningsMixin';
import imageMixin from '../mixins/imageMixin'; import imageMixin from '../../mixins/imageMixin';
import stockPreviewMixin from '../mixins/stockPreviewMixin'; import stockPreviewMixin from '../../mixins/stockPreviewMixin';
import { IStock } from '../../types';
import StockThumbnails from '../utils/StockThumbnails.vue';
import stockMixin from '../../mixins/stockMixin';
export default defineComponent({ export default defineComponent({
name: 'stock-list', name: 'stock-list',
components: { TrainImage }, components: { TrainImage, StockThumbnails },
mixins: [warningsMixin, imageMixin, stockPreviewMixin], mixins: [warningsMixin, imageMixin, stockMixin, stockPreviewMixin],
setup() { setup() {
const store = useStore(); const store = useStore();
@@ -208,6 +188,10 @@ export default defineComponent({
.join(';'); .join(';');
}, },
stockIsEmpty() {
return this.store.stockList.length == 0;
},
chosenStockVehicle() { chosenStockVehicle() {
return this.store.chosenStockListIndex == -1 ? undefined : this.store.stockList[this.store.chosenStockListIndex]; return this.store.chosenStockListIndex == -1 ? undefined : this.store.stockList[this.store.chosenStockListIndex];
}, },
@@ -218,6 +202,10 @@ export default defineComponent({
return this.tooManyLocomotives || this.trainTooHeavy || this.trainTooLong || this.locoNotSuitable; return this.tooManyLocomotives || this.trainTooHeavy || this.trainTooLong || this.locoNotSuitable;
}, },
stockImageError(e: Event, stock: IStock): void {
(e.target as HTMLImageElement).src = `images/${stock.useType}-unknown.png`;
},
copyToClipboard() { copyToClipboard() {
// if (this.stockHasWarnings()) { // if (this.stockHasWarnings()) {
// alert('Jazda tym pociągiem jest niezgodna z regulaminem symulatora! Zmień parametry zestawienia!'); // alert('Jazda tym pociągiem jest niezgodna z regulaminem symulatora! Zmień parametry zestawienia!');
@@ -282,6 +270,8 @@ export default defineComponent({
if (index == -1) return; if (index == -1) return;
this.store.stockList = this.store.stockList.filter((stock, i) => i != index); this.store.stockList = this.store.stockList.filter((stock, i) => i != index);
if (this.store.stockList.length < index + 1) this.store.chosenStockListIndex = -1;
}, },
moveUpStock(index: number) { moveUpStock(index: number) {
@@ -333,9 +323,13 @@ export default defineComponent({
// if (this.stockHasWarnings()) // if (this.stockHasWarnings())
// return alert('Jazda tym pociągiem jest niezgodna z regulaminem symulatora! Zmień parametry zestawienia!'); // return alert('Jazda tym pociągiem jest niezgodna z regulaminem symulatora! Zmień parametry zestawienia!');
const defaultName = `${this.store.chosenRealStockName || this.store.stockList[0].type} ${
this.store.totalMass
}t; ${this.store.totalLength}m; vmax ${this.store.maxStockSpeed}`;
const fileName = prompt( const fileName = prompt(
'Nazwij plik, a następnie pobierz do folderu Presets (Dokumenty/TTSK/TrainDriver2):', 'Nazwij plik, a następnie pobierz do folderu Presets (Dokumenty/TTSK/TrainDriver2):',
`${this.store.chosenRealStockName || this.store.stockList[0].type}` defaultName
); );
if (!fileName) return; if (!fileName) return;
@@ -352,6 +346,29 @@ export default defineComponent({
a.dispatchEvent(e); a.dispatchEvent(e);
}, },
uploadStock() {
const inputEl = this.$refs['conFile'] as HTMLInputElement;
const files = inputEl.files;
if (files?.length != 1) return;
if (!/\.con$/.test(files[0].name)) return;
const reader = new FileReader();
reader.readAsText(files[0]);
reader.onload = (res) => {
const stockString = res.target?.result;
if (!stockString || typeof stockString !== 'string') return;
this.loadStockFromString(stockString);
};
reader.onerror = (err) => console.log(err);
inputEl.value = '';
},
onDragStart(vehicleIndex: number) { onDragStart(vehicleIndex: number) {
this.draggedVehicleID = vehicleIndex; this.draggedVehicleID = vehicleIndex;
}, },
@@ -379,11 +396,7 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../styles/global'; @import '../../styles/global';
.stock_warnings {
margin-top: 1em;
}
.warning { .warning {
padding: 0.25em; padding: 0.25em;
@@ -403,12 +416,13 @@ export default defineComponent({
justify-content: center; justify-content: center;
align-items: center; align-items: center;
gap: 0.5em;
flex-wrap: wrap; flex-wrap: wrap;
margin: 1em 0; padding: 0.5em;
border: 1px solid white; margin-bottom: 1em;
padding: 0 0.3em; background-color: #353a57;
&[data-disabled='true'] { &[data-disabled='true'] {
opacity: 0.8; opacity: 0.8;
@@ -430,28 +444,26 @@ export default defineComponent({
} }
button { button {
margin: 0.25em;
padding: 0.25em;
&:focus-visible {
outline: 1px solid white;
}
img { img {
vertical-align: text-bottom;
margin-right: 0.25em; margin-right: 0.25em;
width: 1.1em;
height: 1.1em;
} }
} }
} }
.stock_clipboard-text { .stock_actions {
font-weight: bold; display: grid;
gap: 0.5em;
margin-bottom: 1em;
& > .btn { grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
margin: 0 0.5em 0.5em 0;
label.file-label {
text-align: center;
cursor: pointer;
input {
display: none;
}
} }
} }
@@ -466,8 +478,7 @@ ul {
overflow: auto; overflow: auto;
height: 70vh; height: 500px;
margin-top: 1em;
} }
ul > li { ul > li {
@@ -487,7 +498,8 @@ ul > li {
&.list-empty { &.list-empty {
background-color: $secondaryColor; background-color: $secondaryColor;
padding: 0.5em; border-radius: 0.5em;
padding: 0.75em;
} }
} }
@@ -509,6 +521,10 @@ li > .stock-info {
} }
} }
.stock_warnings {
margin: 0.5em 0;
}
.stock-info { .stock-info {
&__no, &__no,
&__type { &__type {
+124
View File
@@ -0,0 +1,124 @@
<template>
<div class="stock_thumbnails" ref="thumbnailsRef">
<div
v-for="(stock, stockIndex) in store.stockList"
:data-selected="store.chosenStockListIndex == stockIndex"
draggable="true"
@dragstart="onDragStart(stockIndex)"
@drop="onDrop($event, stockIndex)"
@dragover="allowDrop"
>
<span @click="onListItemClick(stockIndex)" :key="stock.id">
<b>
{{ stock.type }}
</b>
<span>
<img
draggable="false"
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stock.type}.png`"
:alt="stock.type"
:title="stock.type"
@error="stockImageError($event, stock)"
/>
</span>
</span>
</div>
</div>
</template>
<script setup lang="ts">
import { Ref, computed, nextTick, ref, watch } from 'vue';
import { useStore } from '../../store';
import { IStock } from '../../types';
const store = useStore();
const emit = defineEmits(['listItemClick', 'stockImageError']);
const thumbnailsRef = ref() as Ref<HTMLElement>;
const draggedIndex = ref(-1);
const onListItemClick = (index: number) => {
emit('listItemClick', index);
};
const stockImageError = (e: Event, stock: IStock) => {
emit('stockImageError', e, stock);
};
watch(
computed(() => store.chosenStockListIndex),
(index) => {
if (index < 0) return;
nextTick(() => {
(thumbnailsRef.value as HTMLElement)
.querySelector(`div:nth-child(${index + 1})`)
?.scrollIntoView({ block: 'nearest', inline: 'start', behavior: 'smooth' });
});
}
);
// Dragging images
const onDragStart = (vehicleIndex: number) => {
draggedIndex.value = vehicleIndex;
};
const onDrop = (e: DragEvent, vehicleIndex: number) => {
e.preventDefault();
let targetEl = thumbnailsRef.value.querySelector(`div:nth-child(${vehicleIndex + 1})`);
if (!targetEl && draggedIndex.value != -1) return;
const tempVehicle = store.stockList[vehicleIndex];
store.stockList[vehicleIndex] = store.stockList[draggedIndex.value];
store.stockList[draggedIndex.value] = tempVehicle;
store.chosenStockListIndex = vehicleIndex;
};
const allowDrop = (e: DragEvent) => {
e.preventDefault();
};
</script>
<style lang="scss" scoped>
.stock_thumbnails {
display: flex;
margin: 1em 0;
overflow: auto;
background-color: #353a57;
> div {
display: flex;
align-items: flex-end;
cursor: pointer;
min-height: 100px;
&[data-selected='true'] {
background-color: rebeccapurple;
}
> span {
display: flex;
flex-direction: column;
gap: 0.5em;
padding: 0.5em 0;
text-align: center;
font-size: 0.85em;
user-select: none;
-moz-user-select: none;
}
}
img {
max-height: 60px;
}
}
</style>
+39
View File
@@ -0,0 +1,39 @@
{
"regionNumbers": {
"Warszawa": 1,
"Lublin": 2,
"Kraków": 3,
"Sosnowiec": 4,
"Gdańsk": 5,
"Wrocław": 6,
"Poznań": 7,
"Szczecin": 8,
"Rezerwa": 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": [10, 11, 19, 91, 93, 97, 99],
"Lublin": [20, 22, 29],
"Kraków": [30, 33, 39],
"Sosnowiec": [40, 44, 49, 94],
"Gdańsk": [50, 55, 59, 90, 95, 96],
"Wrocław": [66, 60, 69],
"Poznań": [77, 70, 79],
"Szczecin": [88, 80],
"Rezerwa": [89, 92, 98]
},
"categories": {
"ekspres krajowy (EI)": "2:00-99:2",
"międzywojewódzki pośpieszny (MP)": "2:050-169:3",
"wojewódzki pośpieszny (RP)": "2:050-169:3",
"wojewódzki osobowy (RO)": "2:200-999:3",
"próżny \"służbowy\" (PW)": "2:6;3:0-899:3",
"towarowy do przewozów masowych (TM)": "2:4;3:0-899:3",
"towarowy do obsługi stacji (TK)": "2:3;3:0-899:3",
"lokomotywa luzem (LT)": "2:5;3:0-899:3"
}
}
+53
View File
@@ -0,0 +1,53 @@
{
"EU07": {
"passenger": {
"650": 125
},
"cargo": {
"2000": 70
}
},
"EP07": {
"passenger": {
"650": 125
},
"cargo": null
},
"EP08": {
"passenger": {
"650": 140
},
"cargo": null
},
"ET41": {
"passenger": {
"700": 125
},
"cargo": {
"4000": 70
}
},
"SM42": {
"passenger": {
"95": 90,
"200": 80,
"300": 70,
"450": 60,
"750": 50,
"1130": 40,
"1720": 30,
"2400": 20
},
"cargo": {
"95": 90,
"200": 80,
"300": 70,
"450": 60,
"750": 50,
"1130": 40,
"1720": 30,
"2400": 20
}
}
}
+1 -5
View File
@@ -7,11 +7,7 @@ import App from './App.vue';
const pinia = createPinia(); const pinia = createPinia();
const updateSW = registerSW({ const updateSW = registerSW({
onOfflineReady() {}, immediate: true,
onNeedRefresh() {
console.log('Need refresh!');
},
}); });
createApp(App).use(pinia).mount('#app'); createApp(App).use(pinia).mount('#app');
+55 -13
View File
@@ -15,7 +15,7 @@ export default defineComponent({
return `${Math.random().toString(36).slice(5)}`; return `${Math.random().toString(36).slice(5)}`;
}, },
getStockObject(vehicle: Vehicle, cargo?: ICargo, count = 1): IStock { getStockObject(vehicle: Vehicle, cargo?: ICargo | null, count = 1): IStock {
const isLoco = isLocomotive(vehicle); const isLoco = isLocomotive(vehicle);
return { return {
@@ -33,13 +33,22 @@ export default defineComponent({
}; };
}, },
addVehicle(vehicle: Vehicle | null, cargo?: ICargo | null) {
if (!vehicle) return;
const stock = this.getStockObject(vehicle, cargo);
if (stock.isLoco && !this.store.stockList[0]?.isLoco) this.store.stockList.unshift(stock);
else this.store.stockList.push(stock);
},
addLocomotive(loco: ILocomotive) { addLocomotive(loco: ILocomotive) {
const previousStock = // const previousStock =
this.store.stockList.length > 0 ? this.store.stockList[this.store.stockList.length - 1] : null; // this.store.stockList.length > 0 ? this.store.stockList[this.store.stockList.length - 1] : null;
if (previousStock && previousStock.type == loco.type) { // if (previousStock && previousStock.type == loco.type) {
this.store.stockList[this.store.stockList.length - 1].count++; // this.store.stockList[this.store.stockList.length - 1].count++;
return; // return;
} // }
const stockObj = this.getStockObject(loco); const stockObj = this.getStockObject(loco);
@@ -48,18 +57,51 @@ export default defineComponent({
}, },
addCarWagon(car: ICarWagon, cargo?: ICargo) { addCarWagon(car: ICarWagon, cargo?: ICargo) {
const previousStock = // const previousStock =
this.store.stockList.length > 0 ? this.store.stockList[this.store.stockList.length - 1] : null; // this.store.stockList.length > 0 ? this.store.stockList[this.store.stockList.length - 1] : null;
if (previousStock && previousStock.type == car.type && previousStock.cargo?.id == cargo?.id) { // if (previousStock && previousStock.type == car.type && previousStock.cargo?.id == cargo?.id) {
this.store.stockList[this.store.stockList.length - 1].count++; // this.store.stockList[this.store.stockList.length - 1].count++;
return; // return;
} // }
const stockObj = this.getStockObject(car, cargo); const stockObj = this.getStockObject(car, cargo);
this.store.stockList.push(stockObj); this.store.stockList.push(stockObj);
}, },
loadStockFromString(stockString: string) {
const stockArray = stockString.trim().split(';');
this.store.stockList.length = 0;
this.store.chosenVehicle = null;
this.store.chosenCar = null;
this.store.chosenCargo = null;
this.store.chosenLoco = null;
this.store.chosenStockListIndex = -1;
this.store.swapVehicles = false;
stockArray.forEach((type) => {
let vehicle: Vehicle | null = null;
let vehicleCargo: ICargo | null = null;
if (/^(EU|EP|ET|SM|EN|2EN|SN)/.test(type)) {
const [locoType, coldStart] = type.split(',');
vehicle = this.store.locoDataList.find((loco) => loco.type == locoType) || null;
} else {
const [carType, cargo] = type.split(':');
vehicle = this.store.carDataList.find((car) => car.type == carType) || null;
if (cargo) vehicleCargo = vehicle?.cargoList.find((c) => c.id == cargo) || null;
}
if (!vehicle) console.log('Brak pojazdu:', type);
this.addVehicle(vehicle, vehicleCargo);
});
},
}, },
}); });
+1 -26
View File
@@ -18,32 +18,7 @@ export default defineComponent({
}, },
trainTooHeavy() { trainTooHeavy() {
const totalMass = this.store.totalMass; return this.store.acceptableMass && this.store.totalMass > this.store.acceptableMass;
const isTrainPassenger = this.store.isTrainPassenger;
const stockList = this.store.stockList;
if (stockList.length == 0 || !stockList[0].isLoco) return false;
const activeLocomotiveType = stockList[0].type;
// Spalinowy SM
if (/^SM/.test(activeLocomotiveType) && totalMass > 2400) return true;
// Elektryczne EU07 / EP07 / EP08 / ET41
// Pasażerski elektr.
if (isTrainPassenger) {
if (/^(EU|EP)/.test(activeLocomotiveType) && totalMass > 650) return true;
if (/^ET/.test(activeLocomotiveType) && totalMass > 700) return true;
return false;
}
// Towarowy / inny elektr.
if (/^EU/.test(activeLocomotiveType) && totalMass > 2000) return true;
if (/^ET/.test(activeLocomotiveType) && totalMass > 4000) return true;
return false;
}, },
locoNotSuitable() { locoNotSuitable() {
+2
View File
@@ -1,6 +1,7 @@
import { IStore } from './types'; import { IStore } from './types';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { import {
acceptableMass,
carDataList, carDataList,
chosenRealStock, chosenRealStock,
isTrainPassenger, isTrainPassenger,
@@ -53,6 +54,7 @@ export const useStore = defineStore({
maxStockSpeed: (state) => maxStockSpeed(state), maxStockSpeed: (state) => maxStockSpeed(state),
isTrainPassenger: (state) => isTrainPassenger(state), isTrainPassenger: (state) => isTrainPassenger(state),
chosenRealStock: (state) => chosenRealStock(state), chosenRealStock: (state) => chosenRealStock(state),
acceptableMass: (state) => acceptableMass(state),
}, },
}); });
+14 -14
View File
@@ -3,14 +3,14 @@
$breakpointMd: 960px; $breakpointMd: 960px;
$breakpointSm: 550px; $breakpointSm: 550px;
$bgColor: #2c3149; $bgColor: #2b3552;
$textColor: #fff; $textColor: #fff;
$secondaryColor: #222; $secondaryColor: #222;
$accentColor: #e4c428; $accentColor: #e4c428;
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 0.5rem; width: 7px;
height: 0.5rem; height: 7px;
&-track { &-track {
background: #222; background: #222;
@@ -21,6 +21,10 @@ $accentColor: #e4c428;
border-radius: 1rem; border-radius: 1rem;
background: #777; background: #777;
} }
&-corner {
background: #222;
}
} }
body, body,
@@ -31,7 +35,6 @@ html {
font-family: 'Lato', sans-serif; font-family: 'Lato', sans-serif;
background-color: $bgColor; background-color: $bgColor;
width: 100vw;
overflow-x: hidden; overflow-x: hidden;
} }
@@ -81,14 +84,20 @@ button {
} }
} }
button.btn { .btn {
padding: 0.4em 0.75em; padding: 0.4em 0.75em;
outline: none; outline: none;
background-color: #222; background-color: #222;
border-radius: 8px;
font-weight: bold;
transition: all 250ms; transition: all 250ms;
&:hover {
color: $accentColor;
}
&.btn--outline { &.btn--outline {
background: none; background: none;
font-weight: bold; font-weight: bold;
@@ -120,15 +129,6 @@ button.btn {
outline: 1px solid white; outline: 1px solid white;
} }
} }
&--choice {
padding: 0.25em 0.3em;
background-color: #222;
&:focus-visible {
outline: 1px solid white;
}
}
} }
select, select,
+80
View File
@@ -0,0 +1,80 @@
@import './global.scss';
.tab {
height: 100%;
margin-top: 1px;
&_header {
padding: 0.5em 1em;
background-color: $secondaryColor;
h2 {
margin: 0;
color: white;
font-size: 1.35em;
text-align: center;
}
button {
margin-left: auto;
}
}
&_content {
margin-top: 1em;
height: 100%;
}
&_attributes {
display: flex;
flex-wrap: wrap;
gap: 1em;
label {
display: flex;
flex-direction: column;
}
input {
max-width: 250px;
margin-top: 0.5em;
}
}
&_actions {
display: grid;
gap: 0.5em;
grid-template-columns: repeat(3, 1fr);
button {
padding: 0.5em;
font-weight: bold;
}
&[data-disabled] button {
opacity: 0.75;
}
}
}
hr {
height: 3px;
background-color: white;
outline: none;
margin: 0;
}
@media only screen and (max-width: 470px) {
.tab_attributes {
label {
width: 100%;
}
input {
max-width: 100%;
width: 100%;
}
}
}
+11 -2
View File
@@ -27,7 +27,7 @@ export interface IStore {
isRandomizerCardOpen: boolean; isRandomizerCardOpen: boolean;
isRealStockListCardOpen: boolean; isRealStockListCardOpen: boolean;
stockSectionMode: 'stock-list' | 'stock-generator'; stockSectionMode: 'stock-list' | 'stock-generator' | 'number-generator';
stockData?: IStockData; stockData?: IStockData;
} }
@@ -49,7 +49,12 @@ export interface IStockData {
}; };
info: { info: {
[key in TStockInfoKey]: any[]; 'car-cargo': [string, string, boolean, boolean, string][];
'car-passenger': [string, string, boolean, boolean, string][];
'loco-e': [string, string, string, string, boolean][];
'loco-s': [string, string, string, string, boolean][];
'loco-szt': [string, string, string, string, boolean][];
'loco-ezt': [string, string, string, string, boolean][];
}; };
props: IStockProps[]; props: IStockProps[];
@@ -108,3 +113,7 @@ export interface IReadyStockList {
[key: string]: { stockString: string; type: string; number: string; name: string }; [key: string]: { stockString: string; type: string; number: string; name: string };
} }
+14
View File
@@ -0,0 +1,14 @@
import speedLimitTable from '../constants/speedLimits.json';
export type LocoType = keyof typeof speedLimitTable;
export const calculateSpeedLimit = (locoType: LocoType, stockMass: number, isTrainPassenger: boolean) => {
const speedTable = speedLimitTable[locoType][isTrainPassenger ? 'passenger' : 'cargo'];
if (!speedTable) return undefined;
let speedLimit = 0;
for (let mass in speedTable) if (stockMass > Number(mass)) speedLimit = (speedTable as any)[mass];
return speedLimit;
};
+75 -239
View File
@@ -1,35 +1,6 @@
import { EVehicleUseType } from '../enums/EVehicleUseType'; import { EVehicleUseType } from '../enums/EVehicleUseType';
import { ICarWagon, ILocomotive, IStore, TStockInfoKey } from '../types'; import { ICarWagon, ILocomotive, IStore, TStockInfoKey } from '../types';
import { LocoType, calculateSpeedLimit } from './speedLimitUtils';
// rodzaj: [tMaxPas, vMaxPas, tMaxTow, vMaxTow] | SM42: [tMax, vMax, ...]
const maxAllowedSpeedTable = {
EU07: [
[650, 125],
[2000, 70],
],
EP07: [
[650, 125],
[0, 0],
],
EP08: [
[650, 140],
[0, 0],
],
ET41: [
[700, 125],
[4000, 70],
],
SM42: [
[95, 90],
[200, 80],
[300, 70],
[450, 60],
[750, 50],
[1130, 40],
[1720, 30],
[2400, 20],
],
};
export function isLocomotive(vehicle: ILocomotive | ICarWagon): vehicle is ILocomotive { export function isLocomotive(vehicle: ILocomotive | ICarWagon): vehicle is ILocomotive {
return (vehicle as ILocomotive).power !== undefined; return (vehicle as ILocomotive).power !== undefined;
@@ -40,84 +11,28 @@ export function locoDataList(state: IStore) {
const stockData = state.stockData; const stockData = state.stockData;
return Object.keys(stockData.info).reduce((acc, vehicleTypeKey) => { return Object.keys(stockData.info).reduce((acc, vehiclePower) => {
if (!vehicleTypeKey.startsWith('loco')) return acc; if (!vehiclePower.startsWith('loco')) return acc;
const locoVehiclesData = stockData.info[vehicleTypeKey as TStockInfoKey]; const locoVehiclesData = stockData.info[vehiclePower as 'loco-e' | 'loco-s' | 'loco-ezt' | 'loco-szt'];
locoVehiclesData.forEach((loco) => { locoVehiclesData.forEach((loco) => {
if (state.showSupporter && !loco[4]) return; if (state.showSupporter && !loco[4]) return;
const locoType = loco[0] as string; const [type, constructionType, cabinType, maxSpeed, supportersOnly] = loco;
const locoProps = stockData.props.find((prop) => constructionType == prop.type);
let length = 0,
mass = 0;
// Elektrowozy
if (vehicleTypeKey.startsWith('loco-e')) {
// 32m dla ET41, reszta 16
length = locoType.startsWith('ET') ? 32 : 16;
// 80t dla wszystkich EU06, EP08
mass = 80;
// 83t dla: EU07 o nr większych niż 300 & dla wszystkich EP07 oprócz nr 135,242,1002,1048
const locoNumber = Number(locoType.split('-')[1]);
if (
(locoType.startsWith('EU') && locoNumber > 300) ||
(locoType.startsWith('EP') && ![242, 135, 1002, 1048].includes(locoNumber))
) {
mass = 83;
}
if (locoType.startsWith('ET')) {
mass = 167;
}
}
// Spalinowozy
if (vehicleTypeKey.startsWith('loco-s')) {
length = 14;
mass = 74;
}
// EZT
if (vehicleTypeKey.startsWith('loco-ezt')) {
// EN57
length = 65;
mass = 126;
// EN71
if (locoType.startsWith('EN71')) {
length = 86;
mass = 182;
}
// 2xEN57
if (locoType.startsWith('2EN57')) {
length = 130;
mass = 253;
}
}
// SZT
if (vehicleTypeKey.startsWith('loco-szt')) {
length = 14;
mass = 23;
}
acc.push({ acc.push({
power: vehicleTypeKey, power: vehiclePower,
type: loco[0] as string, type,
constructionType: loco[1] as string, constructionType,
cabinType: loco[2] as string, cabinType,
maxSpeed: Number(loco[3] as string), maxSpeed: Number(maxSpeed),
supportersOnly: loco[4] as boolean, supportersOnly,
imageSrc: loco[5] as string, imageSrc: '',
length, length: locoProps?.length && type.startsWith('2EN') ? locoProps.length * 2 : locoProps?.length || 0,
mass, mass: locoProps?.mass && type.startsWith('2EN') ? 253 : locoProps?.mass || 0,
}); });
}); });
@@ -130,10 +45,10 @@ export function carDataList(state: IStore) {
const stockData = state.stockData; const stockData = state.stockData;
return Object.keys(stockData.info).reduce((acc, vehicleTypeKey) => { return Object.keys(stockData.info).reduce((acc, vehicleUseType) => {
if (!vehicleTypeKey.startsWith('car')) return acc; if (!vehicleUseType.startsWith('car')) return acc;
const carVehiclesData = stockData.info[vehicleTypeKey as TStockInfoKey]; const carVehiclesData = stockData.info[vehicleUseType as 'car-passenger' | 'car-cargo'];
carVehiclesData.forEach((car) => { carVehiclesData.forEach((car) => {
if (state.showSupporter && !car[3]) return; if (state.showSupporter && !car[3]) return;
@@ -141,19 +56,21 @@ export function carDataList(state: IStore) {
const carPropsData = stockData.props.find((v) => car[0].toString().startsWith(v.type)); const carPropsData = stockData.props.find((v) => car[0].toString().startsWith(v.type));
acc.push({ acc.push({
useType: vehicleTypeKey as 'car-passenger' | 'car-cargo', useType: vehicleUseType as 'car-passenger' | 'car-cargo',
type: car[0] as string, type: car[0],
constructionType: car[1] as string, constructionType: car[1],
loadable: car[2] as boolean, loadable: car[2],
supportersOnly: car[3] as boolean, supportersOnly: car[3],
maxSpeed: Number(car[4] as string), maxSpeed: Number(car[4]),
imageSrc: car[5] as string, imageSrc: '',
cargoList: carPropsData?.cargo.split(';').filter((s) => s.length > 0) cargoList:
? carPropsData.cargo.split(';').map((cargo) => ({ !carPropsData || carPropsData.cargo === null
id: cargo.split(':')[0], ? []
totalMass: Number(cargo.split(':')[1]), : carPropsData.cargo.split(';').map((cargo) => ({
})) id: cargo.split(':')[0],
: [], totalMass: Number(cargo.split(':')[1]),
})),
mass: carPropsData?.mass || 0, mass: carPropsData?.mass || 0,
length: carPropsData?.length || 0, length: carPropsData?.length || 0,
}); });
@@ -164,7 +81,7 @@ export function carDataList(state: IStore) {
} }
export function totalMass(state: IStore) { export function totalMass(state: IStore) {
return state.stockList.reduce( return ~~state.stockList.reduce(
(acc, stock) => acc + (stock.cargo ? stock.cargo.totalMass : stock.mass) * stock.count, (acc, stock) => acc + (stock.cargo ? stock.cargo.totalMass : stock.mass) * stock.count,
0 0
); );
@@ -175,7 +92,47 @@ export function totalLength(state: IStore) {
} }
export function maxStockSpeed(state: IStore) { export function maxStockSpeed(state: IStore) {
return state.stockList.reduce((acc, stock) => (stock.maxSpeed < acc || acc == 0 ? stock.maxSpeed : acc), 0); const stockSpeedLimit = state.stockList.reduce(
(acc, stock) => (stock.maxSpeed < acc || acc == 0 ? stock.maxSpeed : acc),
0
);
const headingLoco = state.stockList[0]?.isLoco ? state.stockList[0] : undefined;
if (!headingLoco) return stockSpeedLimit;
const locoType = headingLoco.type.split('-')[0];
if (/^(EN|2EN|SN)/.test(locoType)) return stockSpeedLimit;
const stockMass = totalMass(state);
const speedLimitByMass = calculateSpeedLimit(locoType as LocoType, stockMass, isTrainPassenger(state));
return speedLimitByMass ? Math.min(stockSpeedLimit, speedLimitByMass) : stockSpeedLimit;
}
export function acceptableMass(state: IStore) {
if (state.stockList.length == 0 || !state.stockList[0].isLoco) return 0;
const activeLocomotiveType = state.stockList[0].type;
if (/^SM/.test(activeLocomotiveType)) return 2400;
// Elektryczne EU07 / EP07 / EP08 / ET41
// Pasażerski elektr.
if (isTrainPassenger(state)) {
if (/^(EU|EP)/.test(activeLocomotiveType)) return 650;
if (/^ET/.test(activeLocomotiveType)) return 700;
return 0;
}
// Towarowy / inny elektr.
if (/^EU/.test(activeLocomotiveType)) return 2000;
if (/^ET/.test(activeLocomotiveType)) return 4000;
if (/^EP/.test(activeLocomotiveType)) return 650;
return 0;
} }
export function isTrainPassenger(state: IStore) { export function isTrainPassenger(state: IStore) {
@@ -206,124 +163,3 @@ export function chosenRealStock(state: IStore) {
return realStockObj; return realStockObj;
} }
// export function maxAllowedSpeed(state: IStore) {
// const headLocoType = state.stockList[0]?.isLoco ? state.stockList[0].type : undefined;
// if (!headLocoType) return 0;
// const isPassenger = isTrainPassenger(state);
// const stockMass = totalMass(state);
// // const maxSpeed = maxAllowedSpeedTable[headLocoType];
// // if()
// }
// export function maxAllowedSpeed(state: IStore) {
// if (state.stockList.length < 1) return -1;
// if (!state.stockList[0].isLoco) return -1;
// const headingLoco = state.stockList[0];
// const isPassenger = isTrainPassenger(state);
// if (headingLoco.type.startsWith('EU07')) {
// if (isPassenger && totalMass.value <= 650) return 125;
// if (!isPassenger && totalMass.value <= 2000) return 70;
// return -1;
// }
// if (headingLoco.type.startsWith('EP07')) {
// if (isPassenger && totalMass.value <= 650) return 125;
// if (!isPassenger) return -1;
// return -1;
// }
// if (headingLoco.type.startsWith('EP08')) {
// if (isPassenger && totalMass.value <= 650) return 140;
// if (!isPassenger) return -1;
// return -1;
// }
// if (headingLoco.type.startsWith('ET41')) {
// if (isPassenger && totalMass.value <= 700) return 125;
// if (!isPassenger && totalMass.value <= 4000) return 70;
// return -1;
// }
// if (headingLoco.type.startsWith('SM42')) {
// if (totalMass.value <= 95) return 90;
// if (totalMass.value <= 200) return 80;
// if (totalMass.value <= 300) return 70;
// if (totalMass.value <= 450) return 60;
// if (totalMass.value <= 750) return 50;
// if (totalMass.value <= 1130) return 40;
// if (totalMass.value <= 1720) return 30;
// if (totalMass.value <= 2400) return 20;
// return -1;
// }
// return Store.stockList.reduce((acc, stock) => (stock.maxSpeed < acc || acc == 0 ? stock.maxSpeed : acc), 0);
// });
// export const warnings = {
// trainTooLong: computed(() => {
// if (isTrainPassenger.value && totalLength.value > 350) return true;
// if (!isTrainPassenger.value && totalLength.value > 650) return true;
// return false;
// }),
// locoNotSuitable: computed(() => {
// if (
// !isTrainPassenger.value &&
// Store.stockList.length > 1 &&
// !Store.stockList.every((stock) => stock.isLoco) &&
// Store.stockList.find((stock) => stock.isLoco && stock.type.startsWith('EP'))
// )
// return true;
// return false;
// }),
// trainTooHeavy: computed(() => {
// if (Store.stockList.length == 0 || !Store.stockList[0].isLoco) return false;
// const headingLoco = Store.stockList[0];
// if (
// isTrainPassenger.value &&
// (headingLoco.type.startsWith('EU') || headingLoco.type.startsWith('EP')) &&
// totalMass.value > 650
// )
// return true;
// if (isTrainPassenger.value && headingLoco.type.startsWith('ET') && totalMass.value > 700) return true;
// if (!isTrainPassenger.value && headingLoco.type.startsWith('EU') && totalMass.value > 2000) return true;
// if (!isTrainPassenger.value && headingLoco.type.startsWith('ET') && totalMass.value > 4000) return true;
// if (headingLoco.type.startsWith('SM') && totalMass.value > 2400) return true;
// return false;
// }),
// tooManyLocos: computed(() => {
// if (
// Store.stockList.reduce((acc, stock) => {
// if (!stock.isLoco) return acc;
// acc += stock.count;
// return acc;
// }, 0) > 2
// )
// return true;
// return false;
// }),
// };