section improvements; hotfixes

This commit is contained in:
2023-10-26 21:35:42 +02:00
parent 45b2bd01a2
commit 2bbf9a8ac3
28 changed files with 495 additions and 650 deletions
-6
View File
@@ -54,10 +54,4 @@ h2 {
color: #d1d1d1;
}
@media screen and (max-width: $breakpointMd) {
#app {
font-size: calc(0.7rem + 0.75vw);
}
}
</style>
+1 -1
View File
@@ -27,7 +27,7 @@ main {
gap: 1em;
width: 100%;
max-width: 1500px;
max-width: 1300px;
min-height: 75vh;
grid-template-columns: 1fr 2fr;
+83 -68
View File
@@ -1,7 +1,7 @@
<template>
<section class="inputs-section">
<div class="input_container">
<h2 class="input_header">{{ $t("inputs.title") }}</h2>
<h2 class="input_header">{{ $t('inputs.title') }}</h2>
<div class="input_list type">
<div class="vehicle-types locos">
@@ -25,11 +25,9 @@
@keydown.backspace="removeVehicle"
>
<option :value="null" disabled>
{{ $t("inputs.input-vehicle") }}
</option>
<option v-for="loco in locoOptions" :value="loco" :key="loco.type">
{{ loco.type }}<b v-if="loco.supportersOnly">*</b>
{{ $t('inputs.input-vehicle') }}
</option>
<option v-for="loco in locoOptions" :value="loco" :key="loco.type">{{ loco.type }}<b v-if="loco.isSponsorsOnly">*</b></option>
</select>
</div>
@@ -55,23 +53,19 @@
@keydown.backspace="removeVehicle"
>
<option :value="null" disabled>
{{ $t("inputs.input-carwagon") }}
{{ $t('inputs.input-carwagon') }}
</option>
<option v-for="car in carOptions" :value="car" :key="car.type">
{{ car.type }}<b v-if="car.supportersOnly">*</b>
</option>
<option v-for="car in carOptions" :value="car" :key="car.type">{{ car.type }}<b v-if="car.isSponsorsOnly">*</b></option>
</select>
</div>
<div class="input_list cargo">
<label for="cargo-select">{{ $t("inputs.cargo-title") }}</label>
<label for="cargo-select">{{ $t('inputs.cargo-title') }}</label>
<select
id="cargo-select"
:disabled="
(store.chosenCar && !store.chosenCar.loadable) ||
(store.chosenCar && store.chosenCar.useType == 'car-passenger') ||
!store.chosenCar
(store.chosenCar && !store.chosenCar.loadable) || (store.chosenCar && store.chosenCar.useType == 'car-passenger') || !store.chosenCar
"
data-select="cargo"
data-ignore-outside="1"
@@ -81,49 +75,30 @@
@keydown.enter.prevent="addOrSwitchVehicle"
@keydown.backspace="removeVehicle"
>
<option
:value="null"
v-if="!store.chosenCar || !store.chosenCar.loadable"
>
{{ $t("inputs.no-cargo-available") }}
<option :value="null" v-if="!store.chosenCar || !store.chosenCar.loadable">
{{ $t('inputs.no-cargo-available') }}
</option>
<option :value="null" v-else>{{ $t("inputs.cargo-empty") }}</option>
<option :value="null" v-else>{{ $t('inputs.cargo-empty') }}</option>
<option
v-for="cargo in store.chosenCar?.cargoList"
:value="cargo"
:key="cargo.id"
>
<option v-for="cargo in store.chosenCar?.cargoList" :value="cargo" :key="cargo.id">
{{ cargo.id }}
</option>
</select>
</div>
<div class="input_actions">
<button
class="btn"
@click="addVehicle(store.chosenVehicle, store.chosenCargo)"
>
{{ $t("inputs.action-add") }}
<button class="btn" @click="addVehicle(store.chosenVehicle, store.chosenCargo)">
{{ $t('inputs.action-add') }}
</button>
<button
class="btn"
@click="switchVehicles"
:disabled="store.chosenStockListIndex == -1"
:data-disabled="store.chosenStockListIndex == -1"
>
{{ $t("inputs.action-swap") }}
<button class="btn" @click="switchVehicles" :disabled="store.chosenStockListIndex == -1" :data-disabled="store.chosenStockListIndex == -1">
{{ $t('inputs.action-swap') }}
<b class="text--accent">
{{
store.chosenStockListIndex == -1
? ""
: `${store.chosenStockListIndex + 1}.`
}}
{{ store.chosenStockListIndex == -1 ? '' : `${store.chosenStockListIndex + 1}.` }}
</b>
</button>
<button class="btn" @click="store.isRealStockListCardOpen = true">
<b>{{ $t("inputs.real-stock") }}</b>
<b>{{ $t('inputs.real-stock') }}</b>
</button>
</div>
</div>
@@ -131,54 +106,63 @@
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { defineComponent } from 'vue';
import imageMixin from "../../mixins/imageMixin";
import { useStore } from "../../store";
import stockPreviewMixin from "../../mixins/stockPreviewMixin";
import stockMixin from "../../mixins/stockMixin";
import imageMixin from '../../mixins/imageMixin';
import { useStore } from '../../store';
import stockPreviewMixin from '../../mixins/stockPreviewMixin';
import stockMixin from '../../mixins/stockMixin';
export default defineComponent({
mixins: [imageMixin, stockPreviewMixin, stockMixin],
data: () => ({
store: useStore(),
locomotiveTypeList: [
{
id: "loco-e",
desc: "ELEKTRYCZNE",
id: 'loco-e',
desc: 'ELEKTRYCZNE',
},
{
id: "loco-s",
desc: "SPALINOWE",
id: 'loco-s',
desc: 'SPALINOWE',
},
{
id: "loco-ezt",
desc: "ELEKTR. ZESPOŁY TRAKCYJNE",
id: 'loco-ezt',
desc: 'ELEKTR. ZESPOŁY TRAKCYJNE',
},
{
id: "loco-szt",
desc: "SPAL. ZESPOŁY TRAKCYJNE",
id: 'loco-szt',
desc: 'SPAL. ZESPOŁY TRAKCYJNE',
},
],
carTypeList: [
{
id: "car-passenger",
desc: "PASAŻERSKIE",
id: 'car-passenger',
desc: 'PASAŻERSKIE',
},
{
id: "car-cargo",
desc: "TOWAROWE",
id: 'car-cargo',
desc: 'TOWAROWE',
},
],
}),
setup() {
const store = useStore();
computed: {
locoOptions() {
return this.store.locoDataList
.slice()
.sort((a, b) => (a.type > b.type ? 1 : -1))
.filter((loco) => loco.power == this.store.chosenLocoPower);
},
return {
store,
};
carOptions() {
return this.store.carDataList
.slice()
.sort((a, b) => (a.type > b.type ? 1 : -1))
.filter((car) => car.useType == this.store.chosenCarUseType);
},
},
methods: {
@@ -189,8 +173,7 @@ export default defineComponent({
addOrSwitchVehicle() {
if (!this.store.chosenVehicle) return;
if (this.store.chosenStockListIndex == -1)
this.addVehicle(this.store.chosenVehicle, this.store.chosenCargo);
if (this.store.chosenStockListIndex == -1) this.addVehicle(this.store.chosenVehicle, this.store.chosenCargo);
else this.switchVehicles();
},
@@ -213,12 +196,35 @@ export default defineComponent({
const stockObject = this.getStockObject(vehicle, this.store.chosenCargo);
this.store.stockList[this.store.chosenStockListIndex] = stockObject;
},
selectLocoType(locoTypeId: string) {
this.store.chosenLocoPower = locoTypeId;
this.store.chosenVehicle = this.locoOptions[0];
this.store.chosenLoco = this.locoOptions[0];
},
selectCarWagonType(carWagonTypeId: string) {
this.store.chosenCarUseType = carWagonTypeId;
this.store.chosenVehicle = this.carOptions[0];
this.store.chosenCar = this.carOptions[0];
this.store.chosenCargo = null;
},
previewVehicleByType(type: 'loco' | 'car' | 'cargo') {
this.$nextTick(() => {
if (!this.store.chosenLoco && !this.store.chosenCar) return;
this.store.chosenVehicle = type == 'loco' ? this.store.chosenLoco : this.store.chosenCar;
this.store.chosenCargo = this.store.chosenCar?.cargoList.find((cargo) => cargo.id == this.store.chosenCargo?.id) || null;
});
},
},
});
</script>
<style lang="scss" scoped>
@import "../../styles/global";
@import '../../styles/global';
.inputs-section {
display: flex;
@@ -228,6 +234,11 @@ export default defineComponent({
grid-column: 1;
}
.input_container {
width: 100%;
max-width: 380px;
}
.input_header {
margin-bottom: 1em;
}
@@ -236,7 +247,7 @@ button.btn--choice {
font-size: 0.9em;
padding: 0.3em 0.6em;
&[data-selected="true"] {
&[data-selected='true'] {
background-color: $accentColor;
color: black;
}
@@ -247,6 +258,10 @@ button.btn--choice {
.input_list {
margin: 0.5em 0;
select {
width: 100%;
}
label {
display: block;
+79 -116
View File
@@ -1,89 +1,66 @@
<template>
<section class="train-image-section">
<div class="train-image__wrapper">
<div
class="train-image__content"
:class="{ supporter: store.chosenVehicle?.supportersOnly }"
>
<transition name="img-message-anim">
<div
class="empty-message"
v-if="store.imageLoading && store.chosenVehicle?.imageSrc"
>
{{ $t("preview.loading") }}
</div>
</transition>
<div class="train-image__content" :class="{ sponsor: store.chosenVehicle?.isSponsorsOnly }">
<img
v-if="store.chosenVehicle"
tabindex="0"
:src="getThumbnailURL(store.chosenVehicle.type, 'small')"
@click="onImageClick"
@keydown.enter="onImageClick"
@error="onImageError"
type="image/jpeg"
/>
<div class="no-img" v-if="!store.chosenVehicle">
{{ $t("preview.title") }}
</div>
<img
v-if="store.chosenVehicle"
:src="getThumbnailURL(store.chosenVehicle.type, 'small')"
:alt="store.chosenVehicle.type"
@load="onImageLoad"
@click="onImageClick"
/>
<!-- <div class="empty-message" v-if="store.chosenVehicle && !store.chosenVehicle.imageSrc">Ten pojazd nie ma jeszcze podglądu!</div> -->
</div>
<div class="train-image__info" v-if="store.chosenVehicle">
<b class="text--accent">{{ store.chosenVehicle.type }}</b> &bull;
<b style="color: #ccc">
{{
$t(
`preview.${
isLocomotive(store.chosenVehicle)
? store.chosenVehicle.power
: store.chosenVehicle.useType
}`,
)
}}
</b>
<div style="color: #ccc">
<div>
{{ store.chosenVehicle.length }}m | {{ store.chosenVehicle.mass }}t
| {{ store.chosenVehicle.maxSpeed }} km/h
</div>
<div v-if="isLocomotive(store.chosenVehicle)">
{{ $t("preview.cabin") }} {{ store.chosenVehicle.cabinType }}
</div>
<div v-else>
{{
store.chosenVehicle.useType == "car-cargo" // ? store.stockData?.usage[store.chosenVehicle.constructionType]
? $t(`usage.${store.chosenVehicle.constructionType}`)
: `${$t("preview.construction")} ${
store.chosenVehicle.constructionType
}`
}}
</div>
<b style="color: salmon" v-if="store.chosenVehicle.supportersOnly">{{
$t("preview.sponsor-only")
}}</b>
</div>
</div>
<div class="train-image__info" v-else>{{ $t("preview.desc") }}</div>
<img v-else src="images/placeholder.jpg" alt="placeholder" />
</div>
<div class="train-image__info" v-if="store.chosenVehicle">
<b class="text--accent">{{ store.chosenVehicle.type }}</b> &bull;
<b style="color: #ccc">
{{ $t(`preview.${isLocomotive(store.chosenVehicle) ? store.chosenVehicle.power : store.chosenVehicle.useType}`) }}
</b>
<div style="color: #ccc">
<div>{{ store.chosenVehicle.length }}m | {{ store.chosenVehicle.mass }}t | {{ store.chosenVehicle.maxSpeed }} km/h</div>
<div v-if="isLocomotive(store.chosenVehicle)">{{ $t('preview.cabin') }} {{ store.chosenVehicle.cabinType }}</div>
<div v-else>
{{
store.chosenVehicle.useType == 'car-cargo'
? $t(`usage.${store.chosenVehicle.constructionType}`)
: `${$t('preview.construction')} ${store.chosenVehicle.constructionType}`
}}
</div>
<b style="color: salmon" v-if="store.chosenVehicle.isSponsorsOnly">{{
$t('preview.sponsor-only', [
new Date(store.chosenVehicle.sponsorsOnlyTimestamp).toLocaleDateString($i18n.locale == 'pl' ? 'pl-PL' : 'en-GB'),
])
}}</b>
</div>
</div>
<div class="train-image__info" v-else>{{ $t('preview.desc') }}</div>
</section>
</template>
<script lang="ts">
import { computed, defineComponent } from "vue";
import { useStore } from "../../store";
import { isLocomotive } from "../../utils/vehicleUtils";
import { ILocomotive, Vehicle } from "../../types";
import imageMixin from "../../mixins/imageMixin";
import { computed, defineComponent } from 'vue';
import { useStore } from '../../store';
import { isLocomotive } from '../../utils/vehicleUtils';
import { ILocomotive, Vehicle } from '../../types';
import imageMixin from '../../mixins/imageMixin';
export default defineComponent({
mixins: [imageMixin],
data() {
return {
noImageAvailable: false,
};
},
setup() {
const store = useStore();
@@ -106,88 +83,74 @@ export default defineComponent({
this.store.imageLoading = false;
},
onImageError(e: Event) {
const el = e.target as HTMLImageElement;
if (el.src == '/images/placeholder.jpg') return;
el.src = '/images/placeholder.jpg';
},
isLocomotive(vehicle: Vehicle): vehicle is ILocomotive {
return isLocomotive(vehicle);
},
onImageClick() {
onImageClick(e: Event) {
const target = e.target as HTMLElement;
const chosenVehicle = this.store.chosenVehicle;
if (!chosenVehicle) return;
this.store.vehiclePreviewSrc = this.getThumbnailURL(
chosenVehicle.type,
"large",
);
this.store.lastFocusedElement = target;
this.store.vehiclePreviewSrc = this.getThumbnailURL(chosenVehicle.type, 'large');
},
},
});
</script>
<style lang="scss" scoped>
@import "../../styles/global.scss";
@import '../../styles/global.scss';
.train-image-section {
display: flex;
flex-direction: column;
text-align: center;
grid-row: 3;
grid-column: 1;
margin-top: 2em;
margin-top: 1em;
height: 22em;
}
.train-image {
&__wrapper {
text-align: center;
}
&__content {
border: 1px solid white;
position: relative;
overflow: hidden;
max-width: 22em;
height: 13em;
margin: 0 auto;
&.supporter {
&.sponsor img {
border: 1px solid salmon;
}
img {
max-width: 380px;
width: 100%;
height: 100%;
border: 1px solid white;
cursor: zoom-in;
}
.empty-message,
.no-img {
position: absolute;
left: 0;
bottom: 0;
padding: 0.3em 0;
width: 100%;
}
.empty-message {
background: rgba(#000, 0.75);
}
}
}
.train-image__info {
margin: 1em 0;
font-size: 1.1em;
padding: 0 1em;
padding: 0.5em;
margin: 0.5em auto;
line-height: 1.35;
b {
font-size: 1.1em;
}
width: 100%;
max-width: 380px;
div {
margin: 0.25em 0;
}
background-color: $secondaryColor;
font-weight: bold;
}
// Transition animations
+92 -194
View File
@@ -1,176 +1,111 @@
<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") }}
</div>
<button class="btn btn--image" @click="clickFileInput">
<input type="file" @change="uploadStock" ref="conFile" accept=".con,.txt" />
<img src="/images/icon-upload.svg" alt="upload icon" />
{{ $t('stocklist.action-upload') }}
</button>
<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") }}
{{ $t('stocklist.vehicle-no') }}
<span class="text--accent">{{ store.chosenStockListIndex + 1 }}</span>
&nbsp;
</b>
<b v-else>
{{ $t("stocklist.no-vehicle-chosen") }}
{{ $t('stocklist.no-vehicle-chosen') }}
</b>
<button
class="btn"
:tabindex="store.chosenStockListIndex == -1 ? -1 : 0"
@click="moveUpStock(store.chosenStockListIndex)"
>
<button class="btn" :tabindex="store.chosenStockListIndex == -1 ? -1 : 0" @click="moveUpStock(store.chosenStockListIndex)">
<img :src="getIconURL('higher')" alt="move up vehicle" />
{{ $t("stocklist.action-move-up") }}
{{ $t('stocklist.action-move-up') }}
</button>
<button
class="btn"
:tabindex="store.chosenStockListIndex == -1 ? -1 : 0"
@click="moveDownStock(store.chosenStockListIndex)"
>
<button class="btn" :tabindex="store.chosenStockListIndex == -1 ? -1 : 0" @click="moveDownStock(store.chosenStockListIndex)">
<img :src="getIconURL('lower')" alt="move down vehicle" />
{{ $t("stocklist.action-move-down") }}
{{ $t('stocklist.action-move-down') }}
</button>
<button
class="btn"
:tabindex="store.chosenStockListIndex == -1 ? -1 : 0"
@click="removeStock(store.chosenStockListIndex)"
>
<button class="btn" :tabindex="store.chosenStockListIndex == -1 ? -1 : 0" @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") }}:
- {{ $t('stocklist.vmax') }}:
<span class="text--accent">{{ store.maxStockSpeed }} km/h</span>
</span>
</div>
<div class="stock_cold-start">
<label>
<input
type="checkbox"
v-model="store.isColdStart"
:disabled="
!locoSupportsColdStart(store.stockList[0]?.constructionType || '')
"
/>
{{ $t("stocklist.coldstart-info") }}
<input type="checkbox" v-model="store.isColdStart" :disabled="!locoSupportsColdStart(store.stockList[0]?.constructionType || '')" />
{{ $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") }}
</div>
<div class="warning" v-if="trainTooLong && store.isTrainPassenger">(!) {{ $t('stocklist.warning-passenger-too-long') }}</div>
<div class="warning" v-if="trainTooLong && !store.isTrainPassenger">
(!) {{ $t("stocklist.warning-freight-too-long") }}
</div>
<div class="warning" v-if="trainTooLong && !store.isTrainPassenger">(!) {{ $t('stocklist.warning-freight-too-long') }}</div>
<div class="warning" v-if="trainTooHeavy">
(!)
<i18n-t keypath="stocklist.warning-too-heavy">
<template #href>
<a
target="_blank"
href="https://docs.google.com/spreadsheets/d/1bFXUsHsAu4youmNz-46Q1HslZaaoklvfoBDS553TnNk/edit"
>
{{ $t("stocklist.acceptable-mass-docs") }}
<a target="_blank" href="https://docs.google.com/spreadsheets/d/1bFXUsHsAu4youmNz-46Q1HslZaaoklvfoBDS553TnNk/edit">
{{ $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>
@@ -179,7 +114,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">
@@ -195,25 +130,13 @@
@keydown.backspace="removeStock(i)"
ref="itemRefs"
>
<div
class="stock-info"
@dragstart="onDragStart(i)"
@drop="onDrop($event, i)"
@dragover="allowDrop"
draggable="true"
>
<span
class="stock-info__no"
:data-selected="i == store.chosenStockListIndex"
>
<div class="stock-info" @dragstart="onDragStart(i)" @drop="onDrop($event, i)" @dragover="allowDrop" draggable="true">
<span class="stock-info__no" :data-selected="i == store.chosenStockListIndex">
<span v-if="i == store.chosenStockListIndex">&bull;&nbsp;</span>
{{ i + 1 }}.
</span>
<span
class="stock-info__type"
:class="{ supporter: stock.supportersOnly }"
>
<span class="stock-info__type" :class="{ sponsor: stock.isSponsorsOnly }">
{{ stock.isLoco ? stock.type : getCarSpecFromType(stock.type) }}
</span>
@@ -221,9 +144,7 @@
{{ 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>
@@ -233,19 +154,19 @@
</template>
<script lang="ts">
import { defineComponent } from "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",
name: 'stock-list',
components: { StockThumbnails },
mixins: [warningsMixin, imageMixin, stockMixin, stockPreviewMixin],
@@ -269,20 +190,12 @@ 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 coldStart =
i == 0 &&
this.store.isColdStart &&
locoSupportsColdStart(stock.constructionType || "")
? ",c"
: "";
let stockTypeStr = stock.isLoco || !stock.cargo ? stock.type : `${stock.type}:${stock.cargo.id}`;
let coldStart = i == 0 && this.store.isColdStart && locoSupportsColdStart(stock.constructionType || '') ? ',c' : '';
return stockTypeStr + coldStart;
})
.join(";");
.join(';');
},
stockIsEmpty() {
@@ -290,18 +203,11 @@ 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;
},
},
@@ -312,18 +218,18 @@ export default defineComponent({
navigator.clipboard.writeText(this.stockString);
setTimeout(() => {
alert(this.$t("stocklist.alert-copied"));
alert(this.$t('stocklist.alert-copied'));
}, 20);
},
clickFileInput() {
(this.$refs['conFile'] as HTMLInputElement).click();
},
onListItemClick(stockID: number) {
const stock = this.store.stockList[stockID];
this.store.chosenStockListIndex =
this.store.chosenStockListIndex == stockID &&
this.store.chosenVehicle?.type == stock.type
? -1
: stockID;
this.store.chosenStockListIndex = this.store.chosenStockListIndex == stockID && this.store.chosenVehicle?.type == stock.type ? -1 : stockID;
if (this.store.chosenStockListIndex == -1) {
this.store.chosenVehicle = null;
@@ -336,13 +242,12 @@ 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]})`;
@@ -370,12 +275,9 @@ 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) {
@@ -413,8 +315,7 @@ 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];
@@ -423,33 +324,30 @@ 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;
@@ -461,14 +359,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) {
@@ -478,14 +376,13 @@ 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;
@@ -499,8 +396,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;
@@ -532,7 +429,7 @@ export default defineComponent({
background-color: #353a57;
&[data-disabled="true"] {
&[data-disabled='true'] {
opacity: 0.8;
user-select: none;
@@ -564,12 +461,13 @@ export default defineComponent({
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
label.file-label {
text-align: center;
cursor: pointer;
button {
width: 100%;
input {
display: none;
opacity: 0;
width: 0;
height: 0;
}
}
}
@@ -626,7 +524,7 @@ li > .stock-info {
}
}
.supporter {
.sponsor {
color: salmon;
}
@@ -644,7 +542,7 @@ li > .stock-info {
min-width: 3.5em;
text-align: right;
&[data-selected="true"] {
&[data-selected='true'] {
color: $accentColor;
}
}
+65 -61
View File
@@ -7,10 +7,10 @@
<div class="tab_content">
<div class="actions-panel">
<div class="actions-panel_vehicles">
<button class="btn" :data-chosen="filters.tractions" @click="toggleFilter('tractions')">
<button class="btn" :data-chosen="currentFilterMode == 'tractions'" @click="toggleFilter('tractions')">
{{ $t('wiki.action-vehicles') }}
</button>
<button class="btn" :data-chosen="filters.carriages" @click="toggleFilter('carriages')">
<button class="btn" :data-chosen="currentFilterMode == 'carriages'" @click="toggleFilter('carriages')">
{{ $t('wiki.action-carriages') }}
</button>
</div>
@@ -34,12 +34,16 @@
</tr>
</thead>
<!-- @click="previewLocomotive(vehicle)"
@keydown.enter="previewLocomotive(vehicle)"
@dblclick="addLocomotive(vehicle)"
-->
<tbody>
<tr v-for="vehicle in computedVehicleList" v-show="vehicle.show" :key="vehicle.type" tabindex="0">
<tr
v-for="{ vehicle, show } in computedTableData"
tabindex="0"
v-show="show"
:key="vehicle.type"
@click="previewVehicle(vehicle)"
@keydown.enter="previewVehicle(vehicle)"
@dblclick="addVehicle(vehicle)"
>
<td style="width: 120px">
<img
width="120"
@@ -50,16 +54,18 @@
/>
</td>
<td>{{ vehicle.type }}</td>
<!-- <td>{{ $t(`wiki.${vehicle.power || vehicle.}`) }}</td> -->
<td :data-sponsoronly="vehicle.isSponsorsOnly">{{ vehicle.type }}</td>
<td v-if="isLocomotive(vehicle)">{{ $t(`wiki.${vehicle.power}`) }}</td>
<td v-else>{{ $t(`wiki.${vehicle.useType}`) }}</td>
<td>{{ vehicle.constructionType }}</td>
<td>{{ vehicle.length }}m</td>
<td>{{ vehicle.mass }}t</td>
<td>{{ vehicle.maxSpeed }}km/h</td>
<td v-if="!filters.tractions && filters.carriages">{{ !isLocomotive(vehicle) ? vehicle.cargoList.length ?? '---' : 'niedost.' }}</td>
<td v-if="filters.tractions && !filters.carriages">
<td v-if="currentFilterMode == 'carriages'">{{ !isLocomotive(vehicle) ? vehicle.cargoList.length : '---' }}</td>
<td v-if="currentFilterMode == 'tractions'">
{{ isLocomotive(vehicle) ? (locoSupportsColdStart(vehicle.constructionType) ? `&check;` : '&cross;') : '---' }}
</td>
</tr>
@@ -82,18 +88,23 @@ import stockMixin from '../../mixins/stockMixin';
import imageMixin from '../../mixins/imageMixin';
import { locoSupportsColdStart } from '../../utils/locoUtils';
type SorterID = 'type' | 'constructionType' | 'image' | 'length' | 'mass' | 'maxSpeed' | 'cargoCount' | 'power' | 'coldStart';
type SorterID = 'type' | 'constructionType' | 'image' | 'length' | 'mass' | 'maxSpeed' | 'cargoCount' | 'group' | 'coldStart';
interface WikiHeader {
interface IWikiHeader {
id: SorterID;
sortable: boolean;
for: 'all' | 'carriages' | 'tractions';
}
const headers: WikiHeader[] = [
interface IWikiRow {
vehicle: Vehicle;
show: boolean;
}
const headers: IWikiHeader[] = [
{ id: 'image', sortable: false, for: 'all' },
{ id: 'type', sortable: true, for: 'all' },
{ id: 'power', sortable: true, for: 'all' },
{ id: 'group', sortable: true, for: 'all' },
{ id: 'constructionType', sortable: true, for: 'all' },
{ id: 'length', sortable: true, for: 'all' },
{ id: 'mass', sortable: true, for: 'all' },
@@ -102,16 +113,6 @@ const headers: WikiHeader[] = [
{ id: 'cargoCount', sortable: true, for: 'carriages' },
];
// 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],
@@ -120,8 +121,7 @@ export default defineComponent({
store: useStore(),
headers,
locosScrollTop: 0,
carsScrollTop: 0,
scrollTop: 0,
searchedVehicleTypeName: '',
@@ -130,95 +130,95 @@ export default defineComponent({
direction: 1,
},
filters: {
tractions: true,
carriages: true,
},
currentFilterMode: 'all' as 'all' | 'tractions' | 'carriages',
};
},
activated() {
const tableWrapperRef = this.$refs['table-wrapper'] as HTMLElement;
// tableWrapperRef.scrollTo({
// top: this.wikiMode == 'locomotives' ? this.locosScrollTop : this.carsScrollTop,
// });
tableWrapperRef.scrollTo({
top: this.scrollTop,
});
},
methods: {
locoSupportsColdStart,
isLocomotive,
toggleFilter(name: keyof typeof this.filters) {
this.filters[name] = !this.filters[name];
toggleFilter(name: typeof this.currentFilterMode) {
this.currentFilterMode = this.currentFilterMode == name ? 'all' : name;
},
toggleSorter(header: WikiHeader) {
toggleSorter(header: IWikiHeader) {
if (!header.sortable) return;
if (header.id == this.currentSorter.id) this.currentSorter.direction *= -1;
this.currentSorter.id = header.id;
},
sortVehicles(vA: Vehicle, vB: Vehicle) {
sortTableRows(row1: IWikiRow, row2: IWikiRow) {
if (!row1.show) return 0;
const { id, direction } = this.currentSorter;
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 'group':
return direction == 1 ? row1.vehicle[id].localeCompare(row2.vehicle[id]) : row2.vehicle[id].localeCompare(row1.vehicle[id]);
case 'mass':
case 'length':
case 'maxSpeed':
return Math.sign(vA[id] - vB[id]) * direction;
return Math.sign(row1.vehicle[id] - row2.vehicle[id]) * direction;
case 'cargoCount':
if (vehiclesAreCars) return Math.sign((vA.cargoList.length || -1) - (vB.cargoList.length || -1)) * direction;
break;
return (
(!isLocomotive(row1.vehicle) ? Math.sign(row1.vehicle.cargoList.length || -1) : -1) -
(!isLocomotive(row2.vehicle) ? (row2.vehicle.cargoList.length || -1) * direction : -1)
);
case 'coldStart':
if (vehiclesAreLocos) return (locoSupportsColdStart(vA.constructionType) > locoSupportsColdStart(vB.constructionType) ? 1 : -1) * direction;
break;
return (locoSupportsColdStart(row1.vehicle.constructionType) > locoSupportsColdStart(row2.vehicle.constructionType) ? 1 : -1) * direction;
default:
break;
}
return direction == 1 ? vA.type.localeCompare(vB.type) : vB.type.localeCompare(vA.type);
return direction == 1 ? row1.vehicle.type.localeCompare(row2.vehicle.type) : row2.vehicle.type.localeCompare(row1.vehicle.type);
},
},
computed: {
computedVehicleList() {
computedTableData(): IWikiRow[] {
return this.store.vehicleDataList
.map((vehicle) => ({
...vehicle,
vehicle,
show:
new RegExp(`${this.searchedVehicleTypeName.trim()}`, 'i').test(vehicle.type) &&
((this.filters.tractions && isLocomotive(vehicle)) || (this.filters.carriages && !isLocomotive(vehicle))),
(this.currentFilterMode == 'all' ||
(this.currentFilterMode == 'tractions' && isLocomotive(vehicle)) ||
(this.currentFilterMode == 'carriages' && !isLocomotive(vehicle))),
// ((this.filters.tractions && isLocomotive(vehicle)) || (this.filters.carriages && !isLocomotive(vehicle))),
}))
.sort(this.sortVehicles);
.sort((a, b) => this.sortTableRows(a, b));
},
visibleHeaders() {
const filtersActive =
this.filters.carriages && this.filters.tractions ? 'all' : this.filters.carriages ? 'carriages' : this.filters.tractions ? 'tractions' : null;
console.log(filtersActive);
const filtersActive = this.currentFilterMode;
return this.headers.filter((header) => header.for == 'all' || header.for == filtersActive);
},
// computedCarList() {
// const trimmedSearchValue = this.searchedVehicleTypeName.trim();
areTractionVehiclesShown() {
return this.currentFilterMode == 'all' || this.currentFilterMode == 'tractions';
},
// return this.store.carDataList.map((car) =>({
// })).sort(this.sortVehicles);
// },
areCarriagesShown() {
return this.currentFilterMode == 'all' || this.currentFilterMode == 'carriages';
},
},
});
</script>
@@ -291,6 +291,10 @@ export default defineComponent({
td {
text-align: center;
height: 70px;
&[data-sponsoronly='true'] {
color: salmon;
}
}
}
+15 -19
View File
@@ -10,7 +10,7 @@
@dragover="allowDrop"
>
<span @click="onListItemClick(stockIndex)" :key="stock.id">
<b :class="{ supporter: stock.supportersOnly }">
<b :class="{ sponsor: stock.isSponsorsOnly }">
{{ stock.type }}
</b>
@@ -29,18 +29,18 @@
</template>
<script setup lang="ts">
import { Ref, computed, nextTick, ref, watch } from "vue";
import { useStore } from "../../store";
import { IStock } from "../../types";
import { Ref, computed, nextTick, ref, watch } from 'vue';
import { useStore } from '../../store';
import { IStock } from '../../types';
const store = useStore();
const emit = defineEmits(["listItemClick"]);
const emit = defineEmits(['listItemClick']);
const thumbnailsRef = ref() as Ref<HTMLElement>;
const draggedIndex = ref(-1);
const onListItemClick = (index: number) => {
emit("listItemClick", index);
emit('listItemClick', index);
};
const stockImageError = (e: Event, stock: IStock) => {
@@ -53,15 +53,13 @@ watch(
if (index < 0) return;
nextTick(() => {
(thumbnailsRef.value as HTMLElement)
.querySelector(`div:nth-child(${index + 1})`)
?.scrollIntoView({
block: "nearest",
inline: "start",
behavior: "smooth",
});
(thumbnailsRef.value as HTMLElement).querySelector(`div:nth-child(${index + 1})`)?.scrollIntoView({
block: 'nearest',
inline: 'start',
behavior: 'smooth',
});
});
},
}
);
// Dragging images
@@ -72,9 +70,7 @@ const onDragStart = (vehicleIndex: number) => {
const onDrop = (e: DragEvent, vehicleIndex: number) => {
e.preventDefault();
let targetEl = thumbnailsRef.value.querySelector(
`div:nth-child(${vehicleIndex + 1})`,
);
let targetEl = thumbnailsRef.value.querySelector(`div:nth-child(${vehicleIndex + 1})`);
if (!targetEl && draggedIndex.value != -1) return;
@@ -102,7 +98,7 @@ const allowDrop = (e: DragEvent) => {
cursor: pointer;
min-height: 100px;
&[data-selected="true"] {
&[data-selected='true'] {
background-color: rebeccapurple;
}
@@ -125,7 +121,7 @@ const allowDrop = (e: DragEvent) => {
}
}
.supporter {
.sponsor {
color: salmon;
}
</style>
+1 -1
View File
@@ -2,7 +2,7 @@ import axios from "axios";
const http = axios.create({
baseURL:
import.meta.env.VITE_API_DEV === "1"
import.meta.env.VITE_API_DEV === "1" && import.meta.env.DEV
? "http://localhost:5500"
: "https://spythere.github.io/api",
});
+2 -2
View File
@@ -29,7 +29,7 @@
"title": "RAILWAY VEHICLE PREVIEW",
"loading": "IMAGE LOADING...",
"desc": "Choose a railway vehicle above to see its preview",
"sponsor-only": "* SPONSORS ONLY",
"sponsor-only": "* SPONSORS ONLY UNTIL {0}",
"loco-e": "ELECTRIC LOCO",
"loco-s": "DIESEL LOCO",
"loco-ezt": "ELECTRIC M.U.",
@@ -128,7 +128,7 @@
"header": {
"image": "Image",
"type": "Name",
"power": "Type",
"group": "Type group",
"constructionType": "Construction",
"coldStart": "Cold start",
"length": "Length",
+2 -2
View File
@@ -29,7 +29,7 @@
"title": "PODGLĄD WYBRANEGO POJAZDU",
"loading": "ŁADOWANIE OBRAZU...",
"desc": "Wybierz pojazd lub wagon, aby zobaczyć jego podgląd powyżej",
"sponsor-only": "* TYLKO DLA SPONSORÓW",
"sponsor-only": "* TYLKO DLA SPONSORÓW DO {0}",
"loco-e": "ELEKTROWÓZ",
"loco-s": "SPALINOWÓZ",
"loco-ezt": "EZT",
@@ -128,7 +128,7 @@
"header": {
"image": "Zdjęcie",
"type": "Nazwa",
"power": "Rodzaj",
"group": "Rodzaj",
"constructionType": "Konstrukcja",
"coldStart": "Zimny start",
"length": "Długość",
+1 -1
View File
@@ -29,7 +29,7 @@ export default defineComponent({
count,
imgSrc: vehicle.imageSrc,
useType: isLoco ? vehicle.power : vehicle.useType,
supportersOnly: vehicle.supportersOnly,
isSponsorsOnly: vehicle.isSponsorsOnly,
constructionType: vehicle.constructionType,
};
},
+13 -51
View File
@@ -1,6 +1,7 @@
import { defineComponent } from "vue";
import { useStore } from "../store";
import { ICarWagon, ILocomotive, IStock } from "../types";
import { defineComponent } from 'vue';
import { useStore } from '../store';
import { ICarWagon, ILocomotive, IStock, Vehicle } from '../types';
import { isLocomotive } from '../utils/vehicleUtils';
export default defineComponent({
setup() {
@@ -9,64 +10,20 @@ export default defineComponent({
};
},
computed: {
locoOptions() {
return this.store.locoDataList
.slice()
.sort((a, b) => (a.type > b.type ? 1 : -1))
.filter((loco) => loco.power == this.store.chosenLocoPower);
},
carOptions() {
return this.store.carDataList
.slice()
.sort((a, b) => (a.type > b.type ? 1 : -1))
.filter((car) => car.useType == this.store.chosenCarUseType);
},
},
computed: {},
methods: {
selectLocoType(locoTypeId: string) {
this.store.chosenLocoPower = locoTypeId;
this.store.chosenVehicle = this.locoOptions[0];
this.store.chosenLoco = this.locoOptions[0];
},
selectCarWagonType(carWagonTypeId: string) {
this.store.chosenCarUseType = carWagonTypeId;
this.store.chosenVehicle = this.carOptions[0];
this.store.chosenCar = this.carOptions[0];
this.store.chosenCargo = null;
},
previewVehicleByType(type: "loco" | "car" | "cargo") {
this.$nextTick(() => {
if (!this.store.chosenLoco && !this.store.chosenCar) return;
this.store.chosenVehicle =
type == "loco" ? this.store.chosenLoco : this.store.chosenCar;
this.store.chosenCargo =
this.store.chosenCar?.cargoList.find(
(cargo) => cargo.id == this.store.chosenCargo?.id,
) || null;
});
},
previewStock(stock: IStock) {
if (this.store.chosenVehicle?.imageSrc != stock.imgSrc)
this.store.imageLoading = true;
if (this.store.chosenVehicle?.imageSrc != stock.imgSrc) this.store.imageLoading = true;
if (stock.isLoco) {
const chosenLoco =
this.store.locoDataList.find((v) => v.type == stock.type) || null;
const chosenLoco = this.store.locoDataList.find((v) => v.type == stock.type) || null;
this.store.chosenVehicle = chosenLoco;
this.store.chosenLoco = chosenLoco;
this.store.chosenCargo = null;
this.store.chosenLocoPower = stock.useType;
} else {
const chosenCar =
this.store.carDataList.find((v) => v.type == stock.type) || null;
const chosenCar = this.store.carDataList.find((v) => v.type == stock.type) || null;
this.store.chosenVehicle = chosenCar;
this.store.chosenCar = chosenCar;
@@ -89,6 +46,11 @@ export default defineComponent({
this.store.chosenCargo = null;
},
previewVehicle(vehicle: Vehicle) {
if (isLocomotive(vehicle)) this.previewLocomotive(vehicle);
else this.previewCarWagon(vehicle);
},
resetPreview() {
this.store.chosenVehicle = null;
this.store.chosenCar = null;
+19 -18
View File
@@ -1,5 +1,5 @@
import { IStockData, IStore } from "./types";
import { defineStore } from "pinia";
import { IStockData, IStore } from './types';
import { defineStore } from 'pinia';
import {
acceptableMass,
carDataList,
@@ -9,11 +9,11 @@ import {
maxStockSpeed,
totalLength,
totalMass,
} from "./utils/vehicleUtils";
import http from "./http";
} from './utils/vehicleUtils';
import http from './http';
export const useStore = defineStore({
id: "store",
id: 'store',
state: () =>
({
chosenCar: null,
@@ -26,8 +26,8 @@ export const useStore = defineStore({
showSupporter: false,
imageLoading: false,
chosenLocoPower: "loco-e",
chosenCarUseType: "car-passenger",
chosenLocoPower: 'loco-e',
chosenCarUseType: 'car-passenger',
stockList: [],
cargoOptions: [],
@@ -39,20 +39,22 @@ export const useStore = defineStore({
chosenStockListIndex: -1,
chosenRealStockName: undefined,
vehiclePreviewSrc: "",
vehiclePreviewSrc: '',
stockSectionMode: "stock-list",
stockSectionMode: 'stock-list',
isRandomizerCardOpen: false,
isRealStockListCardOpen: false,
stockData: undefined,
lastFocusedElement: null,
}) as IStore,
getters: {
locoDataList: (state) => locoDataList(state),
carDataList: (state) => carDataList(state),
vehicleDataList: (state) => ([...locoDataList(state), ...carDataList(state)]),
vehicleDataList: (state) => [...locoDataList(state), ...carDataList(state)],
totalMass: (state) => totalMass(state),
totalLength: (state) => totalLength(state),
maxStockSpeed: (state) => maxStockSpeed(state),
@@ -63,21 +65,20 @@ export const useStore = defineStore({
actions: {
async fetchStockInfoData() {
const stockData = (await http.get<IStockData>("td2/data/stockInfo.json"))
.data;
const stockData = (await http.get<IStockData>('td2/data/stockInfo.json')).data;
this.stockData = stockData;
},
handleRouting() {
switch (window.location.pathname) {
case "/numgnr":
this.stockSectionMode = "number-generator";
case '/numgnr':
this.stockSectionMode = 'number-generator';
break;
case "/stockgnr":
this.stockSectionMode = "stock-generator";
case '/stockgnr':
this.stockSectionMode = 'stock-generator';
break;
case "/vehicles":
this.stockSectionMode = "wiki-list";
case '/vehicles':
this.stockSectionMode = 'wiki-list';
break;
default:
break;
+33 -5
View File
@@ -1,5 +1,3 @@
@import url('https://fonts.googleapis.com/css2?family=Lato:wght@400;700;900&display=swap');
$breakpointMd: 960px;
$breakpointSm: 550px;
@@ -8,6 +6,36 @@ $textColor: #fff;
$secondaryColor: #222;
$accentColor: #e4c428;
@font-face {
font-family: 'Lato';
src:
url('$fonts/Lato-Light.woff2') format('woff2'),
url('$fonts/Lato-Light.woff') format('woff');
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Lato';
src:
url('$fonts/Lato-Bold.woff2') format('woff2'),
url('$fonts/Lato-Bold.woff') format('woff');
font-weight: bold;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Lato';
src:
url('$fonts/Lato-Regular.woff2') format('woff2'),
url('$fonts/Lato-Regular.woff') format('woff');
font-weight: normal;
font-style: normal;
font-display: swap;
}
::-webkit-scrollbar {
width: 7px;
height: 7px;
@@ -32,7 +60,7 @@ html {
margin: 0;
padding: 0;
font-family: 'Lato', sans-serif;
font-family: Lato, sans-serif;
background-color: $bgColor;
overflow-x: hidden;
@@ -64,7 +92,7 @@ select,
option,
input,
button {
font-family: 'Lato', sans-serif;
font-family: Lato, sans-serif;
font-size: 1em;
}
@@ -157,7 +185,7 @@ button {
select,
input[type='text'],
input[type='number'] {
background: none;
background: $bgColor;
border: 2px solid #aaa;
outline: none;
+22 -24
View File
@@ -1,5 +1,5 @@
export type Vehicle = ILocomotive | ICarWagon;
export type StockSectionMode = "STOCK_LIST" | "STOCK_GENERATOR";
export type StockSectionMode = 'STOCK_LIST' | 'STOCK_GENERATOR';
export interface IStore {
chosenCar: ICarWagon | null;
@@ -28,21 +28,14 @@ export interface IStore {
isRandomizerCardOpen: boolean;
isRealStockListCardOpen: boolean;
stockSectionMode:
| "stock-list"
| "stock-generator"
| "number-generator"
| "wiki-list";
stockSectionMode: 'stock-list' | 'stock-generator' | 'number-generator' | 'wiki-list';
stockData?: IStockData;
lastFocusedElement: HTMLElement | null;
}
export type TStockInfoKey =
| "loco-e"
| "loco-s"
| "loco-ezt"
| "loco-szt"
| "car-passenger"
| "car-cargo";
export type TLocoGroup = 'loco-e' | 'loco-s' | 'loco-ezt' | 'loco-szt';
export type TCarWagonGroup = 'car-passenger' | 'car-cargo';
export interface IStockProps {
type: string;
@@ -62,12 +55,12 @@ export interface IStockData {
};
info: {
"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][];
'car-cargo': [string, string, boolean, number | null, string][];
'car-passenger': [string, string, boolean, number | null, string][];
'loco-e': [string, string, string, string, number | null][];
'loco-s': [string, string, string, string, number | null][];
'loco-szt': [string, string, string, string, number | null][];
'loco-ezt': [string, string, string, string, number | null][];
};
props: IStockProps[];
@@ -77,11 +70,13 @@ export interface IStockData {
export interface ILocomotive {
type: string;
power: string;
power: TLocoGroup;
group: TLocoGroup;
constructionType: string;
cabinType: string;
maxSpeed: number;
supportersOnly: boolean;
isSponsorsOnly: boolean;
sponsorsOnlyTimestamp: number;
imageSrc: string;
mass: number;
@@ -90,10 +85,12 @@ export interface ILocomotive {
export interface ICarWagon {
type: string;
useType: "car-passenger" | "car-cargo";
useType: TCarWagonGroup;
group: TCarWagonGroup;
constructionType: string;
loadable: boolean;
supportersOnly: boolean;
isSponsorsOnly: boolean;
sponsorsOnlyTimestamp: number;
maxSpeed: number;
imageSrc: string;
@@ -117,7 +114,8 @@ export interface IStock {
maxSpeed: number;
cargo?: { id: string; totalMass: number };
isLoco: boolean;
supportersOnly: boolean;
isSponsorsOnly: boolean;
sponsorsOnlyTimestamp: number;
count: number;
imgSrc?: string;
}
+43 -76
View File
@@ -1,10 +1,8 @@
import { EVehicleUseType } from "../enums/EVehicleUseType";
import { ICarWagon, ILocomotive, IStore } from "../types";
import { LocoType, calculateSpeedLimit } from "./speedLimitUtils";
import { EVehicleUseType } from '../enums/EVehicleUseType';
import { ICarWagon, ILocomotive, IStore, TCarWagonGroup, TLocoGroup } from '../types';
import { LocoType, calculateSpeedLimit } from './speedLimitUtils';
export function isLocomotive(
vehicle: ILocomotive | ICarWagon,
): vehicle is ILocomotive {
export function isLocomotive(vehicle: ILocomotive | ICarWagon): vehicle is ILocomotive {
return (vehicle as ILocomotive).power !== undefined;
}
@@ -14,39 +12,29 @@ export function locoDataList(state: IStore) {
const stockData = state.stockData;
return Object.keys(stockData.info).reduce((acc, vehiclePower) => {
if (!vehiclePower.startsWith("loco")) return acc;
if (!vehiclePower.startsWith('loco')) return acc;
const locoVehiclesData =
stockData.info[
vehiclePower as "loco-e" | "loco-s" | "loco-ezt" | "loco-szt"
];
const locoVehiclesData = stockData.info[vehiclePower as TLocoGroup];
locoVehiclesData.forEach((loco) => {
if (state.showSupporter && !loco[4]) return;
const [type, constructionType, cabinType, maxSpeed, supportersOnly] =
loco;
const locoProps = stockData.props.find(
(prop) => constructionType == prop.type,
);
const [type, constructionType, cabinType, maxSpeed, sponsorsTimestamp] = loco;
const locoProps = stockData.props.find((prop) => constructionType == prop.type);
acc.push({
power: vehiclePower,
power: vehiclePower as TLocoGroup,
group: vehiclePower as TLocoGroup,
type,
constructionType,
cabinType,
maxSpeed: Number(maxSpeed),
supportersOnly,
imageSrc: "",
isSponsorsOnly: Number(sponsorsTimestamp) > Date.now(),
sponsorsOnlyTimestamp: Number(sponsorsTimestamp),
imageSrc: '',
length:
locoProps?.length && type.startsWith("2EN")
? locoProps.length * 2
: locoProps?.length || 0,
mass:
locoProps?.mass && type.startsWith("2EN")
? 253
: locoProps?.mass || 0,
length: locoProps?.length && type.startsWith('2EN') ? locoProps.length * 2 : locoProps?.length || 0,
mass: locoProps?.mass && type.startsWith('2EN') ? 253 : locoProps?.mass || 0,
});
});
@@ -60,32 +48,33 @@ export function carDataList(state: IStore) {
const stockData = state.stockData;
return Object.keys(stockData.info).reduce((acc, vehicleUseType) => {
if (!vehicleUseType.startsWith("car")) return acc;
if (!vehicleUseType.startsWith('car')) return acc;
const carVehiclesData =
stockData.info[vehicleUseType as "car-passenger" | "car-cargo"];
const carVehiclesData = stockData.info[vehicleUseType as TCarWagonGroup];
carVehiclesData.forEach((car) => {
if (state.showSupporter && !car[3]) return;
const [type, constructionType, loadable, sponsorsOnlyTimestamp, maxSpeed] = car;
const carPropsData = stockData.props.find((v) =>
car[0].toString().startsWith(v.type),
);
if (state.showSupporter && Number(sponsorsOnlyTimestamp) <= Date.now()) return;
const carPropsData = stockData.props.find((v) => type.toString().startsWith(v.type));
acc.push({
useType: vehicleUseType as "car-passenger" | "car-cargo",
type: car[0],
constructionType: car[1],
loadable: car[2],
supportersOnly: car[3],
maxSpeed: Number(car[4]),
imageSrc: "",
useType: vehicleUseType as TCarWagonGroup,
group: vehicleUseType as TCarWagonGroup,
type,
constructionType,
loadable,
isSponsorsOnly: Number(sponsorsOnlyTimestamp) > Date.now(),
sponsorsOnlyTimestamp: Number(sponsorsOnlyTimestamp),
maxSpeed: Number(maxSpeed),
imageSrc: '',
cargoList:
!carPropsData || carPropsData.cargo === null
? []
: carPropsData.cargo.split(";").map((cargo) => ({
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,
@@ -98,46 +87,28 @@ export function carDataList(state: IStore) {
}
export function totalMass(state: IStore) {
return ~~state.stockList.reduce(
(acc, stock) =>
acc + (stock.cargo ? stock.cargo.totalMass : stock.mass) * stock.count,
0,
);
return ~~state.stockList.reduce((acc, stock) => acc + (stock.cargo ? stock.cargo.totalMass : stock.mass) * stock.count, 0);
}
export function totalLength(state: IStore) {
return state.stockList.reduce(
(acc, stock) => acc + stock.length * stock.count,
0,
);
return state.stockList.reduce((acc, stock) => acc + stock.length * stock.count, 0);
}
export function maxStockSpeed(state: IStore) {
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;
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];
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),
);
const speedLimitByMass = calculateSpeedLimit(locoType as LocoType, stockMass, isTrainPassenger(state));
return speedLimitByMass
? Math.min(stockSpeedLimit, speedLimitByMass)
: stockSpeedLimit;
return speedLimitByMass ? Math.min(stockSpeedLimit, speedLimitByMass) : stockSpeedLimit;
}
export function acceptableMass(state: IStore) {
@@ -168,9 +139,7 @@ export function isTrainPassenger(state: IStore) {
if (state.stockList.length == 0) return false;
if (state.stockList.every((stock) => stock.isLoco)) return false;
return state.stockList
.filter((stock) => !stock.isLoco)
.every((stock) => stock.useType === EVehicleUseType.CAR_PASSENGER);
return state.stockList.filter((stock) => !stock.isLoco).every((stock) => stock.useType === EVehicleUseType.CAR_PASSENGER);
}
export function chosenRealStock(state: IStore) {
@@ -179,11 +148,9 @@ export function chosenRealStock(state: IStore) {
for (let i = 0; i < stock.count; i++) acc.push(stock.type);
return acc;
}, [] as string[])
.join(";");
.join(';');
const realStockObj = state.readyStockList.find(
(readyStock) => readyStock.stockString == currentStockString,
);
const realStockObj = state.readyStockList.find((readyStock) => readyStock.stockString == currentStockString);
state.chosenRealStockName = realStockObj?.stockId ?? undefined;