mirror of
https://github.com/Spythere/pojazdownik.git
synced 2026-05-04 20:18:12 +00:00
format; linting; aktualizacja do 2023.2.1
This commit is contained in:
@@ -1,34 +1,55 @@
|
||||
<template>
|
||||
<div class="number-generator tab">
|
||||
<div class="tab_header">
|
||||
<h2>{{ $t('numgen.title') }}</h2>
|
||||
<h2>{{ $t("numgen.title") }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="tab_content">
|
||||
<div class="options">
|
||||
<select v-model="chosenCategory" @change="randomizeTrainNumber()">
|
||||
<option :value="null" disabled>{{ $t('numgen.train-category') }}</option>
|
||||
<option v-for="(_, category) in genData.categories" :value="category">
|
||||
<option :value="null" disabled>
|
||||
{{ $t("numgen.train-category") }}
|
||||
</option>
|
||||
<option
|
||||
v-for="(_, category) in genData.categories"
|
||||
:key="category"
|
||||
:value="category"
|
||||
>
|
||||
{{ $t(`numgen.categories.${category}`) }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<select v-model="beginRegionName" @change="randomizeTrainNumber()">
|
||||
<option :value="null" disabled>{{ $t('numgen.start-region') }}</option>
|
||||
<option v-for="(_, name) in genData.regionNumbers" :value="name">{{ name }}</option>
|
||||
<option :value="null" disabled>
|
||||
{{ $t("numgen.start-region") }}
|
||||
</option>
|
||||
<option
|
||||
v-for="(_, name) in genData.regionNumbers"
|
||||
:key="name"
|
||||
:value="name"
|
||||
>
|
||||
{{ name }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<select v-model="endRegionName" @change="randomizeTrainNumber()">
|
||||
<option :value="null" disabled>{{ $t('numgen.end-region') }}</option>
|
||||
<option v-for="(_, name) in genData.regionNumbers" :value="name">{{ name }}</option>
|
||||
<option :value="null" disabled>{{ $t("numgen.end-region") }}</option>
|
||||
<option
|
||||
v-for="(_, name) in genData.regionNumbers"
|
||||
:key="name"
|
||||
:value="name"
|
||||
>
|
||||
{{ name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="generated-number" @click="copyNumber">
|
||||
<span v-if="trainNumber">
|
||||
{{ $t('numgen.number-info') }} <b class="text--accent">{{ trainNumber }}</b>
|
||||
{{ $t("numgen.number-info") }}
|
||||
<b class="text--accent">{{ trainNumber }}</b>
|
||||
</span>
|
||||
<span v-else>{{ $t('numgen.warning') }}</span>
|
||||
<span v-else>{{ $t("numgen.warning") }}</span>
|
||||
</div>
|
||||
|
||||
<!-- <div v-if="chosenCategory">
|
||||
@@ -50,25 +71,29 @@
|
||||
|
||||
<div class="tab_links">
|
||||
<a :href="$t('numgen.td2-wiki-link')" target="_blank">
|
||||
{{ $t('numgen.td2-wiki') }}
|
||||
{{ $t("numgen.td2-wiki") }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="tab_actions">
|
||||
<button class="btn" @click="randomizeTrainNumber(true)">{{ $t('numgen.action-random-region') }}</button>
|
||||
<button class="btn" @click="randomizeTrainNumber(false)">{{ $t('numgen.action-random-number') }}</button>
|
||||
<button class="btn" @click="randomizeTrainNumber(true)">
|
||||
{{ $t("numgen.action-random-region") }}
|
||||
</button>
|
||||
<button class="btn" @click="randomizeTrainNumber(false)">
|
||||
{{ $t("numgen.action-random-number") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Ref, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { Ref, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
import genData from '../../constants/numberGeneratorData.json';
|
||||
import genData from "../../constants/numberGeneratorData.json";
|
||||
|
||||
const i18n = useI18n();
|
||||
type RegionName = keyof typeof genData.regionNumbers;
|
||||
@@ -83,7 +108,7 @@ const trainNumber = ref(null) as Ref<string | null>;
|
||||
const copyNumber = () => {
|
||||
if (trainNumber.value) {
|
||||
navigator.clipboard.writeText(trainNumber.value);
|
||||
alert(i18n.t('numgen.alert'));
|
||||
alert(i18n.t("numgen.alert"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -93,16 +118,21 @@ const randomizeTrainNumber = (randomizeRegions = false) => {
|
||||
const regionKeys = Object.keys(genData.regionNumbers);
|
||||
|
||||
if (beginRegionName.value == null || randomizeRegions)
|
||||
beginRegionName.value = regionKeys[(regionKeys.length * Math.random()) << 0] as RegionName;
|
||||
beginRegionName.value = regionKeys[
|
||||
(regionKeys.length * Math.random()) << 0
|
||||
] as RegionName;
|
||||
|
||||
if (endRegionName.value == null || randomizeRegions)
|
||||
endRegionName.value = regionKeys[(regionKeys.length * Math.random()) << 0] as RegionName;
|
||||
endRegionName.value = regionKeys[
|
||||
(regionKeys.length * Math.random()) << 0
|
||||
] as RegionName;
|
||||
|
||||
let number = '';
|
||||
let number = "";
|
||||
|
||||
if (beginRegionName.value == endRegionName.value) {
|
||||
const sameRegionsNumbers = genData.sameRegions[beginRegionName.value!];
|
||||
const randRegionNumber = sameRegionsNumbers[Math.floor(Math.random() * sameRegionsNumbers.length)];
|
||||
const randRegionNumber =
|
||||
sameRegionsNumbers[Math.floor(Math.random() * sameRegionsNumbers.length)];
|
||||
number += randRegionNumber.toString();
|
||||
} else {
|
||||
const beginRegionNumber = genData.regionNumbers[beginRegionName.value!];
|
||||
@@ -117,23 +147,30 @@ const randomizeTrainNumber = (randomizeRegions = false) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (chosenCategory.value == null) chosenCategory.value = 'EI';
|
||||
if (chosenCategory.value == null) chosenCategory.value = "EI";
|
||||
|
||||
const rulesArray = genData.categories[chosenCategory.value].split(';').map((r) => ({
|
||||
index: r.split(':')[0],
|
||||
rule: r.split(':')[1],
|
||||
nums: Number(r.split(':')[2] || '1'),
|
||||
}));
|
||||
const rulesArray = genData.categories[chosenCategory.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('-');
|
||||
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();
|
||||
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;
|
||||
number +=
|
||||
new Array(Math.abs(randRange.length - r.nums)).fill("0").join("") +
|
||||
randRange;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -142,8 +179,8 @@ const randomizeTrainNumber = (randomizeRegions = false) => {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/tab.scss';
|
||||
@import '../../styles/global.scss';
|
||||
@import "../../styles/tab.scss";
|
||||
@import "../../styles/global.scss";
|
||||
|
||||
.options {
|
||||
display: grid;
|
||||
@@ -189,4 +226,3 @@ const randomizeTrainNumber = (randomizeRegions = false) => {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,45 +1,64 @@
|
||||
<template>
|
||||
<div class="stock-generator tab">
|
||||
<div class="tab_header">
|
||||
<h2>{{ $t('stockgen.title') }}</h2>
|
||||
<h2>{{ $t("stockgen.title") }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="tab_content">
|
||||
<div>
|
||||
<h2>{{ $t('stockgen.properties-title') }}</h2>
|
||||
<h2>{{ $t("stockgen.properties-title") }}</h2>
|
||||
|
||||
<b class="text--accent">
|
||||
{{ $t('stockgen.properties-desc') }}
|
||||
{{ $t("stockgen.properties-desc") }}
|
||||
</b>
|
||||
|
||||
<div class="tab_attributes">
|
||||
<label>
|
||||
{{ $t('stockgen.input-mass') }}
|
||||
<input type="number" v-model="maxMass" step="100" max="4000" min="0" />
|
||||
{{ $t("stockgen.input-mass") }}
|
||||
<input
|
||||
type="number"
|
||||
v-model="maxMass"
|
||||
step="100"
|
||||
max="4000"
|
||||
min="0"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
{{ $t('stockgen.input-length') }}
|
||||
<input type="number" v-model="maxLength" step="25" max="650" min="0" />
|
||||
{{ $t("stockgen.input-length") }}
|
||||
<input
|
||||
type="number"
|
||||
v-model="maxLength"
|
||||
step="25"
|
||||
max="650"
|
||||
min="0"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
{{ $t('stockgen.input-carcount') }}
|
||||
<input type="number" v-model="maxCarCount" step="1" max="60" min="1" />
|
||||
{{ $t("stockgen.input-carcount") }}
|
||||
<input
|
||||
type="number"
|
||||
v-model="maxCarCount"
|
||||
step="1"
|
||||
max="60"
|
||||
min="1"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>{{ $t('stockgen.cargo-title') }}</h2>
|
||||
<b>{{ $t('stockgen.cargo-desc') }}</b>
|
||||
<h2>{{ $t("stockgen.cargo-title") }}</h2>
|
||||
<b>{{ $t("stockgen.cargo-desc") }}</b>
|
||||
</div>
|
||||
|
||||
<div class="generator_cargo">
|
||||
<button
|
||||
v-for="(cargoArray, cargoName) in store.stockData?.generator.cargo"
|
||||
:key="cargoName"
|
||||
class="btn"
|
||||
:data-chosen="chosenCargoTypes.includes(cargoName.toString())"
|
||||
v-for="(cargoArray, cargoName) in store.stockData?.generator.cargo"
|
||||
@click="toggleCargoChosen(cargoName.toString(), cargoArray)"
|
||||
>
|
||||
{{ $t(`cargo.${cargoName}`) }}
|
||||
@@ -47,15 +66,15 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>{{ $t('stockgen.chosen-title') }}</h2>
|
||||
<h2>{{ $t("stockgen.chosen-title") }}</h2>
|
||||
|
||||
<div class="generator_warning">
|
||||
<span v-if="computedChosenCarTypes.size == 0">
|
||||
{{ $t('stockgen.chosen-empty-warning') }}
|
||||
{{ $t("stockgen.chosen-empty-warning") }}
|
||||
</span>
|
||||
|
||||
<span v-else>
|
||||
{{ $t('stockgen.chosen-warning') }}
|
||||
{{ $t("stockgen.chosen-warning") }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -78,16 +97,28 @@
|
||||
<hr />
|
||||
|
||||
<div class="tab_actions">
|
||||
<button class="btn" :data-disabled="computedChosenCarTypes.size == 0" @click="generateStock()">
|
||||
{{ $t('stockgen.action-generate') }}
|
||||
<button
|
||||
class="btn"
|
||||
:data-disabled="computedChosenCarTypes.size == 0"
|
||||
@click="generateStock()"
|
||||
>
|
||||
{{ $t("stockgen.action-generate") }}
|
||||
</button>
|
||||
|
||||
<button class="btn" :data-disabled="computedChosenCarTypes.size == 0" @click="generateStock(true)">
|
||||
{{ $t('stockgen.action-generate-empty') }}
|
||||
<button
|
||||
class="btn"
|
||||
:data-disabled="computedChosenCarTypes.size == 0"
|
||||
@click="generateStock(true)"
|
||||
>
|
||||
{{ $t("stockgen.action-generate-empty") }}
|
||||
</button>
|
||||
|
||||
<button class="btn" :data-disabled="computedChosenCarTypes.size == 0" @click="resetChosenCargo">
|
||||
{{ $t('stockgen.action-reset') }}
|
||||
<button
|
||||
class="btn"
|
||||
:data-disabled="computedChosenCarTypes.size == 0"
|
||||
@click="resetChosenCargo"
|
||||
>
|
||||
{{ $t("stockgen.action-reset") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -95,15 +126,15 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useStore } from '../../store';
|
||||
import { defineComponent } from "vue";
|
||||
import { useStore } from "../../store";
|
||||
|
||||
import stockMixin from '../../mixins/stockMixin';
|
||||
import { ICargo, ICarWagon, IStock } from '../../types';
|
||||
import warningsMixin from '../../mixins/warningsMixin';
|
||||
import stockMixin from "../../mixins/stockMixin";
|
||||
import { ICargo, ICarWagon, IStock } from "../../types";
|
||||
import warningsMixin from "../../mixins/warningsMixin";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'stock-generator',
|
||||
name: "stock-generator",
|
||||
|
||||
mixins: [stockMixin, warningsMixin],
|
||||
|
||||
@@ -126,7 +157,9 @@ export default defineComponent({
|
||||
|
||||
computed: {
|
||||
computedChosenCarTypes() {
|
||||
return new Set<string>(this.chosenCarTypes.sort((c1, c2) => (c1 > c2 ? 1 : -1)));
|
||||
return new Set<string>(
|
||||
this.chosenCarTypes.slice().sort((c1, c2) => (c1 > c2 ? 1 : -1)),
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -150,53 +183,84 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
generateStock(empty = false) {
|
||||
const generatedChosenStockList = this.chosenCargoTypes.reduce((acc, type) => {
|
||||
this.store.stockData?.generator.cargo[type]
|
||||
.filter((c) => !this.excludedCarTypes.includes(c.split(':')[0]))
|
||||
.forEach((c) => {
|
||||
const [type, cargoType] = c.split(':');
|
||||
const generatedChosenStockList = this.chosenCargoTypes.reduce(
|
||||
(acc, type) => {
|
||||
this.store.stockData?.generator.cargo[type]
|
||||
.filter((c) => !this.excludedCarTypes.includes(c.split(":")[0]))
|
||||
.forEach((c) => {
|
||||
const [type, cargoType] = c.split(":");
|
||||
|
||||
const carWagonObjs = this.store.carDataList.filter((cw) => cw.type.startsWith(type));
|
||||
const cargoObjs = [] as (ICargo | undefined)[];
|
||||
const carWagonObjs = this.store.carDataList.filter((cw) =>
|
||||
cw.type.startsWith(type),
|
||||
);
|
||||
const cargoObjs = [] as (ICargo | undefined)[];
|
||||
|
||||
if (!cargoType || empty) cargoObjs.push(undefined);
|
||||
else if (cargoType == 'all') cargoObjs.push(...carWagonObjs[0]?.cargoList);
|
||||
else cargoObjs.push(carWagonObjs[0]?.cargoList.find((cargo) => cargo.id == cargoType));
|
||||
if (!cargoType || empty) cargoObjs.push(undefined);
|
||||
else if (cargoType == "all")
|
||||
cargoObjs.push(...carWagonObjs[0]!.cargoList);
|
||||
else
|
||||
cargoObjs.push(
|
||||
carWagonObjs[0]?.cargoList.find(
|
||||
(cargo) => cargo.id == cargoType,
|
||||
),
|
||||
);
|
||||
|
||||
carWagonObjs.forEach((cw) => {
|
||||
cargoObjs.forEach((cargoObj) => {
|
||||
const chosenStock = acc.find((a) => a.constructionType.includes(cw.constructionType));
|
||||
carWagonObjs.forEach((cw) => {
|
||||
cargoObjs.forEach((cargoObj) => {
|
||||
const chosenStock = acc.find((a) =>
|
||||
a.constructionType.includes(cw.constructionType),
|
||||
);
|
||||
|
||||
if (!chosenStock)
|
||||
acc.push({
|
||||
constructionType: cw.constructionType,
|
||||
carPool: [{ carWagon: cw, cargo: cargoObj }],
|
||||
});
|
||||
else chosenStock.carPool.push({ carWagon: cw, cargo: cargoObj });
|
||||
if (!chosenStock)
|
||||
acc.push({
|
||||
constructionType: cw.constructionType,
|
||||
carPool: [{ carWagon: cw, cargo: cargoObj }],
|
||||
});
|
||||
else
|
||||
chosenStock.carPool.push({ carWagon: cw, cargo: cargoObj });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, [] as { constructionType: string; carPool: { carWagon: ICarWagon; cargo?: ICargo }[] }[]);
|
||||
return acc;
|
||||
},
|
||||
[] as {
|
||||
constructionType: string;
|
||||
carPool: { carWagon: ICarWagon; cargo?: ICargo }[];
|
||||
}[],
|
||||
);
|
||||
|
||||
let bestGeneration: { stockList: IStock[]; value: number } = { stockList: [], value: 0 };
|
||||
let bestGeneration: { stockList: IStock[]; value: number } = {
|
||||
stockList: [],
|
||||
value: 0,
|
||||
};
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const headingLoco = this.store.stockList[0]?.isLoco ? this.store.stockList[0] : undefined;
|
||||
const headingLoco = this.store.stockList[0]?.isLoco
|
||||
? this.store.stockList[0]
|
||||
: undefined;
|
||||
this.store.stockList.length = headingLoco ? 1 : 0;
|
||||
|
||||
const maxMass =
|
||||
this.store.acceptableMass > 0 ? Math.min(this.store.acceptableMass, this.maxMass) : this.maxMass;
|
||||
this.store.acceptableMass > 0
|
||||
? Math.min(this.store.acceptableMass, this.maxMass)
|
||||
: this.maxMass;
|
||||
|
||||
let exceeded = false;
|
||||
|
||||
while (!exceeded) {
|
||||
const randomStockType = generatedChosenStockList[~~(Math.random() * generatedChosenStockList.length)];
|
||||
const { carWagon, cargo } = randomStockType.carPool[~~(Math.random() * randomStockType.carPool.length)];
|
||||
const randomStockType =
|
||||
generatedChosenStockList[
|
||||
~~(Math.random() * generatedChosenStockList.length)
|
||||
];
|
||||
const { carWagon, cargo } =
|
||||
randomStockType.carPool[
|
||||
~~(Math.random() * randomStockType.carPool.length)
|
||||
];
|
||||
|
||||
if (
|
||||
this.store.totalMass + (cargo?.totalMass || carWagon.mass) > maxMass ||
|
||||
this.store.totalMass + (cargo?.totalMass || carWagon.mass) >
|
||||
maxMass ||
|
||||
this.store.totalLength + carWagon.length > this.maxLength ||
|
||||
this.store.stockList.length > this.maxCarCount
|
||||
) {
|
||||
@@ -207,7 +271,10 @@ export default defineComponent({
|
||||
this.addCarWagon(carWagon, cargo);
|
||||
}
|
||||
|
||||
const currentGenerationValue = this.store.totalLength + this.store.totalMass + this.store.stockList.length;
|
||||
const currentGenerationValue =
|
||||
this.store.totalLength +
|
||||
this.store.totalMass +
|
||||
this.store.stockList.length;
|
||||
|
||||
if (bestGeneration.value < currentGenerationValue) {
|
||||
bestGeneration.stockList = this.store.stockList;
|
||||
@@ -216,11 +283,12 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
this.store.stockList = bestGeneration.stockList;
|
||||
this.store.stockSectionMode = 'stock-list';
|
||||
this.store.stockSectionMode = "stock-list";
|
||||
},
|
||||
|
||||
previewCar(type: string) {
|
||||
const c = this.store.carDataList.find((c) => c.type.startsWith(type)) || null;
|
||||
const c =
|
||||
this.store.carDataList.find((c) => c.type.startsWith(type)) || null;
|
||||
|
||||
this.store.chosenVehicle = c;
|
||||
this.store.chosenCar = c;
|
||||
@@ -233,33 +301,38 @@ export default defineComponent({
|
||||
toggleCargoChosen(cargoType: string, vehicles: string[]) {
|
||||
if (this.chosenCargoTypes.includes(cargoType)) {
|
||||
vehicles.forEach((v) => {
|
||||
const [type] = v.split(':');
|
||||
const [type] = v.split(":");
|
||||
this.chosenCarTypes.splice(this.chosenCarTypes.indexOf(type), 1);
|
||||
});
|
||||
|
||||
this.chosenCargoTypes.splice(this.chosenCargoTypes.indexOf(cargoType), 1);
|
||||
this.chosenCargoTypes.splice(
|
||||
this.chosenCargoTypes.indexOf(cargoType),
|
||||
1,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.chosenCargoTypes.push(cargoType);
|
||||
|
||||
vehicles.forEach((v) => {
|
||||
const [type] = v.split(':');
|
||||
const [type] = v.split(":");
|
||||
this.chosenCarTypes.push(type);
|
||||
});
|
||||
},
|
||||
|
||||
toggleCarExclusion(type: string) {
|
||||
if (!this.excludedCarTypes.includes(type)) this.excludedCarTypes.push(type);
|
||||
else this.excludedCarTypes = this.excludedCarTypes.filter((c) => c != type);
|
||||
if (!this.excludedCarTypes.includes(type))
|
||||
this.excludedCarTypes.push(type);
|
||||
else
|
||||
this.excludedCarTypes = this.excludedCarTypes.filter((c) => c != type);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/global.scss';
|
||||
@import '../../styles/tab.scss';
|
||||
@import "../../styles/global.scss";
|
||||
@import "../../styles/tab.scss";
|
||||
|
||||
.generator_cargo,
|
||||
.generator_vehicles {
|
||||
@@ -277,14 +350,14 @@ export default defineComponent({
|
||||
|
||||
background-color: $secondaryColor;
|
||||
|
||||
&[data-chosen='true'] {
|
||||
&[data-chosen="true"] {
|
||||
background-color: $accentColor;
|
||||
color: black;
|
||||
|
||||
box-shadow: 0 0 5px 1px $accentColor;
|
||||
}
|
||||
|
||||
&[data-excluded='true'] {
|
||||
&[data-excluded="true"] {
|
||||
background-color: gray;
|
||||
box-shadow: none;
|
||||
}
|
||||
@@ -316,4 +389,3 @@ export default defineComponent({
|
||||
color: black;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,47 +1,77 @@
|
||||
<template>
|
||||
<section class="stock-list-tab">
|
||||
<div class="tab_header">
|
||||
<h2>{{ $t('stocklist.title') }}</h2>
|
||||
<h2>{{ $t("stocklist.title") }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="stock_actions">
|
||||
<label class="file-label">
|
||||
<div class="btn btn--image">
|
||||
<img src="/images/icon-upload.svg" alt="" />
|
||||
{{ $t('stocklist.action-upload') }}
|
||||
{{ $t("stocklist.action-upload") }}
|
||||
</div>
|
||||
|
||||
<input type="file" @change="uploadStock" ref="conFile" accept=".con,.txt" />
|
||||
<input
|
||||
type="file"
|
||||
@change="uploadStock"
|
||||
ref="conFile"
|
||||
accept=".con,.txt"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<button class="btn btn--image" :data-disabled="stockIsEmpty" :disabled="stockIsEmpty" @click="downloadStock">
|
||||
<button
|
||||
class="btn btn--image"
|
||||
:data-disabled="stockIsEmpty"
|
||||
:disabled="stockIsEmpty"
|
||||
@click="downloadStock"
|
||||
>
|
||||
<img src="/images/icon-download.svg" alt="download icon" />
|
||||
{{ $t('stocklist.action-download') }}
|
||||
{{ $t("stocklist.action-download") }}
|
||||
</button>
|
||||
|
||||
<button class="btn btn--image" :data-disabled="stockIsEmpty" :disabled="stockIsEmpty" @click="copyToClipboard">
|
||||
<button
|
||||
class="btn btn--image"
|
||||
:data-disabled="stockIsEmpty"
|
||||
:disabled="stockIsEmpty"
|
||||
@click="copyToClipboard"
|
||||
>
|
||||
<img src="/images/icon-copy.svg" alt="copy icon" />
|
||||
{{ $t('stocklist.action-copy') }}
|
||||
{{ $t("stocklist.action-copy") }}
|
||||
</button>
|
||||
|
||||
<button class="btn btn--image" :data-disabled="stockIsEmpty" :disabled="stockIsEmpty" @click="resetStock">
|
||||
<button
|
||||
class="btn btn--image"
|
||||
:data-disabled="stockIsEmpty"
|
||||
:disabled="stockIsEmpty"
|
||||
@click="resetStock"
|
||||
>
|
||||
<img src="/images/icon-reset.svg" alt="reset icon" />
|
||||
{{ $t('stocklist.action-reset') }}
|
||||
{{ $t("stocklist.action-reset") }}
|
||||
</button>
|
||||
|
||||
<button class="btn btn--image" :data-disabled="stockIsEmpty" :disabled="stockIsEmpty" @click="shuffleCars">
|
||||
<button
|
||||
class="btn btn--image"
|
||||
:data-disabled="stockIsEmpty"
|
||||
:disabled="stockIsEmpty"
|
||||
@click="shuffleCars"
|
||||
>
|
||||
<img src="/images/icon-shuffle.svg" alt="shuffle icon" />
|
||||
{{ $t('stocklist.action-shuffle') }}
|
||||
{{ $t("stocklist.action-shuffle") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="stock_controls" :data-disabled="store.chosenStockListIndex == -1">
|
||||
<div
|
||||
class="stock_controls"
|
||||
:data-disabled="store.chosenStockListIndex == -1"
|
||||
>
|
||||
<b v-if="store.chosenStockListIndex >= 0">
|
||||
{{ $t('stocklist.vehicle-no') }} <span class="text--accent">{{ store.chosenStockListIndex + 1 }}</span>
|
||||
{{ $t("stocklist.vehicle-no") }}
|
||||
<span class="text--accent">{{ store.chosenStockListIndex + 1 }}</span>
|
||||
|
||||
</b>
|
||||
|
||||
<b v-else>
|
||||
{{ $t('stocklist.no-vehicle-chosen') }}
|
||||
{{ $t("stocklist.no-vehicle-chosen") }}
|
||||
</b>
|
||||
|
||||
<button
|
||||
@@ -50,7 +80,7 @@
|
||||
@click="moveUpStock(store.chosenStockListIndex)"
|
||||
>
|
||||
<img :src="getIconURL('higher')" alt="move up vehicle" />
|
||||
{{ $t('stocklist.action-move-up') }}
|
||||
{{ $t("stocklist.action-move-up") }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
@@ -59,7 +89,7 @@
|
||||
@click="moveDownStock(store.chosenStockListIndex)"
|
||||
>
|
||||
<img :src="getIconURL('lower')" alt="move down vehicle" />
|
||||
{{ $t('stocklist.action-move-down') }}
|
||||
{{ $t("stocklist.action-move-down") }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
@@ -68,26 +98,34 @@
|
||||
@click="removeStock(store.chosenStockListIndex)"
|
||||
>
|
||||
<img :src="getIconURL('remove')" alt="remove vehicle" />
|
||||
{{ $t('stocklist.action-remove') }}
|
||||
{{ $t("stocklist.action-remove") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="stock_specs">
|
||||
<b class="real-stock-info" v-if="store.chosenRealStock">
|
||||
<span class="text--accent">
|
||||
<img :src="getIconURL(store.chosenRealStock.type)" :alt="store.chosenRealStock.type" />
|
||||
<img
|
||||
:src="getIconURL(store.chosenRealStock.type)"
|
||||
:alt="store.chosenRealStock.type"
|
||||
/>
|
||||
{{ store.chosenRealStock.number }} {{ store.chosenRealStock.name }}
|
||||
</span>
|
||||
|
|
||||
</b>
|
||||
|
||||
<span>
|
||||
{{ $t('stocklist.mass') }} <span class="text--accent">{{ store.totalMass }}t</span> ({{
|
||||
$t('stocklist.mass-accepted')
|
||||
}}: <span class="text--accent">{{ store.acceptableMass ? store.acceptableMass + 't' : '-' }}</span
|
||||
>) - {{ $t('stocklist.length') }}:
|
||||
{{ $t("stocklist.mass") }}
|
||||
<span class="text--accent">{{ store.totalMass }}t</span> ({{
|
||||
$t("stocklist.mass-accepted")
|
||||
}}:
|
||||
<span class="text--accent">{{
|
||||
store.acceptableMass ? store.acceptableMass + "t" : "-"
|
||||
}}</span
|
||||
>) - {{ $t("stocklist.length") }}:
|
||||
<span class="text--accent">{{ store.totalLength }}m</span>
|
||||
- {{ $t('stocklist.vmax') }}: <span class="text--accent">{{ store.maxStockSpeed }} km/h</span>
|
||||
- {{ $t("stocklist.vmax") }}:
|
||||
<span class="text--accent">{{ store.maxStockSpeed }} km/h</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -96,21 +134,25 @@
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="store.isColdStart"
|
||||
:disabled="!locoSupportsColdStart(store.stockList[0]?.constructionType || '')"
|
||||
:disabled="
|
||||
!locoSupportsColdStart(store.stockList[0]?.constructionType || '')
|
||||
"
|
||||
/>
|
||||
{{ $t('stocklist.coldstart-info') }}
|
||||
{{ $t("stocklist.coldstart-info") }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="stock_warnings" v-if="stockHasWarnings">
|
||||
<div class="warning" v-if="locoNotSuitable">(!) {{ $t('stocklist.warning-not-suitable') }}</div>
|
||||
<div class="warning" v-if="locoNotSuitable">
|
||||
(!) {{ $t("stocklist.warning-not-suitable") }}
|
||||
</div>
|
||||
|
||||
<div class="warning" v-if="trainTooLong && store.isTrainPassenger">
|
||||
(!) {{ $t('stocklist.warning-passenger-too-long') }}
|
||||
(!) {{ $t("stocklist.warning-passenger-too-long") }}
|
||||
</div>
|
||||
|
||||
<div class="warning" v-if="trainTooLong && !store.isTrainPassenger">
|
||||
(!) {{ $t('stocklist.warning-freight-too-long') }}
|
||||
(!) {{ $t("stocklist.warning-freight-too-long") }}
|
||||
</div>
|
||||
|
||||
<div class="warning" v-if="trainTooHeavy">
|
||||
@@ -121,14 +163,14 @@
|
||||
target="_blank"
|
||||
href="https://docs.google.com/spreadsheets/d/1bFXUsHsAu4youmNz-46Q1HslZaaoklvfoBDS553TnNk/edit"
|
||||
>
|
||||
{{ $t('stocklist.acceptable-mass-docs') }}
|
||||
{{ $t("stocklist.acceptable-mass-docs") }}
|
||||
</a>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<div class="warning" v-if="tooManyLocomotives">
|
||||
{{ $t('stocklist.warning-too-many-locos') }}
|
||||
{{ $t("stocklist.warning-too-many-locos") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -137,7 +179,7 @@
|
||||
<!-- Stock list -->
|
||||
<ul ref="stock_list">
|
||||
<li v-if="stockIsEmpty" class="list-empty">
|
||||
<div class="stock-info">{{ $t('stocklist.list-empty') }}</div>
|
||||
<div class="stock-info">{{ $t("stocklist.list-empty") }}</div>
|
||||
</li>
|
||||
|
||||
<TransitionGroup name="stock-list-anim">
|
||||
@@ -160,18 +202,28 @@
|
||||
@dragover="allowDrop"
|
||||
draggable="true"
|
||||
>
|
||||
<span class="stock-info__no" :data-selected="i == store.chosenStockListIndex">
|
||||
<span
|
||||
class="stock-info__no"
|
||||
:data-selected="i == store.chosenStockListIndex"
|
||||
>
|
||||
<span v-if="i == store.chosenStockListIndex">• </span>
|
||||
{{ i + 1 }}.
|
||||
</span>
|
||||
|
||||
<span class="stock-info__type" :class="{ supporter: stock.supportersOnly }">
|
||||
<span
|
||||
class="stock-info__type"
|
||||
:class="{ supporter: stock.supportersOnly }"
|
||||
>
|
||||
{{ stock.isLoco ? stock.type : getCarSpecFromType(stock.type) }}
|
||||
</span>
|
||||
|
||||
<span class="stock-info__cargo" v-if="stock.cargo"> {{ stock.cargo.id }} </span>
|
||||
<span class="stock-info__cargo" v-if="stock.cargo">
|
||||
{{ stock.cargo.id }}
|
||||
</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>
|
||||
</div>
|
||||
</li>
|
||||
@@ -181,21 +233,20 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import TrainImage from '../sections/TrainImageSection.vue';
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import { useStore } from '../../store';
|
||||
import { useStore } from "../../store";
|
||||
|
||||
import { locoSupportsColdStart } from '../../utils/locoUtils';
|
||||
import warningsMixin from '../../mixins/warningsMixin';
|
||||
import imageMixin from '../../mixins/imageMixin';
|
||||
import stockPreviewMixin from '../../mixins/stockPreviewMixin';
|
||||
import StockThumbnails from '../utils/StockThumbnails.vue';
|
||||
import stockMixin from '../../mixins/stockMixin';
|
||||
import { locoSupportsColdStart } from "../../utils/locoUtils";
|
||||
import warningsMixin from "../../mixins/warningsMixin";
|
||||
import imageMixin from "../../mixins/imageMixin";
|
||||
import stockPreviewMixin from "../../mixins/stockPreviewMixin";
|
||||
import StockThumbnails from "../utils/StockThumbnails.vue";
|
||||
import stockMixin from "../../mixins/stockMixin";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'stock-list',
|
||||
components: { TrainImage, StockThumbnails },
|
||||
name: "stock-list",
|
||||
components: { StockThumbnails },
|
||||
|
||||
mixins: [warningsMixin, imageMixin, stockMixin, stockPreviewMixin],
|
||||
|
||||
@@ -218,13 +269,20 @@ export default defineComponent({
|
||||
stockString() {
|
||||
return this.store.stockList
|
||||
.map((stock, i) => {
|
||||
let stockTypeStr = stock.isLoco || !stock.cargo ? stock.type : `${stock.type}:${stock.cargo.id}`;
|
||||
let stockTypeStr =
|
||||
stock.isLoco || !stock.cargo
|
||||
? stock.type
|
||||
: `${stock.type}:${stock.cargo.id}`;
|
||||
let coldStart =
|
||||
i == 0 && this.store.isColdStart && locoSupportsColdStart(stock.constructionType || '') ? ',c' : '';
|
||||
i == 0 &&
|
||||
this.store.isColdStart &&
|
||||
locoSupportsColdStart(stock.constructionType || "")
|
||||
? ",c"
|
||||
: "";
|
||||
|
||||
return stockTypeStr + coldStart;
|
||||
})
|
||||
.join(';');
|
||||
.join(";");
|
||||
},
|
||||
|
||||
stockIsEmpty() {
|
||||
@@ -232,11 +290,18 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
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];
|
||||
},
|
||||
|
||||
stockHasWarnings() {
|
||||
return this.tooManyLocomotives || this.trainTooHeavy || this.trainTooLong || this.locoNotSuitable;
|
||||
return (
|
||||
this.tooManyLocomotives ||
|
||||
this.trainTooHeavy ||
|
||||
this.trainTooLong ||
|
||||
this.locoNotSuitable
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -247,7 +312,7 @@ export default defineComponent({
|
||||
navigator.clipboard.writeText(this.stockString);
|
||||
|
||||
setTimeout(() => {
|
||||
alert(this.$t('stocklist.alert-copied'));
|
||||
alert(this.$t("stocklist.alert-copied"));
|
||||
}, 20);
|
||||
},
|
||||
|
||||
@@ -255,7 +320,10 @@ export default defineComponent({
|
||||
const stock = this.store.stockList[stockID];
|
||||
|
||||
this.store.chosenStockListIndex =
|
||||
this.store.chosenStockListIndex == stockID && this.store.chosenVehicle?.type == stock.type ? -1 : stockID;
|
||||
this.store.chosenStockListIndex == stockID &&
|
||||
this.store.chosenVehicle?.type == stock.type
|
||||
? -1
|
||||
: stockID;
|
||||
|
||||
if (this.store.chosenStockListIndex == -1) {
|
||||
this.store.chosenVehicle = null;
|
||||
@@ -268,12 +336,13 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
getCarSpecFromType(typeStr: string) {
|
||||
const specArray = typeStr.split('_');
|
||||
const specArray = typeStr.split("_");
|
||||
|
||||
if (specArray.length == 0) return null;
|
||||
|
||||
/* 111a_Grafitti_1 */
|
||||
if (specArray.length == 3) return `${specArray[0]} ${specArray[1]}-${specArray[2]}`;
|
||||
if (specArray.length == 3)
|
||||
return `${specArray[0]} ${specArray[1]}-${specArray[2]}`;
|
||||
|
||||
/* 111a_PKP_Bnouz_01 */
|
||||
return `${specArray[0]} ${specArray[2]}-${specArray[3]} (${specArray[1]})`;
|
||||
@@ -301,9 +370,12 @@ export default defineComponent({
|
||||
removeStock(index: number) {
|
||||
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;
|
||||
if (this.store.stockList.length < index + 1)
|
||||
this.store.chosenStockListIndex = -1;
|
||||
},
|
||||
|
||||
moveUpStock(index: number) {
|
||||
@@ -341,7 +413,8 @@ export default defineComponent({
|
||||
|
||||
availableIndexes.splice(i, -1);
|
||||
|
||||
const randAvailableIndex = availableIndexes[Math.floor(Math.random() * availableIndexes.length)];
|
||||
const randAvailableIndex =
|
||||
availableIndexes[Math.floor(Math.random() * availableIndexes.length)];
|
||||
const tempSwap = this.store.stockList[randAvailableIndex];
|
||||
|
||||
this.store.stockList[randAvailableIndex] = this.store.stockList[i];
|
||||
@@ -350,30 +423,33 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
downloadStock() {
|
||||
if (this.store.stockList.length == 0) return alert(this.$t('stocklist.alert-empty'));
|
||||
if (this.store.stockList.length == 0)
|
||||
return alert(this.$t("stocklist.alert-empty"));
|
||||
|
||||
const defaultName = `${this.store.chosenRealStockName || this.store.stockList[0].type} ${
|
||||
this.store.totalMass
|
||||
}t; ${this.store.totalLength}m; vmax ${this.store.maxStockSpeed}`;
|
||||
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(this.$t('stocklist.prompt-file'), defaultName);
|
||||
const fileName = prompt(this.$t("stocklist.prompt-file"), defaultName);
|
||||
|
||||
if (!fileName) return;
|
||||
|
||||
const blob = new Blob([this.stockString]);
|
||||
const file = fileName + '.con';
|
||||
const file = fileName + ".con";
|
||||
|
||||
var e = document.createEvent('MouseEvents'),
|
||||
a = document.createElement('a');
|
||||
var e = document.createEvent("MouseEvents"),
|
||||
a = document.createElement("a");
|
||||
a.download = file;
|
||||
a.href = window.URL.createObjectURL(blob);
|
||||
a.dataset.downloadurl = ['', a.download, a.href].join(':');
|
||||
e.initEvent('click', true, false);
|
||||
a.dataset.downloadurl = ["", a.download, a.href].join(":");
|
||||
e.initEvent("click", true, false);
|
||||
a.dispatchEvent(e);
|
||||
},
|
||||
|
||||
uploadStock() {
|
||||
const inputEl = this.$refs['conFile'] as HTMLInputElement;
|
||||
const inputEl = this.$refs["conFile"] as HTMLInputElement;
|
||||
const files = inputEl.files;
|
||||
|
||||
if (files?.length != 1) return;
|
||||
@@ -385,14 +461,14 @@ export default defineComponent({
|
||||
reader.onload = (res) => {
|
||||
const stockString = res.target?.result;
|
||||
|
||||
if (!stockString || typeof stockString !== 'string') return;
|
||||
if (!stockString || typeof stockString !== "string") return;
|
||||
|
||||
this.loadStockFromString(stockString);
|
||||
};
|
||||
|
||||
reader.onerror = (err) => console.log(err);
|
||||
|
||||
inputEl.value = '';
|
||||
inputEl.value = "";
|
||||
},
|
||||
|
||||
onDragStart(vehicleIndex: number) {
|
||||
@@ -402,13 +478,14 @@ export default defineComponent({
|
||||
onDrop(e: DragEvent, vehicleIndex: number) {
|
||||
e.preventDefault();
|
||||
|
||||
let targetEl = (this.$refs['itemRefs'] as Element[])[vehicleIndex];
|
||||
let targetEl = (this.$refs["itemRefs"] as Element[])[vehicleIndex];
|
||||
|
||||
if (!targetEl) return;
|
||||
|
||||
const tempVehicle = this.store.stockList[vehicleIndex];
|
||||
|
||||
this.store.stockList[vehicleIndex] = this.store.stockList[this.draggedVehicleID];
|
||||
this.store.stockList[vehicleIndex] =
|
||||
this.store.stockList[this.draggedVehicleID];
|
||||
this.store.stockList[this.draggedVehicleID] = tempVehicle;
|
||||
|
||||
this.store.chosenStockListIndex = vehicleIndex;
|
||||
@@ -422,8 +499,8 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/global';
|
||||
@import '../../styles/tab.scss';
|
||||
@import "../../styles/global";
|
||||
@import "../../styles/tab.scss";
|
||||
|
||||
.stock-list-tab {
|
||||
display: grid;
|
||||
@@ -455,7 +532,7 @@ export default defineComponent({
|
||||
|
||||
background-color: #353a57;
|
||||
|
||||
&[data-disabled='true'] {
|
||||
&[data-disabled="true"] {
|
||||
opacity: 0.8;
|
||||
|
||||
user-select: none;
|
||||
@@ -567,7 +644,7 @@ li > .stock-info {
|
||||
min-width: 3.5em;
|
||||
text-align: right;
|
||||
|
||||
&[data-selected='true'] {
|
||||
&[data-selected="true"] {
|
||||
color: $accentColor;
|
||||
}
|
||||
}
|
||||
@@ -610,4 +687,3 @@ li > .stock-info {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
+358
-356
@@ -1,356 +1,358 @@
|
||||
<template>
|
||||
<section class="wiki-list tab">
|
||||
<div class="tab_header">
|
||||
<h2>{{ $t('wiki.title') }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="tab_content">
|
||||
<div class="actions-panel">
|
||||
<div class="actions-panel_vehicles">
|
||||
<button class="btn btn--choice" @click="changeWikiMode('locomotives')">
|
||||
{{ $t('wiki.action-vehicles') }}
|
||||
</button>
|
||||
<button class="btn btn--choice" @click="changeWikiMode('carWagons')">
|
||||
{{ $t('wiki.action-carriages') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="actions-panel_search">
|
||||
<input type="text" :placeholder="$t('wiki.search')" v-model="searchedVehicleTypeName" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-wrapper" @scroll="scrollEvent" ref="table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="header in wikiMode == 'locomotives' ? locoHeaders : carHeaders" @click="toggleSorter(header)">
|
||||
{{ $t(`wiki.header.${header.id}`) }}
|
||||
|
||||
<span v-if="currentModeSorter.id == header.id">
|
||||
{{ currentModeSorter.direction == 1 ? `⇑` : `⇓` }}
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody v-if="wikiMode == 'locomotives'">
|
||||
<tr
|
||||
v-for="loco in computedLocoList"
|
||||
@click="previewLocomotive(loco)"
|
||||
@keydown.enter="previewLocomotive(loco)"
|
||||
@dblclick="addLocomotive(loco)"
|
||||
tabindex="0"
|
||||
>
|
||||
<td>
|
||||
<img
|
||||
:src="`https://spythere.github.io/api/td2/images/${loco.type}--300px.jpg`"
|
||||
loading="lazy"
|
||||
:alt="`Lokomotywa ${loco.type}`"
|
||||
/>
|
||||
</td>
|
||||
|
||||
<td>{{ loco.type }}</td>
|
||||
<td>{{ $t(`wiki.${loco.power}`) }}</td>
|
||||
<td>{{ loco.constructionType }}</td>
|
||||
<td>{{ locoSupportsColdStart(loco.constructionType) ? `✓` : '✗' }}</td>
|
||||
<td>{{ loco.length }}m</td>
|
||||
<td>{{ loco.mass }}t</td>
|
||||
<td>{{ loco.maxSpeed }}km/h</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tbody v-else>
|
||||
<tr
|
||||
v-for="car in computedCarList"
|
||||
@keydow.enter="previewCarWagon(car)"
|
||||
@click="previewCarWagon(car)"
|
||||
@dblclick="addCarWagon(car)"
|
||||
tabindex="0"
|
||||
>
|
||||
<td>
|
||||
<img
|
||||
:src="`https://spythere.github.io/api/td2/images/${car.type}--300px.jpg`"
|
||||
loading="lazy"
|
||||
:alt="`Lokomotywa ${car.type}`"
|
||||
/>
|
||||
</td>
|
||||
|
||||
<td>{{ car.type }}</td>
|
||||
<td>{{ car.constructionType }}</td>
|
||||
<td>{{ car.length }}m</td>
|
||||
<td>{{ car.mass }}t</td>
|
||||
<td>{{ car.maxSpeed }}km/h</td>
|
||||
<td>{{ car.cargoList.length == 0 ? '-' : car.cargoList.length }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useStore } from '../../store';
|
||||
import stockPreviewMixin from '../../mixins/stockPreviewMixin';
|
||||
import { Vehicle } from '../../types';
|
||||
import { isLocomotive } from '../../utils/vehicleUtils';
|
||||
import stockMixin from '../../mixins/stockMixin';
|
||||
import { locoSupportsColdStart } from '../../utils/locoUtils';
|
||||
|
||||
type WikiMode = 'locomotives' | 'carWagons';
|
||||
type SorterID =
|
||||
| 'type'
|
||||
| 'constructionType'
|
||||
| 'image'
|
||||
| 'length'
|
||||
| 'mass'
|
||||
| 'maxSpeed'
|
||||
| 'cargoCount'
|
||||
| 'power'
|
||||
| 'coldStart';
|
||||
|
||||
interface WikiHeader {
|
||||
id: SorterID;
|
||||
sortable: boolean;
|
||||
}
|
||||
|
||||
const locoHeaders: WikiHeader[] = [
|
||||
{ id: 'image', sortable: false },
|
||||
{ id: 'type', sortable: true },
|
||||
{ id: 'power', sortable: true },
|
||||
{ id: 'constructionType', sortable: true },
|
||||
{ id: 'coldStart', sortable: true },
|
||||
{ id: 'length', sortable: true },
|
||||
{ id: 'mass', sortable: true },
|
||||
{ id: 'maxSpeed', sortable: true },
|
||||
];
|
||||
|
||||
const carHeaders: WikiHeader[] = [
|
||||
{ id: 'image', sortable: false },
|
||||
{ id: 'type', sortable: true },
|
||||
{ id: 'constructionType', sortable: true },
|
||||
{ id: 'length', sortable: true },
|
||||
{ id: 'mass', sortable: true },
|
||||
{ id: 'maxSpeed', sortable: true },
|
||||
{ id: 'cargoCount', sortable: true },
|
||||
];
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [stockPreviewMixin, stockMixin],
|
||||
|
||||
data() {
|
||||
return {
|
||||
store: useStore(),
|
||||
locoHeaders,
|
||||
carHeaders,
|
||||
|
||||
locosScrollTop: 0,
|
||||
carsScrollTop: 0,
|
||||
|
||||
wikiMode: 'locomotives' as WikiMode,
|
||||
searchedVehicleTypeName: '',
|
||||
|
||||
currentLocoSorter: {
|
||||
id: 'type' as SorterID,
|
||||
direction: 1,
|
||||
},
|
||||
|
||||
currentCarSorter: {
|
||||
id: 'type' as SorterID,
|
||||
direction: 1,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
activated() {
|
||||
const tableWrapperRef = this.$refs['table-wrapper'] as HTMLElement;
|
||||
tableWrapperRef.scrollTo({ top: this.wikiMode == 'locomotives' ? this.locosScrollTop : this.carsScrollTop });
|
||||
},
|
||||
|
||||
methods: {
|
||||
locoSupportsColdStart,
|
||||
|
||||
scrollEvent(e: Event) {
|
||||
const tableScrollTop = (e.target as HTMLElement).scrollTop;
|
||||
|
||||
if (this.wikiMode == 'locomotives') this.locosScrollTop = tableScrollTop;
|
||||
else this.carsScrollTop = tableScrollTop;
|
||||
},
|
||||
|
||||
changeWikiMode(wikiMode: WikiMode) {
|
||||
this.searchedVehicleTypeName = '';
|
||||
this.wikiMode = wikiMode;
|
||||
},
|
||||
|
||||
toggleSorter(header: WikiHeader) {
|
||||
if (!header.sortable) return;
|
||||
|
||||
if (header.id == this.currentModeSorter.id) this.currentModeSorter.direction *= -1;
|
||||
this.currentModeSorter.id = header.id;
|
||||
},
|
||||
|
||||
sortVehicles(vA: Vehicle, vB: Vehicle) {
|
||||
const { id, direction } = this.currentModeSorter;
|
||||
const vehiclesAreLocos = isLocomotive(vA) && isLocomotive(vB);
|
||||
const vehiclesAreCars = !isLocomotive(vA) && !isLocomotive(vB);
|
||||
|
||||
switch (id) {
|
||||
case 'type':
|
||||
case 'constructionType':
|
||||
return direction == 1 ? vA[id].localeCompare(vB[id]) : vB[id].localeCompare(vA[id]);
|
||||
|
||||
case 'mass':
|
||||
case 'length':
|
||||
case 'maxSpeed':
|
||||
return Math.sign(vA[id] - vB[id]) * direction;
|
||||
|
||||
case 'cargoCount':
|
||||
if (vehiclesAreCars) return Math.sign((vA.cargoList.length || -1) - (vB.cargoList.length || -1)) * direction;
|
||||
|
||||
case 'coldStart':
|
||||
if (vehiclesAreLocos)
|
||||
return (
|
||||
(locoSupportsColdStart(vA.constructionType) > locoSupportsColdStart(vB.constructionType) ? 1 : -1) *
|
||||
direction
|
||||
);
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return direction == 1 ? vA.type.localeCompare(vB.type) : vB.type.localeCompare(vA.type);
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
currentModeSorter() {
|
||||
return this.wikiMode == 'carWagons' ? this.currentCarSorter : this.currentLocoSorter;
|
||||
},
|
||||
|
||||
computedLocoList() {
|
||||
const trimmedSearchValue = this.searchedVehicleTypeName.trim();
|
||||
|
||||
return this.store.locoDataList
|
||||
.filter((loco) => new RegExp(`${trimmedSearchValue}`, 'i').test(loco.type))
|
||||
.sort(this.sortVehicles);
|
||||
},
|
||||
|
||||
computedCarList() {
|
||||
const trimmedSearchValue = this.searchedVehicleTypeName.trim();
|
||||
|
||||
return this.store.carDataList
|
||||
.filter((car) => new RegExp(`${trimmedSearchValue}`, 'i').test(car.type))
|
||||
.sort(this.sortVehicles);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/tab.scss';
|
||||
|
||||
.actions-panel {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5em;
|
||||
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.actions-panel_vehicles {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.actions-panel_search {
|
||||
input {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
overflow: auto;
|
||||
height: 750px;
|
||||
max-height: 95vh;
|
||||
}
|
||||
|
||||
.wiki-list table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
|
||||
thead {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #111;
|
||||
padding: 0.5em;
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
tr {
|
||||
cursor: pointer;
|
||||
background-color: #333;
|
||||
|
||||
&:nth-child(odd) {
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
text-align: center;
|
||||
padding: 0.25em;
|
||||
height: 85px;
|
||||
}
|
||||
|
||||
td:first-child {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
td img {
|
||||
display: block;
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $breakpointMd) {
|
||||
.wiki-list table {
|
||||
td {
|
||||
width: 100px;
|
||||
height: auto;
|
||||
|
||||
img {
|
||||
width: 6em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $breakpointSm) {
|
||||
.actions-panel {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.actions-panel_vehicles {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.actions-panel_search {
|
||||
display: grid;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<section class="wiki-list tab">
|
||||
<div class="tab_header">
|
||||
<h2>{{ $t('wiki.title') }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="tab_content">
|
||||
<div class="actions-panel">
|
||||
<div class="actions-panel_vehicles">
|
||||
<button class="btn btn--choice" @click="changeWikiMode('locomotives')">
|
||||
{{ $t('wiki.action-vehicles') }}
|
||||
</button>
|
||||
<button class="btn btn--choice" @click="changeWikiMode('carWagons')">
|
||||
{{ $t('wiki.action-carriages') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="actions-panel_search">
|
||||
<input type="text" :placeholder="$t('wiki.search')" v-model="searchedVehicleTypeName" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-wrapper" @scroll="scrollEvent" ref="table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="header in wikiMode == 'locomotives' ? locoHeaders : carHeaders" @click="toggleSorter(header)" :key="header.id">
|
||||
{{ $t(`wiki.header.${header.id}`) }}
|
||||
|
||||
<span v-if="currentModeSorter.id == header.id">
|
||||
{{ currentModeSorter.direction == 1 ? `⇑` : `⇓` }}
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody v-if="wikiMode == 'locomotives'">
|
||||
<tr
|
||||
v-for="loco in computedLocoList"
|
||||
:key="loco.type"
|
||||
@click="previewLocomotive(loco)"
|
||||
@keydown.enter="previewLocomotive(loco)"
|
||||
@dblclick="addLocomotive(loco)"
|
||||
tabindex="0"
|
||||
>
|
||||
<td>
|
||||
<object :data="getThumbnailURL(loco.type, 'small')" type="image/jpeg">
|
||||
<!-- <img src="default.jpg" /> -->
|
||||
<div>?</div>
|
||||
</object>
|
||||
</td>
|
||||
|
||||
<td>{{ loco.type }}</td>
|
||||
<td>{{ $t(`wiki.${loco.power}`) }}</td>
|
||||
<td>{{ loco.constructionType }}</td>
|
||||
<td>
|
||||
{{ locoSupportsColdStart(loco.constructionType) ? `✓` : '✗' }}
|
||||
</td>
|
||||
<td>{{ loco.length }}m</td>
|
||||
<td>{{ loco.mass }}t</td>
|
||||
<td>{{ loco.maxSpeed }}km/h</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tbody v-else>
|
||||
<tr
|
||||
v-for="car in computedCarList"
|
||||
:key="car.type"
|
||||
@keydow.enter="previewCarWagon(car)"
|
||||
@click="previewCarWagon(car)"
|
||||
@dblclick="addCarWagon(car)"
|
||||
tabindex="0"
|
||||
>
|
||||
<td>
|
||||
<!-- <img
|
||||
:src="getThumbnailURL(car.type, 'small')"
|
||||
loading="lazy"
|
||||
:alt="`${car.type}`"
|
||||
/> -->
|
||||
<object :data="getThumbnailURL(car.type, 'small')" type="image/jpeg" loading="lazy">
|
||||
<!-- <img src="default.jpg" /> -->
|
||||
<div>?</div>
|
||||
</object>
|
||||
</td>
|
||||
|
||||
<td>{{ car.type }}</td>
|
||||
<td>{{ car.constructionType }}</td>
|
||||
<td>{{ car.length }}m</td>
|
||||
<td>{{ car.mass }}t</td>
|
||||
<td>{{ car.maxSpeed }}km/h</td>
|
||||
<td>
|
||||
{{ car.cargoList.length == 0 ? '-' : car.cargoList.length }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useStore } from '../../store';
|
||||
import stockPreviewMixin from '../../mixins/stockPreviewMixin';
|
||||
import { Vehicle } from '../../types';
|
||||
import { isLocomotive } from '../../utils/vehicleUtils';
|
||||
import stockMixin from '../../mixins/stockMixin';
|
||||
import imageMixin from '../../mixins/imageMixin';
|
||||
import { locoSupportsColdStart } from '../../utils/locoUtils';
|
||||
|
||||
type WikiMode = 'locomotives' | 'carWagons';
|
||||
type SorterID = 'type' | 'constructionType' | 'image' | 'length' | 'mass' | 'maxSpeed' | 'cargoCount' | 'power' | 'coldStart';
|
||||
|
||||
interface WikiHeader {
|
||||
id: SorterID;
|
||||
sortable: boolean;
|
||||
}
|
||||
|
||||
const locoHeaders: WikiHeader[] = [
|
||||
{ id: 'image', sortable: false },
|
||||
{ id: 'type', sortable: true },
|
||||
{ id: 'power', sortable: true },
|
||||
{ id: 'constructionType', sortable: true },
|
||||
{ id: 'coldStart', sortable: true },
|
||||
{ id: 'length', sortable: true },
|
||||
{ id: 'mass', sortable: true },
|
||||
{ id: 'maxSpeed', sortable: true },
|
||||
];
|
||||
|
||||
const carHeaders: WikiHeader[] = [
|
||||
{ id: 'image', sortable: false },
|
||||
{ id: 'type', sortable: true },
|
||||
{ id: 'constructionType', sortable: true },
|
||||
{ id: 'length', sortable: true },
|
||||
{ id: 'mass', sortable: true },
|
||||
{ id: 'maxSpeed', sortable: true },
|
||||
{ id: 'cargoCount', sortable: true },
|
||||
];
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [stockPreviewMixin, stockMixin, imageMixin],
|
||||
|
||||
data() {
|
||||
return {
|
||||
store: useStore(),
|
||||
locoHeaders,
|
||||
carHeaders,
|
||||
|
||||
locosScrollTop: 0,
|
||||
carsScrollTop: 0,
|
||||
|
||||
wikiMode: 'locomotives' as WikiMode,
|
||||
searchedVehicleTypeName: '',
|
||||
|
||||
currentLocoSorter: {
|
||||
id: 'type' as SorterID,
|
||||
direction: 1,
|
||||
},
|
||||
|
||||
currentCarSorter: {
|
||||
id: 'type' as SorterID,
|
||||
direction: 1,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
activated() {
|
||||
const tableWrapperRef = this.$refs['table-wrapper'] as HTMLElement;
|
||||
tableWrapperRef.scrollTo({
|
||||
top: this.wikiMode == 'locomotives' ? this.locosScrollTop : this.carsScrollTop,
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
locoSupportsColdStart,
|
||||
|
||||
scrollEvent(e: Event) {
|
||||
const tableScrollTop = (e.target as HTMLElement).scrollTop;
|
||||
|
||||
if (this.wikiMode == 'locomotives') this.locosScrollTop = tableScrollTop;
|
||||
else this.carsScrollTop = tableScrollTop;
|
||||
},
|
||||
|
||||
changeWikiMode(wikiMode: WikiMode) {
|
||||
this.searchedVehicleTypeName = '';
|
||||
this.wikiMode = wikiMode;
|
||||
},
|
||||
|
||||
toggleSorter(header: WikiHeader) {
|
||||
if (!header.sortable) return;
|
||||
|
||||
if (header.id == this.currentModeSorter.id) this.currentModeSorter.direction *= -1;
|
||||
this.currentModeSorter.id = header.id;
|
||||
},
|
||||
|
||||
sortVehicles(vA: Vehicle, vB: Vehicle) {
|
||||
const { id, direction } = this.currentModeSorter;
|
||||
const vehiclesAreLocos = isLocomotive(vA) && isLocomotive(vB);
|
||||
const vehiclesAreCars = !isLocomotive(vA) && !isLocomotive(vB);
|
||||
|
||||
switch (id) {
|
||||
case 'type':
|
||||
case 'constructionType':
|
||||
return direction == 1 ? vA[id].localeCompare(vB[id]) : vB[id].localeCompare(vA[id]);
|
||||
|
||||
case 'mass':
|
||||
case 'length':
|
||||
case 'maxSpeed':
|
||||
return Math.sign(vA[id] - vB[id]) * direction;
|
||||
|
||||
case 'cargoCount':
|
||||
if (vehiclesAreCars) return Math.sign((vA.cargoList.length || -1) - (vB.cargoList.length || -1)) * direction;
|
||||
break;
|
||||
|
||||
case 'coldStart':
|
||||
if (vehiclesAreLocos) return (locoSupportsColdStart(vA.constructionType) > locoSupportsColdStart(vB.constructionType) ? 1 : -1) * direction;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return direction == 1 ? vA.type.localeCompare(vB.type) : vB.type.localeCompare(vA.type);
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
currentModeSorter() {
|
||||
return this.wikiMode == 'carWagons' ? this.currentCarSorter : this.currentLocoSorter;
|
||||
},
|
||||
|
||||
computedLocoList() {
|
||||
const trimmedSearchValue = this.searchedVehicleTypeName.trim();
|
||||
|
||||
return this.store.locoDataList.filter((loco) => new RegExp(`${trimmedSearchValue}`, 'i').test(loco.type)).sort(this.sortVehicles);
|
||||
},
|
||||
|
||||
computedCarList() {
|
||||
const trimmedSearchValue = this.searchedVehicleTypeName.trim();
|
||||
|
||||
return this.store.carDataList.filter((car) => new RegExp(`${trimmedSearchValue}`, 'i').test(car.type)).sort(this.sortVehicles);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/tab.scss';
|
||||
|
||||
.actions-panel {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5em;
|
||||
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.actions-panel_vehicles {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.actions-panel_search {
|
||||
input {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
overflow: auto;
|
||||
height: 750px;
|
||||
max-height: 95vh;
|
||||
}
|
||||
|
||||
.wiki-list table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
|
||||
thead {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #111;
|
||||
padding: 0.5em;
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
tr {
|
||||
cursor: pointer;
|
||||
background-color: #333;
|
||||
|
||||
&:nth-child(odd) {
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
text-align: center;
|
||||
padding: 0.25em;
|
||||
height: 85px;
|
||||
}
|
||||
|
||||
td:first-child {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
td object[type='image/jpeg'] {
|
||||
display: flex;
|
||||
max-width: 120px;
|
||||
min-height: 60px;
|
||||
|
||||
div {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $breakpointMd) {
|
||||
.wiki-list table {
|
||||
td {
|
||||
width: 100px;
|
||||
height: auto;
|
||||
|
||||
img {
|
||||
width: 6em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $breakpointSm) {
|
||||
.actions-panel {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.actions-panel_vehicles {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.actions-panel_search {
|
||||
display: grid;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user