Compare commits
96 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 76670ceb29 | |||
| 4cdd8ea06a | |||
| 454ae138f7 | |||
| 640c5e262b | |||
| 7416d7d59f | |||
| ca1255b37e | |||
| de0a8520f3 | |||
| e51919896f | |||
| f9cd9a2a33 | |||
| 897d6d0c36 | |||
| b241b60657 | |||
| 1f1bef1cc9 | |||
| 05f8a0ca68 | |||
| 541572e415 | |||
| 61c7f15fcf | |||
| 1da1645c51 | |||
| 8dc670b631 | |||
| 3424f9a952 | |||
| 4079426506 | |||
| 35f2a5ca09 | |||
| b840a6cd46 | |||
| 1aca0f7ed1 | |||
| b009bc03e8 | |||
| baa39a5a99 | |||
| 99cbde3828 | |||
| e72b73ccf0 | |||
| 75e34d9f75 | |||
| 13aa1acc54 | |||
| 966181c977 | |||
| 06eb4bc607 | |||
| c07a16d245 | |||
| 7e67e34526 | |||
| 6cfc535dec | |||
| d072692db7 | |||
| 6255efd98d | |||
| 9c59c30f12 | |||
| 31302cc053 | |||
| 26fd0c67e4 | |||
| d98ec94a66 | |||
| 28485cc28c | |||
| b14c7a9502 | |||
| 90e78e5ac5 | |||
| 27f02e2c2b | |||
| 421ae1db6b | |||
| 90824dc0e5 | |||
| 583c2887e9 | |||
| f915094775 | |||
| d5e735b59e | |||
| 43a724bf13 | |||
| 337425d21c | |||
| 9ff9341851 | |||
| f9276f6c71 | |||
| f4f9a4729f | |||
| ed0906b63e | |||
| 2ae19123a3 | |||
| 0c2be7b927 | |||
| ffce2b572b | |||
| 1b5a26e380 | |||
| c9b681eaee | |||
| 160879adec | |||
| d6ddbe7af7 | |||
| 1c7b72ea1d | |||
| 83f5b07c7e | |||
| 77cb64e25a | |||
| 6d0cc8e7cd | |||
| 7fb4b0ae26 | |||
| ed191d597b | |||
| 5d24accb15 | |||
| 54c6850121 | |||
| fad8c40b4b | |||
| a8485b3531 | |||
| e7dcd125ec | |||
| d886f44c59 | |||
| 5ff6c0504e | |||
| 2158145ae8 | |||
| 6101c9bfb2 | |||
| bd9af9a630 | |||
| 2188dbdf9b | |||
| c4576d7802 | |||
| 86cb4cff4d | |||
| 0534848677 | |||
| c5d116e2db | |||
| 3dce5ec7c0 | |||
| 956ff8afd7 | |||
| 5b629833df | |||
| b53201a7ff | |||
| f0ddd0e27f | |||
| 94bfba2c49 | |||
| 1e4541ef0d | |||
| c25a55a7d9 | |||
| bea3c59405 | |||
| 8cd43adff3 | |||
| 5ab1963117 | |||
| 50d784a0de | |||
| f61135ce6b | |||
| 5e43ece1aa |
@@ -1,18 +1,13 @@
|
||||
/* eslint-env node */
|
||||
require('@rushstack/eslint-patch/modern-module-resolution')
|
||||
require('@rushstack/eslint-patch/modern-module-resolution');
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
'plugin:vue/vue3-essential',
|
||||
'eslint:recommended',
|
||||
'@vue/eslint-config-typescript',
|
||||
'@vue/eslint-config-prettier/skip-formatting'
|
||||
],
|
||||
extends: ['plugin:vue/vue3-essential', 'eslint:recommended', '@vue/eslint-config-typescript', '@vue/eslint-config-prettier/skip-formatting'],
|
||||
rules: {
|
||||
'vue/multi-word-component-names': 'off'
|
||||
'vue/multi-word-component-names': 'off',
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest'
|
||||
}
|
||||
}
|
||||
ecmaVersion: 'latest',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
github-releases-to-discord:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Github Releases To Discord
|
||||
uses: SethCohen/github-releases-to-discord@v1.13.1
|
||||
with:
|
||||
webhook_url: ${{ secrets.WEBHOOK_URL }}
|
||||
color: "15844367"
|
||||
footer_title: "Changelog - Pojazdownik"
|
||||
footer_timestamp: true
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"hosting": {
|
||||
"public": "dist",
|
||||
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
|
||||
"ignore": [],
|
||||
"rewrites": [
|
||||
{
|
||||
"source": "**",
|
||||
@@ -9,4 +9,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
@@ -8,23 +8,20 @@
|
||||
<title>Pojazdownik</title>
|
||||
<meta name="description" content="Edytor pociągów online do symulatora Train Driver 2" />
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon-180x180.png" />
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
|
||||
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
|
||||
<meta name="msapplication-TileColor" content="#da532c" />
|
||||
<meta name="theme-color" content="#e4c428" />
|
||||
<meta name="theme-color" content="#111" />
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>
|
||||
We're sorry but Pojazdownik doesn't work properly without JavaScript enabled. Please enable it to continue.
|
||||
</strong>
|
||||
<strong> We're sorry but Pojazdownik doesn't work properly without JavaScript enabled. Please enable it to continue. </strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"name": "pojazdownik",
|
||||
"version": "1.7.0",
|
||||
"version": "1.8.6",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
@@ -15,20 +16,21 @@
|
||||
"pinia": "^2.0.17",
|
||||
"prettier": "^3.0.3",
|
||||
"vue": "^3.2.37",
|
||||
"vue-i18n": "9"
|
||||
"vue-i18n": "9.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rushstack/eslint-patch": "^1.3.3",
|
||||
"@vitejs/plugin-vue": "^4.1.0",
|
||||
"@vue/eslint-config-prettier": "^8.0.0",
|
||||
"@vite-pwa/assets-generator": "^0.2.3",
|
||||
"@vitejs/plugin-vue": "^5.0.3",
|
||||
"@vue/eslint-config-prettier": "^9.0.0",
|
||||
"@vue/eslint-config-typescript": "^12.0.0",
|
||||
"@vue/tsconfig": "^0.4.0",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"eslint": "^8.49.0",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"sass": "^1.59.3",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.2.1",
|
||||
"vite-plugin-pwa": "^0.14.6",
|
||||
"vite": "^5.0.12",
|
||||
"vite-plugin-pwa": "^0.17.5",
|
||||
"vue-tsc": "^1.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,24 @@
|
||||
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="path-1-inside-1_102_63" fill="white">
|
||||
<path d="M0 250C0 111.929 111.929 6.10352e-05 250 6.10352e-05C388.071 6.10352e-05 500 111.929 500 250C500 388.071 388.071 500 250 500C111.929 500 0 388.071 0 250Z"/>
|
||||
</mask>
|
||||
<path d="M0 250C0 111.929 111.929 6.10352e-05 250 6.10352e-05C388.071 6.10352e-05 500 111.929 500 250C500 388.071 388.071 500 250 500C111.929 500 0 388.071 0 250Z" fill="#242424"/>
|
||||
<path d="M0 222.821C0 84.7503 111.929 -27.1785 250 -27.1785C388.071 -27.1785 500 84.7503 500 222.821V250C500 126.939 388.071 27.1787 250 27.1787C111.929 27.1787 0 126.939 0 250V222.821ZM500 277.179C500 415.25 388.071 527.179 250 527.179C111.929 527.179 0 415.25 0 277.179V250C0 373.061 111.929 472.821 250 472.821C388.071 472.821 500 373.061 500 250V277.179ZM0 500V6.10352e-05V500ZM500 6.10352e-05V500V6.10352e-05Z" fill="#FFD600" mask="url(#path-1-inside-1_102_63)"/>
|
||||
<path d="M210.369 301.604C210.369 301.604 210.369 341.807 210.369 364.846C210.369 387.885 202.798 417.491 171.591 417.491C140.385 417.491 132.813 417.491 132.813 417.491L132.812 78.125L250.754 78.125C274.312 78.125 294.504 80.9665 311.331 86.6494C328.311 92.1788 342.232 99.8585 353.093 109.689C364.107 119.519 372.214 131.115 377.415 144.478C382.616 157.84 385.217 172.278 385.217 187.791C385.217 204.533 382.54 219.892 377.186 233.869C371.832 247.846 363.648 259.827 352.634 269.81C341.62 279.794 327.623 287.627 310.643 293.31C293.816 298.839 273.853 301.604 250.754 301.604L210.369 301.604ZM210.369 242.854L250.754 242.854C270.946 242.854 285.479 238.016 294.351 228.34C303.224 218.663 307.66 205.147 307.66 187.791C307.66 180.111 306.512 173.123 304.218 166.825C301.923 160.528 298.405 155.152 293.663 150.698C289.074 146.09 283.184 142.558 275.995 140.1C268.958 137.643 260.544 136.414 250.754 136.414L210.369 136.414L210.369 242.854Z" fill="url(#paint0_linear_102_63)"/>
|
||||
<path d="M239.215 301.604C239.215 301.604 239.215 341.807 239.215 364.846C239.215 387.885 231.643 417.491 200.437 417.491C169.231 417.491 161.659 417.491 161.659 417.491L161.658 78.125L279.6 78.125C303.158 78.125 323.35 80.9665 340.177 86.6494C357.157 92.1788 371.077 99.8585 381.938 109.689C392.952 119.519 401.06 131.115 406.261 144.478C411.462 157.84 414.062 172.278 414.062 187.791C414.062 204.533 411.385 219.892 406.031 233.869C400.677 247.846 392.493 259.827 381.479 269.81C370.465 279.794 356.468 287.627 339.488 293.31C322.662 298.839 302.699 301.604 279.6 301.604L239.215 301.604ZM239.215 242.854L279.6 242.854C299.792 242.854 314.325 238.016 323.197 228.34C332.069 218.663 336.505 205.147 336.505 187.791C336.505 180.111 335.358 173.123 333.064 166.825C330.769 160.528 327.251 155.152 322.509 150.698C317.919 146.09 312.03 142.558 304.84 140.1C297.804 137.643 289.39 136.414 279.6 136.414L239.215 136.414L239.215 242.854Z" fill="url(#paint1_linear_102_63)"/>
|
||||
<path d="M210.685 301.604C210.685 301.604 210.685 341.807 210.685 364.846C210.685 387.885 203.082 417.491 171.749 417.491C140.416 417.491 132.813 417.491 132.813 417.491L132.812 78.125L251.233 78.125C274.887 78.125 295.161 80.9665 312.057 86.6494C329.105 92.1788 343.083 99.8585 353.988 109.689C365.046 119.519 373.187 131.115 378.409 144.478C383.631 157.84 386.242 172.278 386.242 187.791C386.242 204.533 383.555 219.892 378.179 233.869C372.803 247.846 364.586 259.827 353.527 269.81C342.468 279.794 328.414 287.627 311.365 293.31C294.47 298.839 274.426 301.604 251.233 301.604L210.685 301.604ZM210.685 242.854L251.233 242.854C271.508 242.854 286.099 238.016 295.008 228.34C303.916 218.663 308.37 205.147 308.37 187.791C308.37 180.111 307.218 173.123 304.914 166.825C302.611 160.528 299.078 155.152 294.316 150.698C289.709 146.09 283.795 142.558 276.576 140.1C269.511 137.643 261.063 136.414 251.233 136.414L210.685 136.414L210.685 242.854Z" fill="url(#paint2_radial_102_63)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_102_63" x1="259.015" y1="78.125" x2="259.015" y2="417.491" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.135417" stop-color="#FFD600"/>
|
||||
<stop offset="1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_102_63" x1="287.86" y1="78.125" x2="287.86" y2="417.491" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.135417" stop-color="#FFD600"/>
|
||||
<stop offset="1"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint2_radial_102_63" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(259.527 247.808) rotate(0.36307) scale(345.948 325.206)">
|
||||
<stop offset="0.484375" stop-color="white"/>
|
||||
<stop offset="1"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="18px" height="18px"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
|
||||
|
After Width: | Height: | Size: 256 B |
|
Before Width: | Height: | Size: 1020 B After Width: | Height: | Size: 1020 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 932 B After Width: | Height: | Size: 932 B |
|
Before Width: | Height: | Size: 953 B After Width: | Height: | Size: 953 B |
@@ -5,15 +5,26 @@
|
||||
"description": "Generator składów online dla symulatora Train Driver 2",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"src": "pwa-64x64.png",
|
||||
"sizes": "64x64",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "pwa-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-512x512.png",
|
||||
"src": "pwa-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "maskable-icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"theme_color": "#2c3149",
|
||||
|
||||
|
After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
@@ -5,19 +5,29 @@
|
||||
"description": "Generator składów online dla symulatora Train Driver 2",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"src": "pwa-64x64.png",
|
||||
"sizes": "64x64",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "pwa-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-512x512.png",
|
||||
"src": "pwa-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "maskable-icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"theme_color": "#2c3149",
|
||||
"background_color": "#2c3149",
|
||||
"display": "standalone"
|
||||
}
|
||||
|
||||
|
||||
@@ -12,16 +12,17 @@ import AppContainerView from './views/AppContainerView.vue';
|
||||
import AppModals from './components/app/AppModals.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { ImageFullscreenPreview, AppContainerView, AppModals },
|
||||
data() {
|
||||
return {
|
||||
store: useStore(),
|
||||
};
|
||||
},
|
||||
|
||||
async created() {
|
||||
this.store.fetchStockInfoData();
|
||||
this.store.handleRouting();
|
||||
this.store.setupAPIData();
|
||||
},
|
||||
components: { ImageFullscreenPreview, AppContainerView, AppModals },
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -34,24 +35,14 @@ export default defineComponent({
|
||||
|
||||
color: $textColor;
|
||||
font-size: 1em;
|
||||
padding: 1em 0.5em;
|
||||
}
|
||||
padding: 0;
|
||||
|
||||
/* HEADER SECTION */
|
||||
@media screen and (max-width: $breakpointMd) {
|
||||
font-size: calc(0.7rem + 0.75vw);
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
margin-bottom: 0.5em;
|
||||
|
||||
color: $accentColor;
|
||||
font-weight: 700;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.header-bottom {
|
||||
margin: 0;
|
||||
font-size: 1.5em;
|
||||
|
||||
color: #d1d1d1;
|
||||
@media screen and (orientation: landscape) and (max-width: $breakpointMd) {
|
||||
font-size: calc(0.75rem + 0.4vw);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { useStore } from "../../store";
|
||||
import RealStockCard from "../cards/RealStockCard.vue";
|
||||
import { defineComponent } from 'vue';
|
||||
import { useStore } from '../../store';
|
||||
import RealStockCard from '../cards/RealStockCard.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { RealStockCard },
|
||||
|
||||
@@ -3,35 +3,32 @@
|
||||
<i18n-t keypath="footer.disclaimer" tag="div" class="text--grayed">
|
||||
<template #tos>
|
||||
<a style="color: #ccc" :href="$t('footer.tos-href')" target="_blank">
|
||||
{{ $t("footer.tos") }}
|
||||
{{ $t('footer.tos') }}
|
||||
</a>
|
||||
</template>
|
||||
</i18n-t>
|
||||
|
||||
<div class="text--grayed" v-if="store.stockData">
|
||||
{{ $t("footer.version-check", { version: store.stockData.version }) }}
|
||||
<div class="text--grayed" v-if="store.vehiclesData">
|
||||
{{ $t('footer.version-check', { version: store.vehiclesData.simulatorVersion }) }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
©
|
||||
<a href="https://td2.info.pl/profile/?u=20777" target="_blank"
|
||||
>Spythere</a
|
||||
>
|
||||
{{ new Date().getUTCFullYear() }} | v{{ VERSION
|
||||
}}{{ !isOnProductionHost ? "dev" : "" }}
|
||||
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
|
||||
{{ new Date().getUTCFullYear() }} | v{{ VERSION }}{{ !isOnProductionHost ? 'dev' : '' }}
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import packageInfo from "../../../package.json";
|
||||
import { useStore } from "../../store";
|
||||
import { defineComponent } from 'vue';
|
||||
import packageInfo from '../../../package.json';
|
||||
import { useStore } from '../../store';
|
||||
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
isOnProductionHost: location.hostname == "pojazdownik-td2.web.app",
|
||||
isOnProductionHost: location.hostname == 'pojazdownik-td2.web.app',
|
||||
VERSION: packageInfo.version,
|
||||
store: useStore(),
|
||||
};
|
||||
@@ -41,12 +38,7 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25em;
|
||||
|
||||
text-align: center;
|
||||
padding: 1em 1em 0 1em;
|
||||
margin-top: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import LogoSection from "../sections/LogoSection.vue";
|
||||
import InputsSection from "../sections/InputsSection.vue";
|
||||
import TrainImageSection from "../sections/TrainImageSection.vue";
|
||||
import StockSection from "../sections/StockSection.vue";
|
||||
import { defineComponent } from 'vue';
|
||||
import LogoSection from '../sections/LogoSection.vue';
|
||||
import InputsSection from '../sections/InputsSection.vue';
|
||||
import TrainImageSection from '../sections/TrainImageSection.vue';
|
||||
import StockSection from '../sections/StockSection.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { LogoSection, InputsSection, TrainImageSection, StockSection },
|
||||
@@ -20,26 +20,31 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../styles/global.scss";
|
||||
@import '../../styles/global.scss';
|
||||
|
||||
main {
|
||||
display: grid;
|
||||
gap: 1em;
|
||||
|
||||
width: 100%;
|
||||
max-width: 1300px;
|
||||
min-height: 75vh;
|
||||
max-width: 1350px;
|
||||
|
||||
grid-template-columns: 1fr 2fr;
|
||||
grid-template-rows: auto 360px minmax(400px, 1fr);
|
||||
grid-template-rows: auto 360px minmax(300px, 1fr);
|
||||
|
||||
background-color: darken($color: $bgColor, $amount: 5);
|
||||
border-radius: 1em;
|
||||
|
||||
min-height: 950px;
|
||||
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
@media screen and (max-width: $breakpointMd) {
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
gap: 1em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
<template>
|
||||
<div
|
||||
class="real-stock-card g-card"
|
||||
@keydown.esc="store.isRealStockListCardOpen = false"
|
||||
>
|
||||
<div class="real-stock-card g-card" @keydown.esc="store.isRealStockListCardOpen = false">
|
||||
<div class="g-card_bg" @click="store.isRealStockListCardOpen = false"></div>
|
||||
|
||||
<div class="card_content">
|
||||
<div class="card_nav">
|
||||
<div class="top-pane">
|
||||
<h1>
|
||||
{{ $t("realstock.title") }}
|
||||
<a href="https://td2.info.pl/profile/?u=17708" target="_blank"
|
||||
>Railtrains997</a
|
||||
>
|
||||
{{ $t('realstock.title') }}
|
||||
<a href="https://td2.info.pl/profile/?u=17708" target="_blank">Railtrains997</a>
|
||||
</h1>
|
||||
<button
|
||||
class="btn exit-btn"
|
||||
@click="store.isRealStockListCardOpen = false"
|
||||
>
|
||||
⨯
|
||||
<button class="btn action-exit" @click="store.isRealStockListCardOpen = false">
|
||||
<img src="/images/icon-exit.svg" alt="" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -31,7 +23,7 @@
|
||||
|
||||
<datalist id="readyStockDataList">
|
||||
<option
|
||||
v-for="stock in store.readyStockList"
|
||||
v-for="stock in store.realCompositionList"
|
||||
:value="stock.stockId"
|
||||
:key="stock.name"
|
||||
>
|
||||
@@ -55,32 +47,23 @@
|
||||
</option>
|
||||
</datalist>
|
||||
|
||||
<button class="btn" @click="resetStockFilters">
|
||||
{{ $t("realstock.action-reset") }}
|
||||
<button class="btn action-reset" @click="resetStockFilters">
|
||||
{{ $t('realstock.action-reset') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="card_list" ref="list" @scroll="onListScroll">
|
||||
<li
|
||||
v-for="rStock in computedReadyStockList"
|
||||
:key="rStock.stockId"
|
||||
:data-last-selected="store.chosenRealStockName === rStock.stockId"
|
||||
>
|
||||
<li v-for="rStock in computedReadyStockList" :key="rStock.stockId">
|
||||
<!-- :data-last-selected="store.ch === rStock.stockId" -->
|
||||
<div
|
||||
class="stock-title"
|
||||
tabindex="0"
|
||||
@click="chooseStock(rStock)"
|
||||
@keydown.enter="chooseStock(rStock)"
|
||||
>
|
||||
<img
|
||||
class="stock-icon"
|
||||
:src="getIconURL(rStock.type)"
|
||||
:alt="rStock.type"
|
||||
/>
|
||||
<b class="text--accent" style="margin-left: 5px">
|
||||
{{ rStock.name }}</b
|
||||
>
|
||||
<img class="stock-icon" :src="getIconURL(rStock.type)" :alt="rStock.type" />
|
||||
<b class="text--accent" style="margin-left: 5px"> {{ rStock.name }}</b>
|
||||
<div>{{ rStock.number }}</div>
|
||||
</div>
|
||||
|
||||
@@ -111,24 +94,19 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
import { useStore } from "../../store";
|
||||
import imageMixin from "../../mixins/imageMixin";
|
||||
import stockMixin from "../../mixins/stockMixin";
|
||||
import { useStore } from '../../store';
|
||||
import imageMixin from '../../mixins/imageMixin';
|
||||
import stockMixin from '../../mixins/stockMixin';
|
||||
|
||||
import { IReadyStockItem } from "../../types";
|
||||
import http from "../../http";
|
||||
import { IRealComposition, VehicleGroupType } from '../../types';
|
||||
|
||||
interface ResponseJSONData {
|
||||
[key: string]: string;
|
||||
}
|
||||
function getVehicleType(stockType: string): VehicleGroupType {
|
||||
if (/^E/.test(stockType)) return 'loco-electric';
|
||||
if (/^S/.test(stockType)) return 'loco-diesel';
|
||||
|
||||
function getVehicleType(stockType: string) {
|
||||
if (/^E/.test(stockType)) return "loco-e";
|
||||
if (/^S/.test(stockType)) return "loco-s";
|
||||
|
||||
return "car-passenger";
|
||||
return 'wagon-passenger';
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
@@ -136,55 +114,51 @@ export default defineComponent({
|
||||
|
||||
data: () => ({
|
||||
store: useStore(),
|
||||
responseStatus: "loading",
|
||||
responseStatus: 'loading',
|
||||
isMobile:
|
||||
"ontouchstart" in document.documentElement &&
|
||||
navigator.userAgent.match(/Mobi/)
|
||||
'ontouchstart' in document.documentElement && navigator.userAgent.match(/Mobi/)
|
||||
? true
|
||||
: false,
|
||||
observer: null as IntersectionObserver | null,
|
||||
searchedReadyStockName: "",
|
||||
searchedReadyStockString: "",
|
||||
searchedReadyStockName: '',
|
||||
searchedReadyStockString: '',
|
||||
visibleIndexesTo: 0,
|
||||
lastSelectedStockId: null as string | null,
|
||||
scrollTop: 0,
|
||||
}),
|
||||
|
||||
async mounted() {
|
||||
mounted() {
|
||||
this.mountObserver();
|
||||
this.fetchStockListData();
|
||||
},
|
||||
|
||||
activated() {
|
||||
(this.$refs["focus"] as HTMLElement).focus();
|
||||
(this.$refs['focus'] as HTMLElement).focus();
|
||||
|
||||
(this.$refs["list"] as HTMLElement).scrollTo({
|
||||
(this.$refs['list'] as HTMLElement).scrollTo({
|
||||
top: this.scrollTop,
|
||||
behavior: "auto",
|
||||
behavior: 'auto',
|
||||
});
|
||||
},
|
||||
|
||||
computed: {
|
||||
computedReadyStockList() {
|
||||
if (this.searchedReadyStockName == null) return this.store.readyStockList;
|
||||
|
||||
return this.store.readyStockList
|
||||
computedReadyStockList(): IRealComposition[] {
|
||||
return this.store.realCompositionList
|
||||
.filter(
|
||||
(rs) =>
|
||||
rs.stockId
|
||||
(rc) =>
|
||||
rc.stockId
|
||||
.toLocaleLowerCase()
|
||||
.includes(this.searchedReadyStockName.toLocaleLowerCase()) &&
|
||||
rs.stockString
|
||||
rc.stockString
|
||||
.toLocaleLowerCase()
|
||||
.includes(this.searchedReadyStockString.toLocaleLowerCase()),
|
||||
.includes(this.searchedReadyStockString.toLocaleLowerCase())
|
||||
)
|
||||
.filter((_, i) => i <= this.visibleIndexesTo);
|
||||
},
|
||||
|
||||
computedAvailableStockTypes() {
|
||||
return this.store.readyStockList
|
||||
return this.store.realCompositionList
|
||||
.reduce((acc, rs) => {
|
||||
rs.stockString.split(";").forEach((s) => {
|
||||
rs.stockString.split(';').forEach((s) => {
|
||||
if (!acc.includes(s)) acc.push(s);
|
||||
});
|
||||
|
||||
@@ -198,7 +172,7 @@ export default defineComponent({
|
||||
computedReadyStockList(curr, prev) {
|
||||
if (curr.length < prev.length) {
|
||||
this.visibleIndexesTo = 20;
|
||||
(this.$refs["list"] as HTMLElement).scrollTo({
|
||||
(this.$refs['list'] as HTMLElement).scrollTo({
|
||||
top: 0,
|
||||
});
|
||||
}
|
||||
@@ -206,41 +180,12 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
methods: {
|
||||
async fetchStockListData() {
|
||||
const readyStockJSONData = (
|
||||
await http.get<ResponseJSONData>("td2/data/readyStock.json")
|
||||
).data;
|
||||
|
||||
if (!readyStockJSONData) {
|
||||
this.responseStatus = "error";
|
||||
return;
|
||||
}
|
||||
|
||||
for (let stockKey in readyStockJSONData) {
|
||||
const [type, number, ...name] = stockKey.split(" ");
|
||||
|
||||
const obj = {
|
||||
number: number.replace(/_/g, "/"),
|
||||
name: name.join(" "),
|
||||
stockString: readyStockJSONData[stockKey],
|
||||
type,
|
||||
};
|
||||
|
||||
this.store.readyStockList.push({
|
||||
...obj,
|
||||
stockId: `${obj.type} ${obj.number} ${obj.name}`,
|
||||
});
|
||||
}
|
||||
|
||||
this.responseStatus = "loaded";
|
||||
},
|
||||
|
||||
mountObserver() {
|
||||
this.observer = new IntersectionObserver((entries) => {
|
||||
if (entries[0].intersectionRatio > 0) this.visibleIndexesTo += 20;
|
||||
});
|
||||
|
||||
this.observer.observe(this.$refs["bottom"] as HTMLElement);
|
||||
this.observer.observe(this.$refs['bottom'] as HTMLElement);
|
||||
},
|
||||
|
||||
getImageUrl(name: string) {
|
||||
@@ -248,11 +193,11 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
resetStockFilters() {
|
||||
this.searchedReadyStockName = "";
|
||||
this.searchedReadyStockString = "";
|
||||
this.searchedReadyStockName = '';
|
||||
this.searchedReadyStockString = '';
|
||||
},
|
||||
|
||||
chooseStock(stockItem: IReadyStockItem) {
|
||||
chooseStock(stockItem: IRealComposition) {
|
||||
this.loadStockFromString(stockItem.stockString);
|
||||
this.lastSelectedStockId = stockItem.stockId;
|
||||
this.store.isRealStockListCardOpen = false;
|
||||
@@ -261,7 +206,7 @@ export default defineComponent({
|
||||
onStockItemError(e: Event, stockType: string) {
|
||||
const imageEl = e.target as HTMLImageElement;
|
||||
imageEl.src = `images/${getVehicleType(stockType)}-unknown.png`;
|
||||
imageEl.style.opacity = "1";
|
||||
imageEl.style.opacity = '1';
|
||||
},
|
||||
|
||||
onListScroll(e: Event) {
|
||||
@@ -275,15 +220,17 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../styles/global.scss";
|
||||
@import '../../styles/global.scss';
|
||||
|
||||
.exit-btn {
|
||||
font-size: 1.2em;
|
||||
margin: 0.25em 0;
|
||||
.action-exit {
|
||||
display: flex;
|
||||
background-color: #333;
|
||||
border-radius: 0.25em;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background-color: #444;
|
||||
.action-reset {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
.card_content {
|
||||
@@ -361,7 +308,7 @@ ul {
|
||||
gap: 1rem;
|
||||
padding: 0.1em;
|
||||
|
||||
&[data-last-selected="true"] .stock-title {
|
||||
&[data-last-selected='true'] .stock-title {
|
||||
border: 1px solid $accentColor;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<label>
|
||||
<input type="checkbox" :data-disabled="disabled" :disabled="disabled" v-model="model" />
|
||||
<div><slot /></div>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const model = defineModel();
|
||||
|
||||
defineProps({
|
||||
disabled: Boolean,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
label {
|
||||
text-transform: uppercase;
|
||||
transition: color 200ms;
|
||||
}
|
||||
|
||||
div {
|
||||
padding: 0.25em 0.5em;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
|
||||
background-color: #222;
|
||||
border-radius: 0.25em;
|
||||
|
||||
user-select: none;
|
||||
|
||||
&::before {
|
||||
content: '\2716';
|
||||
margin-right: 0.5em;
|
||||
color: #aaa;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
position: absolute;
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
padding: 0;
|
||||
border: 0;
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
overflow: hidden;
|
||||
|
||||
&:focus-visible + div {
|
||||
outline: 1px solid white;
|
||||
}
|
||||
|
||||
&:checked + div {
|
||||
color: palegreen;
|
||||
|
||||
&::before {
|
||||
color: palegreen;
|
||||
content: '\2714';
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
|
||||
+ div {
|
||||
opacity: 0.55;
|
||||
cursor: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -9,7 +9,7 @@
|
||||
v-for="locoType in locomotiveTypeList"
|
||||
:key="locoType.id"
|
||||
class="btn btn--choice"
|
||||
:data-selected="locoType.id == store.chosenLocoPower"
|
||||
:data-selected="locoType.id == store.chosenLocoGroup"
|
||||
@click="selectLocoType(locoType.id)"
|
||||
>
|
||||
{{ $t(`inputs.${locoType.id}`) }}
|
||||
@@ -27,7 +27,10 @@
|
||||
<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.isSponsorsOnly">*</b></option>
|
||||
<option v-for="loco in locoOptions" :value="loco" :key="loco.type">
|
||||
{{ loco.type
|
||||
}}<b v-if="loco.sponsorOnlyTimestamp && loco.sponsorOnlyTimestamp > Date.now()">*</b>
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -37,7 +40,7 @@
|
||||
v-for="carType in carTypeList"
|
||||
:key="carType.id"
|
||||
class="btn btn--choice"
|
||||
:data-selected="carType.id == store.chosenCarUseType"
|
||||
:data-selected="carType.id == store.chosenCarGroup"
|
||||
@click="selectCarWagonType(carType.id)"
|
||||
>
|
||||
{{ $t(`inputs.${carType.id}`) }}
|
||||
@@ -56,7 +59,10 @@
|
||||
{{ $t('inputs.input-carwagon') }}
|
||||
</option>
|
||||
|
||||
<option v-for="car in carOptions" :value="car" :key="car.type">{{ car.type }}<b v-if="car.isSponsorsOnly">*</b></option>
|
||||
<option v-for="car in carOptions" :value="car" :key="car.type">
|
||||
{{ car.type
|
||||
}}<b v-if="car.sponsorOnlyTimestamp && car.sponsorOnlyTimestamp > Date.now()">*</b>
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -65,7 +71,9 @@
|
||||
<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.group == 'wagon-passenger') ||
|
||||
!store.chosenCar
|
||||
"
|
||||
data-select="cargo"
|
||||
data-ignore-outside="1"
|
||||
@@ -78,9 +86,10 @@
|
||||
<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 v-for="cargo in store.chosenCar?.cargoList" :value="cargo" :key="cargo.id">
|
||||
<option v-for="cargo in store.chosenCar?.cargoTypes" :value="cargo" :key="cargo.id">
|
||||
{{ cargo.id }}
|
||||
</option>
|
||||
</select>
|
||||
@@ -90,7 +99,12 @@
|
||||
<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">
|
||||
<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}.` }}
|
||||
@@ -112,6 +126,7 @@ import imageMixin from '../../mixins/imageMixin';
|
||||
import { useStore } from '../../store';
|
||||
import stockPreviewMixin from '../../mixins/stockPreviewMixin';
|
||||
import stockMixin from '../../mixins/stockMixin';
|
||||
import { LocoGroupType, WagonGroupType } from '../../types';
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [imageMixin, stockPreviewMixin, stockMixin],
|
||||
@@ -120,33 +135,33 @@ export default defineComponent({
|
||||
store: useStore(),
|
||||
locomotiveTypeList: [
|
||||
{
|
||||
id: 'loco-e',
|
||||
id: 'loco-electric',
|
||||
desc: 'ELEKTRYCZNE',
|
||||
},
|
||||
{
|
||||
id: 'loco-s',
|
||||
id: 'loco-diesel',
|
||||
desc: 'SPALINOWE',
|
||||
},
|
||||
{
|
||||
id: 'loco-ezt',
|
||||
id: 'unit-electric',
|
||||
desc: 'ELEKTR. ZESPOŁY TRAKCYJNE',
|
||||
},
|
||||
{
|
||||
id: 'loco-szt',
|
||||
id: 'unit-diesel',
|
||||
desc: 'SPAL. ZESPOŁY TRAKCYJNE',
|
||||
},
|
||||
],
|
||||
] as { id: LocoGroupType; desc: string }[],
|
||||
|
||||
carTypeList: [
|
||||
{
|
||||
id: 'car-passenger',
|
||||
id: 'wagon-passenger',
|
||||
desc: 'PASAŻERSKIE',
|
||||
},
|
||||
{
|
||||
id: 'car-cargo',
|
||||
id: 'wagon-freight',
|
||||
desc: 'TOWAROWE',
|
||||
},
|
||||
],
|
||||
] as { id: WagonGroupType; desc: string }[],
|
||||
}),
|
||||
|
||||
computed: {
|
||||
@@ -154,14 +169,14 @@ export default defineComponent({
|
||||
return this.store.locoDataList
|
||||
.slice()
|
||||
.sort((a, b) => (a.type > b.type ? 1 : -1))
|
||||
.filter((loco) => loco.power == this.store.chosenLocoPower);
|
||||
.filter((loco) => loco.group == this.store.chosenLocoGroup);
|
||||
},
|
||||
|
||||
carOptions() {
|
||||
return this.store.carDataList
|
||||
.slice()
|
||||
.sort((a, b) => (a.type > b.type ? 1 : -1))
|
||||
.filter((car) => car.useType == this.store.chosenCarUseType);
|
||||
.filter((car) => car.group == this.store.chosenCarGroup);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -173,17 +188,15 @@ 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();
|
||||
},
|
||||
|
||||
removeVehicle() {
|
||||
if (this.store.stockList.length == 0) return;
|
||||
|
||||
const lastStock = this.store.stockList.slice(-1)[0];
|
||||
|
||||
if (lastStock.count > 1) lastStock.count--;
|
||||
else this.store.stockList.splice(-1);
|
||||
this.store.stockList.splice(-1);
|
||||
},
|
||||
|
||||
switchVehicles() {
|
||||
@@ -197,14 +210,14 @@ export default defineComponent({
|
||||
this.store.stockList[this.store.chosenStockListIndex] = stockObject;
|
||||
},
|
||||
|
||||
selectLocoType(locoTypeId: string) {
|
||||
this.store.chosenLocoPower = locoTypeId;
|
||||
selectLocoType(locoGroupType: LocoGroupType) {
|
||||
this.store.chosenLocoGroup = locoGroupType;
|
||||
this.store.chosenVehicle = this.locoOptions[0];
|
||||
this.store.chosenLoco = this.locoOptions[0];
|
||||
},
|
||||
|
||||
selectCarWagonType(carWagonTypeId: string) {
|
||||
this.store.chosenCarUseType = carWagonTypeId;
|
||||
selectCarWagonType(wagonGroupType: WagonGroupType) {
|
||||
this.store.chosenCarGroup = wagonGroupType;
|
||||
this.store.chosenVehicle = this.carOptions[0];
|
||||
this.store.chosenCar = this.carOptions[0];
|
||||
this.store.chosenCargo = null;
|
||||
@@ -216,7 +229,10 @@ export default defineComponent({
|
||||
|
||||
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;
|
||||
this.store.chosenCargo =
|
||||
this.store.chosenCar?.cargoTypes.find(
|
||||
(cargo) => cargo.id == this.store.chosenCargo?.id
|
||||
) || null;
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
<template>
|
||||
<section class="logo-section">
|
||||
<img
|
||||
:src="`/logo-${$i18n.locale}.svg`"
|
||||
alt="logo pojazdownik"
|
||||
@click="navigate"
|
||||
/>
|
||||
<img :src="`/logo-${$i18n.locale}.svg`" alt="logo pojazdownik" @click="navigate" />
|
||||
|
||||
<div class="actions">
|
||||
<button
|
||||
@@ -26,36 +22,35 @@ export default {
|
||||
return {
|
||||
localeActions: [
|
||||
{
|
||||
name: "POLSKI",
|
||||
locale: "pl",
|
||||
name: 'POLSKI',
|
||||
locale: 'pl',
|
||||
},
|
||||
{
|
||||
name: "ENGLISH",
|
||||
locale: "en",
|
||||
name: 'ENGLISH',
|
||||
locale: 'en',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
navigate() {
|
||||
window.location.pathname = "";
|
||||
window.location.pathname = '';
|
||||
},
|
||||
|
||||
chooseLocale(locale: string) {
|
||||
this.$i18n.locale = locale;
|
||||
window.localStorage.setItem("locale", locale);
|
||||
window.localStorage.setItem('locale', locale);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../styles/global.scss";
|
||||
@import '../../styles/global.scss';
|
||||
|
||||
.logo-section {
|
||||
grid-row: 1;
|
||||
grid-column: 1;
|
||||
margin-bottom: 1.5em;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -69,7 +64,7 @@ export default {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
|
||||
button[data-selected="true"] {
|
||||
button[data-selected='true'] {
|
||||
font-weight: bold;
|
||||
color: $accentColor;
|
||||
text-decoration: underline;
|
||||
|
||||
@@ -16,22 +16,19 @@
|
||||
|
||||
<transition name="tab-change" mode="out-in">
|
||||
<keep-alive>
|
||||
<component
|
||||
:is="chosenSectionComponent"
|
||||
:key="chosenSectionComponent"
|
||||
></component>
|
||||
<component :is="chosenSectionComponent"></component>
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { useStore } from "../../store";
|
||||
import StockListTab from "../tabs/StockListTab.vue";
|
||||
import StockGeneratorTab from "../tabs/StockGeneratorTab.vue";
|
||||
import NumberGeneratorTab from "../tabs/NumberGeneratorTab.vue";
|
||||
import WikiListTab from "../tabs/WikiListTab.vue";
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useStore } from '../../store';
|
||||
import StockListTab from '../tabs/StockListTab.vue';
|
||||
import StockGeneratorTab from '../tabs/StockGeneratorTab.vue';
|
||||
import NumberGeneratorTab from '../tabs/NumberGeneratorTab.vue';
|
||||
import WikiListTab from '../tabs/WikiListTab.vue';
|
||||
|
||||
const sectionButtonRefs = ref([]);
|
||||
|
||||
@@ -39,36 +36,37 @@ const store = useStore();
|
||||
type SectionMode = typeof store.stockSectionMode;
|
||||
|
||||
const sectionModes: SectionMode[] = [
|
||||
"stock-list",
|
||||
"wiki-list",
|
||||
"number-generator",
|
||||
"stock-generator",
|
||||
'stock-list',
|
||||
'wiki-list',
|
||||
'number-generator',
|
||||
'stock-generator',
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener("keydown", (e) => {
|
||||
window.addEventListener('keydown', (e) => {
|
||||
if (e.target instanceof HTMLInputElement) return;
|
||||
|
||||
if (/[1234]/.test(e.key)) {
|
||||
if (/^[1234]$/.test(e.key)) {
|
||||
const keyNum = Number(e.key);
|
||||
|
||||
store.stockSectionMode = sectionModes[keyNum - 1];
|
||||
(sectionButtonRefs.value[keyNum - 1] as HTMLButtonElement).focus();
|
||||
(sectionButtonRefs.value[keyNum - 1] as HTMLButtonElement)?.focus();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const chosenSectionComponent = computed(() => {
|
||||
switch (store.stockSectionMode) {
|
||||
case "stock-list":
|
||||
case 'stock-list':
|
||||
return StockListTab;
|
||||
|
||||
case "wiki-list":
|
||||
case 'wiki-list':
|
||||
return WikiListTab;
|
||||
|
||||
case "stock-generator":
|
||||
case 'stock-generator':
|
||||
return StockGeneratorTab;
|
||||
|
||||
case "number-generator":
|
||||
case 'number-generator':
|
||||
return NumberGeneratorTab;
|
||||
|
||||
default:
|
||||
@@ -82,7 +80,7 @@ function chooseSection(sectionId: SectionMode) {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../../styles/global.scss";
|
||||
@import '../../styles/global.scss';
|
||||
|
||||
// Tab change animation
|
||||
.tab-change {
|
||||
@@ -124,14 +122,14 @@ function chooseSection(sectionId: SectionMode) {
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
|
||||
content: "";
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 2px;
|
||||
transition: all 100ms;
|
||||
background-color: $accentColor;
|
||||
}
|
||||
|
||||
&[data-selected="true"]::after {
|
||||
&[data-selected='true']::after {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,81 @@
|
||||
<template>
|
||||
<section class="train-image-section">
|
||||
<div class="train-image__content" :class="{ sponsor: store.chosenVehicle?.isSponsorsOnly }">
|
||||
<div v-if="store.chosenVehicle">
|
||||
<img
|
||||
v-if="store.chosenVehicle"
|
||||
tabindex="0"
|
||||
:src="getThumbnailURL(store.chosenVehicle.type, 'small')"
|
||||
:data-preview-active="store.chosenVehicle !== null"
|
||||
:data-sponsor-only="
|
||||
store.chosenVehicle.sponsorOnlyTimestamp &&
|
||||
store.chosenVehicle.sponsorOnlyTimestamp > Date.now()
|
||||
"
|
||||
:data-team-only="store.chosenVehicle.teamOnly"
|
||||
@click="onImageClick"
|
||||
@keydown.enter="onImageClick"
|
||||
@error="onImageError"
|
||||
type="image/jpeg"
|
||||
tabindex="0"
|
||||
/>
|
||||
|
||||
<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> •
|
||||
<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>
|
||||
<div class="image-info">
|
||||
<b class="text--accent">{{ store.chosenVehicle.type }}</b> •
|
||||
<b style="color: #ccc">
|
||||
{{
|
||||
store.chosenVehicle.useType == 'car-cargo'
|
||||
? $t(`usage.${store.chosenVehicle.constructionType}`)
|
||||
: `${$t('preview.construction')} ${store.chosenVehicle.constructionType}`
|
||||
$t(
|
||||
`preview.${isTractionUnit(store.chosenVehicle) ? store.chosenVehicle.group : store.chosenVehicle.group}`
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</b>
|
||||
|
||||
<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 style="color: #ccc">
|
||||
<div>
|
||||
{{ store.chosenVehicle.length }}m |
|
||||
{{ (store.chosenVehicle.weight / 1000).toFixed(1) }}t |
|
||||
{{ store.chosenVehicle.maxSpeed }} km/h
|
||||
</div>
|
||||
|
||||
<div v-if="isTractionUnit(store.chosenVehicle)">
|
||||
{{ $t('preview.cabin') }} {{ store.chosenVehicle.cabinType }}
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
{{
|
||||
store.chosenVehicle.group == 'wagon-freight'
|
||||
? $t(`usage.${store.chosenVehicle.constructionType}`)
|
||||
: `${$t('preview.construction')} ${store.chosenVehicle.constructionType}`
|
||||
}}
|
||||
</div>
|
||||
|
||||
<b
|
||||
v-if="
|
||||
store.chosenVehicle.sponsorOnlyTimestamp &&
|
||||
store.chosenVehicle.sponsorOnlyTimestamp > Date.now()
|
||||
"
|
||||
class="sponsor-only"
|
||||
>
|
||||
{{
|
||||
$t('preview.sponsor-only', [
|
||||
new Date(store.chosenVehicle.sponsorOnlyTimestamp).toLocaleDateString(
|
||||
$i18n.locale == 'pl' ? 'pl-PL' : 'en-GB'
|
||||
),
|
||||
])
|
||||
}}
|
||||
</b>
|
||||
|
||||
<b v-if="store.chosenVehicle.teamOnly" class="team-only">{{ $t('preview.team-only') }}</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="train-image__info" v-else>{{ $t('preview.desc') }}</div>
|
||||
<div v-else>
|
||||
<img src="/images/placeholder.jpg" alt="placeholder image" />
|
||||
<div class="image-info">{{ $t('preview.desc') }}</div>
|
||||
</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 { isTractionUnit } from '../../utils/vehicleUtils';
|
||||
import imageMixin from '../../mixins/imageMixin';
|
||||
|
||||
export default defineComponent({
|
||||
@@ -70,18 +96,8 @@ export default defineComponent({
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
chosenVehicle(vehicle: Vehicle, prevVehicle: Vehicle) {
|
||||
if (vehicle && vehicle.type != prevVehicle?.type) {
|
||||
this.store.imageLoading = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onImageLoad() {
|
||||
this.store.imageLoading = false;
|
||||
},
|
||||
isTractionUnit,
|
||||
|
||||
onImageError(e: Event) {
|
||||
const el = e.target as HTMLImageElement;
|
||||
@@ -90,10 +106,6 @@ export default defineComponent({
|
||||
el.src = '/images/placeholder.jpg';
|
||||
},
|
||||
|
||||
isLocomotive(vehicle: Vehicle): vehicle is ILocomotive {
|
||||
return isLocomotive(vehicle);
|
||||
},
|
||||
|
||||
onImageClick(e: Event) {
|
||||
const target = e.target as HTMLElement;
|
||||
|
||||
@@ -109,41 +121,57 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/global.scss';
|
||||
@import '../../styles/global';
|
||||
|
||||
.train-image-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
|
||||
grid-row: 3;
|
||||
grid-column: 1;
|
||||
min-height: 250px;
|
||||
|
||||
margin-top: 1em;
|
||||
height: 22em;
|
||||
}
|
||||
|
||||
.train-image {
|
||||
&__content {
|
||||
&.sponsor img {
|
||||
border: 1px solid salmon;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 380px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid white;
|
||||
|
||||
cursor: zoom-in;
|
||||
}
|
||||
& > div {
|
||||
max-width: 100%;
|
||||
width: 380px;
|
||||
}
|
||||
}
|
||||
|
||||
.train-image__info {
|
||||
img {
|
||||
width: 100%;
|
||||
|
||||
&[data-preview-active='true'] {
|
||||
border: 1px solid white;
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
&[data-sponsor-only='true'] {
|
||||
border: 1px solid $sponsorColor;
|
||||
}
|
||||
|
||||
&[data-team-only='true'] {
|
||||
border: 1px solid $teamColor;
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
height: 250px;
|
||||
|
||||
background-color: $bgColor;
|
||||
}
|
||||
|
||||
.sponsor-only {
|
||||
color: $sponsorColor;
|
||||
}
|
||||
|
||||
.team-only {
|
||||
color: $teamColor;
|
||||
}
|
||||
|
||||
.image-info {
|
||||
font-size: 1.1em;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em auto;
|
||||
// margin: 0.5em auto;
|
||||
line-height: 1.35;
|
||||
|
||||
width: 100%;
|
||||
@@ -153,19 +181,6 @@ export default defineComponent({
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
// Transition animations
|
||||
.img-message-anim {
|
||||
&-enter-from,
|
||||
&-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&-enter-active,
|
||||
&-leave-active {
|
||||
transition: opacity 75ms ease-in 100ms;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $breakpointMd) {
|
||||
.train-image-section {
|
||||
justify-content: center;
|
||||
|
||||
@@ -1,77 +1,115 @@
|
||||
<template>
|
||||
<div class="number-generator tab">
|
||||
<div class="tab_header">
|
||||
<h2>{{ $t("numgen.title") }}</h2>
|
||||
<h2>{{ $t('numgen.title') }}</h2>
|
||||
<h3>{{ $t('numgen.subtitle') }}</h3>
|
||||
</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"
|
||||
:key="category"
|
||||
:value="category"
|
||||
>
|
||||
{{ $t(`numgen.categories.${category}`) }}
|
||||
</option>
|
||||
</select>
|
||||
<div class="inputs">
|
||||
<label>
|
||||
<span>{{ $t('numgen.train-category') }}</span>
|
||||
<select v-model="chosenCategory" @change="randomizeTrainNumber()">
|
||||
<option :value="null" disabled>
|
||||
{{ $t('numgen.train-category') }}
|
||||
</option>
|
||||
<option
|
||||
v-for="(_, category) in genData.categoriesRules"
|
||||
:key="category"
|
||||
:value="category"
|
||||
>
|
||||
{{ $t(`numgen.categories.${category}`) }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<select v-model="beginRegionName" @change="randomizeTrainNumber()">
|
||||
<option :value="null" disabled>
|
||||
{{ $t("numgen.start-region") }}
|
||||
</option>
|
||||
<option
|
||||
v-for="(_, name) in genData.regionNumbers"
|
||||
:key="name"
|
||||
:value="name"
|
||||
>
|
||||
{{ name }}
|
||||
</option>
|
||||
</select>
|
||||
<label>
|
||||
<span>{{ $t('numgen.start-region') }}</span>
|
||||
<select v-model="beginRegionName" @change="randomizeTrainNumber()">
|
||||
<option :value="null" disabled>
|
||||
{{ $t('numgen.start-region') }}
|
||||
</option>
|
||||
<option v-for="(_, name) in genData.regionNumbers" :key="name" :value="name">
|
||||
{{ name }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<select v-model="endRegionName" @change="randomizeTrainNumber()">
|
||||
<option :value="null" disabled>{{ $t("numgen.end-region") }}</option>
|
||||
<option
|
||||
v-for="(_, name) in genData.regionNumbers"
|
||||
:key="name"
|
||||
:value="name"
|
||||
>
|
||||
{{ name }}
|
||||
</option>
|
||||
</select>
|
||||
<label>
|
||||
<span> {{ $t('numgen.end-region') }}</span>
|
||||
<select v-model="endRegionName" @change="randomizeTrainNumber()">
|
||||
<option :value="null" disabled>
|
||||
{{ $t('numgen.end-region') }}
|
||||
</option>
|
||||
<option v-for="(_, name) in genData.regionNumbers" :key="name" :value="name">
|
||||
{{ name }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="generated-number" @click="copyNumber">
|
||||
<span v-if="trainNumber">
|
||||
{{ $t("numgen.number-info") }}
|
||||
{{ $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">
|
||||
Current numbering rules: {{ $t(`numgen.rules.${chosenCategory}`) }};
|
||||
<div class="category-rules" v-if="chosenCategory && categoryRules && trainNumber">
|
||||
<!-- First & second digit (the same regions) -->
|
||||
<div v-if="beginRegionName && endRegionName && beginRegionName == endRegionName">
|
||||
<b>{{ $t('numgen.rules.two-first-digits') }}</b>
|
||||
{{ $t('numgen.rules.from-pool') }}
|
||||
<b class="text--accent">{{ genData.sameRegions[beginRegionName].join(', ') }}</b>
|
||||
{{ $t('numgen.rules.for-region') }} {{ beginRegionName }}
|
||||
</div>
|
||||
|
||||
<span v-if="beginRegionName && endRegionName">
|
||||
<span v-if="beginRegionName == endRegionName">
|
||||
pierwsze dwie cyfry:
|
||||
{{ genData.sameRegions[beginRegionName].join(', ') }}
|
||||
(numer w obrębie obszaru {{ beginRegionName }})
|
||||
</span>
|
||||
<!-- First & second digit (different regions) -->
|
||||
<div v-else>
|
||||
<div>
|
||||
<b>
|
||||
{{ $t('numgen.rules.first-digit') }}
|
||||
<span class="text--accent">{{ trainNumber[0] }}</span>
|
||||
</b>
|
||||
{{ $t('numgen.rules.for-region-begin') }} {{ beginRegionName }}
|
||||
</div>
|
||||
|
||||
<span v-else>
|
||||
pierwsza cyfra: {{ genData.regionNumbers[beginRegionName] }}; druga cyfra:
|
||||
{{ genData.regionNumbers[endRegionName] }}
|
||||
</span>
|
||||
</span>
|
||||
</div> -->
|
||||
<div>
|
||||
<b>
|
||||
{{ $t('numgen.rules.second-digit') }}
|
||||
<span class="text--accent">{{ trainNumber[1] }} </span>
|
||||
</b>
|
||||
{{ $t('numgen.rules.for-region-end') }} {{ endRegionName }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Third digit (non-passenger only) -->
|
||||
<div v-if="categoryRules[0] != null">
|
||||
<b>
|
||||
{{ $t('numgen.rules.third-digit') }}
|
||||
<span class="text--accent">{{ categoryRules[0] }}</span>
|
||||
</b>
|
||||
{{ $t('numgen.rules.for-category') }} {{ chosenCategory }}
|
||||
</div>
|
||||
|
||||
<!-- Last digits -->
|
||||
<div>
|
||||
<b>
|
||||
{{
|
||||
$t(`numgen.rules.${categoryRules[1]?.length == 3 ? 'three' : 'two'}-last-digits`)
|
||||
}}</b
|
||||
>
|
||||
{{ $t('numgen.rules.from-range') }}
|
||||
<b class="text--accent">{{ categoryRules[1] }}-{{ categoryRules[2] }}</b>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="tab_links">
|
||||
<a :href="$t('numgen.td2-wiki-link')" target="_blank">
|
||||
{{ $t("numgen.td2-wiki") }}
|
||||
{{ $t('numgen.td2-wiki') }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -79,10 +117,15 @@
|
||||
|
||||
<div class="tab_actions">
|
||||
<button class="btn" @click="randomizeTrainNumber(true)">
|
||||
{{ $t("numgen.action-random-region") }}
|
||||
{{ $t('numgen.action-random-region') }}
|
||||
</button>
|
||||
|
||||
<button class="btn" @click="randomizeCategory">
|
||||
{{ $t('numgen.action-random-category') }}
|
||||
</button>
|
||||
|
||||
<button class="btn" @click="randomizeTrainNumber(false)">
|
||||
{{ $t("numgen.action-random-number") }}
|
||||
{{ $t('numgen.action-random-number') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -90,14 +133,15 @@
|
||||
</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';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const i18n = useI18n();
|
||||
type RegionName = keyof typeof genData.regionNumbers;
|
||||
type Category = keyof typeof genData.categories;
|
||||
type Category = keyof typeof genData.categoriesRules;
|
||||
|
||||
const beginRegionName = ref(null) as Ref<RegionName | null>;
|
||||
const endRegionName = ref(null) as Ref<RegionName | null>;
|
||||
@@ -108,27 +152,37 @@ 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'));
|
||||
}
|
||||
};
|
||||
|
||||
const categoryRules = computed(() => {
|
||||
if (!chosenCategory.value) return null;
|
||||
|
||||
return genData.categoriesRules[chosenCategory.value];
|
||||
});
|
||||
|
||||
const randomizeCategory = () => {
|
||||
const categoryKeys = Object.keys(genData.categoriesRules) as Category[];
|
||||
chosenCategory.value = categoryKeys[~~(Math.random() * categoryKeys.length)];
|
||||
|
||||
randomizeTrainNumber(false);
|
||||
};
|
||||
|
||||
const randomizeTrainNumber = (randomizeRegions = false) => {
|
||||
// if (categoryRules.value == null) return;
|
||||
|
||||
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 = '';
|
||||
|
||||
// Two first numbers (begin & end regions)
|
||||
if (beginRegionName.value == endRegionName.value) {
|
||||
const sameRegionsNumbers = genData.sameRegions[beginRegionName.value!];
|
||||
const randRegionNumber =
|
||||
@@ -147,48 +201,57 @@ const randomizeTrainNumber = (randomizeRegions = false) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (chosenCategory.value == null) chosenCategory.value = "EI";
|
||||
// Choose default category if it's not chosen
|
||||
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"),
|
||||
}));
|
||||
// Get category rules
|
||||
const [thirdNumber, minRange, maxRange] = categoryRules.value!;
|
||||
|
||||
rulesArray.forEach((r) => {
|
||||
const range = r.rule.split("-");
|
||||
// Third number
|
||||
number += thirdNumber ?? '';
|
||||
|
||||
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();
|
||||
// Remaining numbers
|
||||
const rangeNums = minRange!.length;
|
||||
const randRange = Math.floor(
|
||||
Math.random() * (Number(maxRange) - Number(minRange)) + Number(minRange)
|
||||
).toString();
|
||||
const leadingZeros = new Array(Math.abs(randRange.length - rangeNums)).fill('0').join('');
|
||||
|
||||
number +=
|
||||
new Array(Math.abs(randRange.length - r.nums)).fill("0").join("") +
|
||||
randRange;
|
||||
}
|
||||
});
|
||||
number += `${leadingZeros}${randRange}`;
|
||||
|
||||
trainNumber.value = number;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../styles/tab.scss";
|
||||
@import "../../styles/global.scss";
|
||||
|
||||
.options {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 0.5em;
|
||||
@import '../../styles/tab.scss';
|
||||
@import '../../styles/global.scss';
|
||||
|
||||
.category-select {
|
||||
select {
|
||||
width: 100%;
|
||||
width: auto;
|
||||
min-width: 50%;
|
||||
}
|
||||
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.inputs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(15em, 1fr));
|
||||
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.inputs > label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25em;
|
||||
|
||||
span {
|
||||
color: #ccc;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,14 +267,19 @@ const randomizeTrainNumber = (randomizeRegions = false) => {
|
||||
background-color: $secondaryColor;
|
||||
}
|
||||
|
||||
.category-rules {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.tab_actions {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.tab_links {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin: 0.25em 0;
|
||||
}
|
||||
|
||||
@media screen and (max-width: $breakpointMd) {
|
||||
@@ -221,8 +289,20 @@ const randomizeTrainNumber = (randomizeRegions = false) => {
|
||||
}
|
||||
|
||||
@media screen and (max-width: $breakpointSm) {
|
||||
.options select {
|
||||
.regions-select {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.regions-select select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.category-select select {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.category-rules {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -12,22 +12,28 @@
|
||||
{{ $t('stockgen.properties-desc') }}
|
||||
</b>
|
||||
|
||||
<div class="tab_attributes">
|
||||
<div class="inputs">
|
||||
<label>
|
||||
{{ $t('stockgen.input-mass') }}
|
||||
<input type="number" v-model="maxMass" step="100" max="4000" min="0" />
|
||||
<span>{{ $t('stockgen.input-mass') }}</span>
|
||||
<input type="number" v-model="maxTons" step="100" max="4000" min="0" />
|
||||
</label>
|
||||
|
||||
<label>
|
||||
{{ $t('stockgen.input-length') }}
|
||||
<span>{{ $t('stockgen.input-length') }}</span>
|
||||
<input type="number" v-model="maxLength" step="25" max="650" min="0" />
|
||||
</label>
|
||||
|
||||
<label>
|
||||
{{ $t('stockgen.input-carcount') }}
|
||||
<span>{{ $t('stockgen.input-carcount') }}</span>
|
||||
<input type="number" v-model="maxCarCount" step="1" max="60" min="1" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- <hr style="margin: 1em 0" /> -->
|
||||
|
||||
<!-- <div class="generator_options">
|
||||
<Checkbox v-model="isCarGroupingEnabled">Grupuj wylosowane wagony (ustawia podobne wagony obok siebie w składzie)</Checkbox>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -37,13 +43,13 @@
|
||||
|
||||
<div class="generator_cargo">
|
||||
<button
|
||||
v-for="(cargoArray, cargoName) in store.stockData?.generator.cargo"
|
||||
:key="cargoName"
|
||||
v-for="cargo in computedCargoData"
|
||||
:key="cargo.name"
|
||||
class="btn"
|
||||
:data-chosen="chosenCargoTypes.includes(cargoName.toString())"
|
||||
@click="toggleCargoChosen(cargoName.toString(), cargoArray)"
|
||||
:data-chosen="chosenCargoTypes.includes(cargo.name)"
|
||||
@click="toggleCargoChosen(cargo.name, cargo.cargoList)"
|
||||
>
|
||||
{{ $t(`cargo.${cargoName}`) }}
|
||||
{{ $t(`cargo.${cargo.name}`) }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -79,15 +85,27 @@
|
||||
<hr />
|
||||
|
||||
<div class="tab_actions">
|
||||
<button class="btn" :data-disabled="computedChosenCarTypes.size == 0" @click="generateStock()">
|
||||
<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)">
|
||||
<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">
|
||||
<button
|
||||
class="btn"
|
||||
:data-disabled="computedChosenCarTypes.size == 0"
|
||||
@click="resetChosenCargo"
|
||||
>
|
||||
{{ $t('stockgen.action-reset') }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -101,12 +119,11 @@ import { useStore } from '../../store';
|
||||
|
||||
import stockMixin from '../../mixins/stockMixin';
|
||||
import { ICargo, ICarWagon, IStock } from '../../types';
|
||||
import warningsMixin from '../../mixins/warningsMixin';
|
||||
import { isTractionUnit } from '../../utils/vehicleUtils';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'stock-generator',
|
||||
|
||||
mixins: [stockMixin, warningsMixin],
|
||||
mixins: [stockMixin],
|
||||
|
||||
data() {
|
||||
return {
|
||||
@@ -117,10 +134,12 @@ export default defineComponent({
|
||||
|
||||
previewTimeout: -1,
|
||||
|
||||
maxMass: 3000,
|
||||
maxTons: 3000,
|
||||
maxLength: 650,
|
||||
maxCarCount: 50,
|
||||
|
||||
isCarGroupingEnabled: false,
|
||||
|
||||
store: useStore(),
|
||||
};
|
||||
},
|
||||
@@ -129,6 +148,19 @@ export default defineComponent({
|
||||
computedChosenCarTypes() {
|
||||
return new Set<string>(this.chosenCarTypes.slice().sort((c1, c2) => (c1 > c2 ? 1 : -1)));
|
||||
},
|
||||
|
||||
computedCargoData() {
|
||||
if (!this.store.vehiclesData?.generator.cargo) return [];
|
||||
|
||||
const cargoGeneratorData = this.store.vehiclesData.generator.cargo;
|
||||
|
||||
return Object.keys(cargoGeneratorData)
|
||||
.sort((v1, v2) => this.$t(`cargo.${v1}`).localeCompare(this.$t(`cargo.${v2}`)))
|
||||
.map((v) => ({
|
||||
name: v,
|
||||
cargoList: cargoGeneratorData[v],
|
||||
}));
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
@@ -150,10 +182,21 @@ export default defineComponent({
|
||||
this.excludedCarTypes.length = 0;
|
||||
},
|
||||
|
||||
// WIP
|
||||
groupStock(stockList: IStock[]) {
|
||||
if (!this.isCarGroupingEnabled) return false;
|
||||
|
||||
stockList.sort((s1, s2) => {
|
||||
return (s1.vehicleRef.constructionType + s1.cargo?.id).localeCompare(
|
||||
s2.vehicleRef.constructionType + s2.cargo?.id
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
generateStock(empty = false) {
|
||||
const generatedChosenStockList = this.chosenCargoTypes.reduce(
|
||||
(acc, type) => {
|
||||
this.store.stockData?.generator.cargo[type]
|
||||
this.store.vehiclesData?.generator.cargo[type]
|
||||
.filter((c) => !this.excludedCarTypes.includes(c.split(':')[0]))
|
||||
.forEach((c) => {
|
||||
const [type, cargoType] = c.split(':');
|
||||
@@ -162,12 +205,15 @@ export default defineComponent({
|
||||
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));
|
||||
else if (cargoType == 'all') cargoObjs.push(...carWagonObjs[0]!.cargoTypes);
|
||||
else
|
||||
cargoObjs.push(carWagonObjs[0]?.cargoTypes.find((cargo) => cargo.id == cargoType));
|
||||
|
||||
carWagonObjs.forEach((cw) => {
|
||||
cargoObjs.forEach((cargoObj) => {
|
||||
const chosenStock = acc.find((a) => a.constructionType.includes(cw.constructionType));
|
||||
const chosenStock = acc.find((a) =>
|
||||
a.constructionType.includes(cw.constructionType)
|
||||
);
|
||||
|
||||
if (!chosenStock)
|
||||
acc.push({
|
||||
@@ -193,30 +239,38 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const headingLoco = this.store.stockList[0]?.isLoco ? this.store.stockList[0] : undefined;
|
||||
this.store.stockList.length = headingLoco ? 1 : 0;
|
||||
this.store.stockList.splice(
|
||||
this.store.stockList.length > 0 && isTractionUnit(this.store.stockList[0].vehicleRef)
|
||||
? 1
|
||||
: 0
|
||||
);
|
||||
|
||||
const maxMass = this.store.acceptableMass > 0 ? Math.min(this.store.acceptableMass, this.maxMass) : this.maxMass;
|
||||
let carCount = 0;
|
||||
const maxWeight =
|
||||
this.store.acceptableWeight > 0
|
||||
? Math.min(this.store.acceptableWeight, this.maxTons * 1000)
|
||||
: this.maxTons * 1000;
|
||||
|
||||
let exceeded = false;
|
||||
|
||||
while (!exceeded) {
|
||||
const randomStockType = generatedChosenStockList[~~(Math.random() * generatedChosenStockList.length)];
|
||||
const { carWagon, cargo } = randomStockType.carPool[~~(Math.random() * randomStockType.carPool.length)];
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
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.totalWeight + (carWagon.weight + (cargo?.weight ?? 0)) > maxWeight ||
|
||||
this.store.totalLength + carWagon.length > this.maxLength ||
|
||||
this.store.stockList.length > this.maxCarCount
|
||||
carCount >= this.maxCarCount
|
||||
) {
|
||||
exceeded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
this.addCarWagon(carWagon, cargo);
|
||||
carCount++;
|
||||
}
|
||||
|
||||
const currentGenerationValue = this.store.totalLength + this.store.totalMass + this.store.stockList.length;
|
||||
const currentGenerationValue = this.store.totalLength + this.store.totalWeight + carCount;
|
||||
|
||||
if (bestGeneration.value < currentGenerationValue) {
|
||||
bestGeneration.stockList = this.store.stockList;
|
||||
@@ -224,6 +278,10 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
const bestStockList = bestGeneration.stockList;
|
||||
|
||||
this.groupStock(bestStockList);
|
||||
|
||||
this.store.stockList = bestGeneration.stockList;
|
||||
this.store.stockSectionMode = 'stock-list';
|
||||
},
|
||||
@@ -236,7 +294,7 @@ export default defineComponent({
|
||||
this.store.chosenLoco = null;
|
||||
this.store.chosenCargo = null;
|
||||
|
||||
if (c) this.store.chosenCarUseType = c?.useType;
|
||||
if (c) this.store.chosenCarGroup = c?.group;
|
||||
},
|
||||
|
||||
toggleCargoChosen(cargoType: string, vehicles: string[]) {
|
||||
@@ -270,6 +328,11 @@ export default defineComponent({
|
||||
@import '../../styles/global.scss';
|
||||
@import '../../styles/tab.scss';
|
||||
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.generator_cargo,
|
||||
.generator_vehicles {
|
||||
display: grid;
|
||||
@@ -310,6 +373,34 @@ export default defineComponent({
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.inputs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(12em, 1fr));
|
||||
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.inputs > label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25em;
|
||||
|
||||
span {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.generator_options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.generator_warning {
|
||||
background-color: $accentColor;
|
||||
padding: 0.5em;
|
||||
|
||||
@@ -4,152 +4,226 @@
|
||||
<h2>{{ $t('stocklist.title') }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="stock_actions">
|
||||
<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>
|
||||
<div class="tab_content">
|
||||
<div class="stock_actions">
|
||||
<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>
|
||||
|
||||
<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') }}
|
||||
</button>
|
||||
|
||||
<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') }}
|
||||
</button>
|
||||
|
||||
<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') }}
|
||||
</button>
|
||||
|
||||
<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') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
</b>
|
||||
|
||||
<b v-else>
|
||||
{{ $t('stocklist.no-vehicle-chosen') }}
|
||||
</b>
|
||||
|
||||
<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') }}
|
||||
</button>
|
||||
|
||||
<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') }}
|
||||
</button>
|
||||
|
||||
<button class="btn" :tabindex="store.chosenStockListIndex == -1 ? -1 : 0" @click="removeStock(store.chosenStockListIndex)">
|
||||
<img :src="getIconURL('remove')" alt="remove vehicle" />
|
||||
{{ $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" />
|
||||
{{ 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') }}:
|
||||
<span class="text--accent">{{ store.totalLength }}m</span>
|
||||
- {{ $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') }}
|
||||
</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="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="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>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<div class="warning" v-if="tooManyLocomotives">
|
||||
{{ $t('stocklist.warning-too-many-locos') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<StockThumbnails :onListItemClick="onListItemClick" />
|
||||
|
||||
<!-- Stock list -->
|
||||
<ul ref="stock_list">
|
||||
<li v-if="stockIsEmpty" class="list-empty">
|
||||
<div class="stock-info">{{ $t('stocklist.list-empty') }}</div>
|
||||
</li>
|
||||
|
||||
<TransitionGroup name="stock-list-anim">
|
||||
<li
|
||||
v-for="(stock, i) in store.stockList"
|
||||
:key="stock.id"
|
||||
:class="{ loco: stock.isLoco }"
|
||||
tabindex="0"
|
||||
@click="onListItemClick(i)"
|
||||
@keydown.enter="onListItemClick(i)"
|
||||
@keydown.w="moveUpStock(i)"
|
||||
@keydown.s="moveDownStock(i)"
|
||||
@keydown.backspace="removeStock(i)"
|
||||
ref="itemRefs"
|
||||
<button
|
||||
class="btn btn--image"
|
||||
:data-disabled="stockIsEmpty"
|
||||
:disabled="stockIsEmpty"
|
||||
@click="downloadStock"
|
||||
>
|
||||
<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">• </span>
|
||||
{{ i + 1 }}.
|
||||
</span>
|
||||
<img src="/images/icon-download.svg" alt="download icon" />
|
||||
{{ $t('stocklist.action-download') }}
|
||||
</button>
|
||||
|
||||
<span class="stock-info__type" :class="{ sponsor: stock.isSponsorsOnly }">
|
||||
{{ stock.isLoco ? stock.type : getCarSpecFromType(stock.type) }}
|
||||
</span>
|
||||
<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') }}
|
||||
</button>
|
||||
|
||||
<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__speed"> {{ stock.maxSpeed }}km/h </span>
|
||||
</div>
|
||||
</li>
|
||||
</TransitionGroup>
|
||||
</ul>
|
||||
<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') }}
|
||||
</button>
|
||||
|
||||
<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') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="stock_controls" :data-disabled="store.chosenStockListIndex == -1">
|
||||
<button
|
||||
class="btn btn--image"
|
||||
:tabindex="store.chosenStockListIndex == -1 ? -1 : 0"
|
||||
@click="moveUpStock(store.chosenStockListIndex)"
|
||||
>
|
||||
<img :src="getIconURL('higher')" alt="move up vehicle" />
|
||||
{{ $t('stocklist.action-move-up') }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn--image"
|
||||
:tabindex="store.chosenStockListIndex == -1 ? -1 : 0"
|
||||
@click="moveDownStock(store.chosenStockListIndex)"
|
||||
>
|
||||
<img :src="getIconURL('lower')" alt="move down vehicle" />
|
||||
{{ $t('stocklist.action-move-down') }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn--image"
|
||||
:tabindex="store.chosenStockListIndex == -1 ? -1 : 0"
|
||||
@click="removeStock(store.chosenStockListIndex)"
|
||||
>
|
||||
<img :src="getIconURL('remove')" alt="remove vehicle" />
|
||||
{{ $t('stocklist.action-remove') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="stock_specs">
|
||||
<b class="real-stock-info" v-if="chosenRealComposition">
|
||||
<span class="text--accent">
|
||||
<img :src="getIconURL(chosenRealComposition.type)" :alt="chosenRealComposition.type" />
|
||||
{{ chosenRealComposition.number }} {{ chosenRealComposition.name }}
|
||||
</span>
|
||||
|
|
||||
</b>
|
||||
|
||||
<span>
|
||||
{{ $t('stocklist.mass') }}
|
||||
<span class="text--accent">{{ (store.totalWeight / 1000).toFixed(1) }}t</span>
|
||||
({{ $t('stocklist.mass-accepted') }}:
|
||||
<span class="text--accent">{{
|
||||
store.acceptableWeight ? `${~~(store.acceptableWeight / 1000)}t` : '-'
|
||||
}}</span
|
||||
>) - {{ $t('stocklist.length') }}:
|
||||
<span class="text--accent">{{ store.totalLength }}m</span>
|
||||
- {{ $t('stocklist.vmax') }}
|
||||
<span tabindex="0" :data-tooltip="$t('stocklist.disclaimer')">(?)</span>:
|
||||
<span class="text--accent">{{ store.maxStockSpeed }} km/h</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div></div>
|
||||
|
||||
<div class="stock_spawn-settings">
|
||||
<Checkbox :disabled="!store.stockSupportsColdStart" v-model="store.isColdStart">
|
||||
{{ $t('stocklist.coldstart-info') }}
|
||||
</Checkbox>
|
||||
|
||||
<Checkbox :disabled="!store.stockSupportsDoubleManning" v-model="store.isDoubleManned">
|
||||
{{ $t('stocklist.doublemanning-info') }}
|
||||
</Checkbox>
|
||||
</div>
|
||||
|
||||
<div class="stock_warnings" v-if="hasAnyWarnings">
|
||||
<div class="warning" v-if="locoNotSuitable">
|
||||
(!) {{ $t('stocklist.warning-not-suitable') }}
|
||||
</div>
|
||||
|
||||
<div class="warning" v-if="lengthExceeded && store.isTrainPassenger">
|
||||
(!) {{ $t('stocklist.warning-passenger-too-long') }}
|
||||
</div>
|
||||
|
||||
<div class="warning" v-if="lengthExceeded && !store.isTrainPassenger">
|
||||
(!) {{ $t('stocklist.warning-freight-too-long') }}
|
||||
</div>
|
||||
|
||||
<div class="warning" v-if="teamOnlyVehicles.length > 0">
|
||||
(!)
|
||||
{{
|
||||
$t('stocklist.warning-team-only-vehicle', [
|
||||
teamOnlyVehicles.map((v) => v.vehicleRef.type).join(', '),
|
||||
])
|
||||
}}
|
||||
</div>
|
||||
|
||||
<div class="warning" v-if="weightExceeded">
|
||||
(!)
|
||||
<i18n-t keypath="stocklist.warning-too-heavy">
|
||||
<template #href>
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://docs.google.com/spreadsheets/d/1KVa5vn2d8XGkXQFwbavVudwKqUQxbLOucHWs2VYqAUE"
|
||||
>
|
||||
{{ $t('stocklist.acceptable-mass-docs') }}
|
||||
</a>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</div>
|
||||
|
||||
<div class="warning" v-if="locoCountExceeded">
|
||||
{{ $t('stocklist.warning-too-many-locos') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<StockThumbnails :onListItemClick="onListItemClick" />
|
||||
|
||||
<!-- Stock list -->
|
||||
<div class="list-wrapper">
|
||||
<div v-if="stockIsEmpty" class="list-empty">
|
||||
<div class="stock-info">{{ $t('stocklist.list-empty') }}</div>
|
||||
</div>
|
||||
|
||||
<ul v-else>
|
||||
<transition-group name="stock-list-anim">
|
||||
<li
|
||||
v-for="(stock, i) in store.stockList"
|
||||
:key="stock.id"
|
||||
:class="{ loco: isTractionUnit(stock.vehicleRef) }"
|
||||
tabindex="0"
|
||||
@click="onListItemClick(i)"
|
||||
@keydown.enter="onListItemClick(i)"
|
||||
@keydown.w="moveUpStock(i)"
|
||||
@keydown.s="moveDownStock(i)"
|
||||
@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">
|
||||
<span v-if="i == store.chosenStockListIndex">• </span>
|
||||
{{ i + 1 }}.
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="stock-info-type"
|
||||
:data-sponsor-only="
|
||||
stock.vehicleRef.sponsorOnlyTimestamp &&
|
||||
stock.vehicleRef.sponsorOnlyTimestamp > Date.now()
|
||||
"
|
||||
:data-team-only="stock.vehicleRef.teamOnly"
|
||||
>
|
||||
{{
|
||||
isTractionUnit(stock.vehicleRef)
|
||||
? stock.vehicleRef.type
|
||||
: getCarSpecFromType(stock.vehicleRef.type)
|
||||
}}
|
||||
</span>
|
||||
|
||||
<span class="stock-info-cargo" v-if="stock.cargo">
|
||||
{{ stock.cargo.id }}
|
||||
</span>
|
||||
|
||||
<span class="stock-info-length">{{ stock.vehicleRef.length }}m</span>
|
||||
|
||||
<span class="stock-info-mass">
|
||||
{{ ((stock.vehicleRef.weight + (stock.cargo?.weight ?? 0)) / 1000).toFixed(1) }}t
|
||||
</span>
|
||||
<span class="stock-info-speed">{{ stock.vehicleRef.maxSpeed }}km/h</span>
|
||||
</div>
|
||||
</li>
|
||||
</transition-group>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -158,18 +232,18 @@ import { defineComponent } from 'vue';
|
||||
|
||||
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 Checkbox from '../common/Checkbox.vue';
|
||||
import { isTractionUnit } from '../../utils/vehicleUtils';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'stock-list',
|
||||
components: { StockThumbnails },
|
||||
components: { StockThumbnails, Checkbox },
|
||||
|
||||
mixins: [warningsMixin, imageMixin, stockMixin, stockPreviewMixin],
|
||||
mixins: [imageMixin, stockMixin, stockPreviewMixin],
|
||||
|
||||
setup() {
|
||||
const store = useStore();
|
||||
@@ -187,13 +261,30 @@ export default defineComponent({
|
||||
}),
|
||||
|
||||
computed: {
|
||||
chosenRealComposition() {
|
||||
const currentStockString = this.store.stockList.map((s) => s.vehicleRef.type).join(';');
|
||||
|
||||
return this.store.realCompositionList.find((rc) => rc.stockString == currentStockString);
|
||||
},
|
||||
|
||||
stockString() {
|
||||
if (this.store.stockList.length == 0) return '';
|
||||
|
||||
const includeColdStart = this.store.isColdStart && this.store.stockSupportsColdStart;
|
||||
const includeDoubleManned =
|
||||
this.store.isDoubleManned && this.store.stockSupportsDoubleManning;
|
||||
|
||||
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 =
|
||||
isTractionUnit(stock.vehicleRef) || !stock.cargo
|
||||
? stock.vehicleRef.type
|
||||
: `${stock.vehicleRef.type}:${stock.cargo.id}`;
|
||||
|
||||
return stockTypeStr + coldStart;
|
||||
if (i == 0 && (includeColdStart || includeDoubleManned))
|
||||
return `${stockTypeStr},${includeColdStart ? 'c' : ''}${includeDoubleManned ? 'd' : ''}`;
|
||||
|
||||
return stockTypeStr;
|
||||
})
|
||||
.join(';');
|
||||
},
|
||||
@@ -203,16 +294,59 @@ 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;
|
||||
lengthExceeded() {
|
||||
return (
|
||||
(this.store.totalLength > 350 && this.store.isTrainPassenger) ||
|
||||
(this.store.totalLength > 650 && !this.store.isTrainPassenger)
|
||||
);
|
||||
},
|
||||
|
||||
weightExceeded() {
|
||||
return this.store.acceptableWeight && this.store.totalWeight > this.store.acceptableWeight;
|
||||
},
|
||||
|
||||
locoNotSuitable() {
|
||||
return (
|
||||
!this.store.isTrainPassenger &&
|
||||
this.store.stockList.length > 1 &&
|
||||
!this.store.stockList.every((stock) => isTractionUnit(stock.vehicleRef)) &&
|
||||
this.store.stockList.some(
|
||||
(stock) => isTractionUnit(stock.vehicleRef) && stock.vehicleRef.type.startsWith('EP')
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
locoCountExceeded() {
|
||||
return (
|
||||
this.store.stockList.reduce((acc, stock) => {
|
||||
if (isTractionUnit(stock.vehicleRef)) acc += 1;
|
||||
return acc;
|
||||
}, 0) > 2
|
||||
);
|
||||
},
|
||||
|
||||
teamOnlyVehicles() {
|
||||
return this.store.stockList.filter((stock) => stock.vehicleRef.teamOnly);
|
||||
},
|
||||
|
||||
hasAnyWarnings() {
|
||||
return (
|
||||
this.locoCountExceeded ||
|
||||
this.weightExceeded ||
|
||||
this.lengthExceeded ||
|
||||
this.locoNotSuitable ||
|
||||
this.teamOnlyVehicles
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
locoSupportsColdStart,
|
||||
isTractionUnit,
|
||||
|
||||
copyToClipboard() {
|
||||
navigator.clipboard.writeText(this.stockString);
|
||||
@@ -229,7 +363,11 @@ export default defineComponent({
|
||||
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.vehicleRef.type
|
||||
? -1
|
||||
: stockID;
|
||||
|
||||
if (this.store.chosenStockListIndex == -1) {
|
||||
this.store.chosenVehicle = null;
|
||||
@@ -258,20 +396,6 @@ export default defineComponent({
|
||||
this.store.chosenStockListIndex = -1;
|
||||
},
|
||||
|
||||
addStock(index: number) {
|
||||
if (index == -1) return;
|
||||
|
||||
this.store.stockList[index].count++;
|
||||
},
|
||||
|
||||
subStock(index: number) {
|
||||
if (index == -1) return;
|
||||
|
||||
if (this.store.stockList[index].count < 2) return;
|
||||
|
||||
this.store.stockList[index].count--;
|
||||
},
|
||||
|
||||
removeStock(index: number) {
|
||||
if (index == -1) return;
|
||||
|
||||
@@ -305,7 +429,7 @@ export default defineComponent({
|
||||
|
||||
shuffleCars() {
|
||||
const availableIndexes = this.store.stockList.reduce((acc, stock, i) => {
|
||||
if (!stock.isLoco) acc.push(i);
|
||||
if (!isTractionUnit(stock.vehicleRef)) acc.push(i);
|
||||
|
||||
return acc;
|
||||
}, [] as number[]);
|
||||
@@ -315,7 +439,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];
|
||||
@@ -326,7 +451,7 @@ export default defineComponent({
|
||||
downloadStock() {
|
||||
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; ${
|
||||
const defaultName = `${this.chosenRealComposition ? this.chosenRealComposition.stockId + ' ' : ''}${this.store.stockList[0].vehicleRef.type} ${(this.store.totalWeight / 1000).toFixed(1)}t; ${
|
||||
this.store.totalLength
|
||||
}m; vmax ${this.store.maxStockSpeed}`;
|
||||
|
||||
@@ -364,7 +489,7 @@ export default defineComponent({
|
||||
this.loadStockFromString(stockString);
|
||||
};
|
||||
|
||||
reader.onerror = (err) => console.log(err);
|
||||
reader.onerror = (err) => console.error(err);
|
||||
|
||||
inputEl.value = '';
|
||||
},
|
||||
@@ -399,13 +524,15 @@ export default defineComponent({
|
||||
@import '../../styles/global';
|
||||
@import '../../styles/tab.scss';
|
||||
|
||||
.stock-list-tab {
|
||||
display: grid;
|
||||
grid-gap: 0.5em;
|
||||
.tab_content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.warning {
|
||||
padding: 0.25em;
|
||||
margin: 0.25em 0;
|
||||
background: $accentColor;
|
||||
color: black;
|
||||
|
||||
@@ -421,9 +548,9 @@ export default defineComponent({
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
gap: 0.5em;
|
||||
flex-wrap: wrap;
|
||||
|
||||
padding: 0.5em;
|
||||
|
||||
@@ -438,21 +565,6 @@ export default defineComponent({
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
input#stock-count {
|
||||
width: 3em;
|
||||
|
||||
margin: 0;
|
||||
padding: 0.25em;
|
||||
outline: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
button {
|
||||
img {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stock_actions {
|
||||
@@ -472,15 +584,30 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
.stock_spawn-settings {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.real-stock-info {
|
||||
img {
|
||||
height: 1.3ch;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
.list-wrapper {
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.list-empty {
|
||||
background-color: $secondaryColor;
|
||||
border-radius: 0.5em;
|
||||
padding: 0.75em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ul {
|
||||
overflow-y: scroll;
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
@@ -498,16 +625,11 @@ ul > li {
|
||||
&:focus-visible {
|
||||
outline: 1px solid white;
|
||||
}
|
||||
|
||||
&.list-empty {
|
||||
background-color: $secondaryColor;
|
||||
border-radius: 0.5em;
|
||||
padding: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
li > .stock-info {
|
||||
display: flex;
|
||||
gap: 0.25em;
|
||||
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
@@ -516,46 +638,39 @@ li > .stock-info {
|
||||
|
||||
& > span {
|
||||
padding: 0.5em;
|
||||
margin-right: 0.25em;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.sponsor {
|
||||
color: salmon;
|
||||
.stock-info-no,
|
||||
.stock-info-type {
|
||||
background-color: $secondaryColor;
|
||||
|
||||
&[data-team-only='true'] {
|
||||
color: $teamColor;
|
||||
}
|
||||
|
||||
&[data-sponsor-only='true'] {
|
||||
color: $sponsorColor;
|
||||
}
|
||||
}
|
||||
|
||||
.stock-info {
|
||||
&__no,
|
||||
&__type {
|
||||
background-color: $secondaryColor;
|
||||
}
|
||||
.stock-info-no {
|
||||
min-width: 3.5em;
|
||||
text-align: right;
|
||||
|
||||
&__count {
|
||||
background-color: #e04e3e;
|
||||
&[data-selected='true'] {
|
||||
color: $accentColor;
|
||||
}
|
||||
}
|
||||
|
||||
&__no {
|
||||
min-width: 3.5em;
|
||||
text-align: right;
|
||||
.stock-info-cargo {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
&[data-selected='true'] {
|
||||
color: $accentColor;
|
||||
}
|
||||
}
|
||||
|
||||
&__cargo {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
&__length,
|
||||
&__mass,
|
||||
&__speed {
|
||||
background-color: #555;
|
||||
}
|
||||
.stock-info-length,
|
||||
.stock-info-mass,
|
||||
.stock-info-speed {
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
.stock-list-anim {
|
||||
|
||||
@@ -5,75 +5,80 @@
|
||||
</div>
|
||||
|
||||
<div class="tab_content">
|
||||
<div class="actions-panel">
|
||||
<div class="actions-panel_vehicles">
|
||||
<button class="btn" :data-chosen="currentFilterMode == 'tractions'" @click="toggleFilter('tractions')">
|
||||
{{ $t('wiki.action-vehicles') }}
|
||||
</button>
|
||||
<button class="btn" :data-chosen="currentFilterMode == 'carriages'" @click="toggleFilter('carriages')">
|
||||
{{ $t('wiki.action-carriages') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<label>
|
||||
<span>{{ $t('wiki.labels.search-vehicle') }}</span>
|
||||
<input
|
||||
type="text"
|
||||
:placeholder="$t('wiki.labels.search-vehicle-placeholder')"
|
||||
v-model="searchedVehicleTypeName"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div class="actions-panel_search">
|
||||
<input type="text" :placeholder="$t('wiki.search')" v-model="searchedVehicleTypeName" />
|
||||
</div>
|
||||
<label>
|
||||
<span>{{ $t('wiki.labels.vehicles') }}</span>
|
||||
<select name="filter-type" id="filter-type" v-model="filterType">
|
||||
<option v-for="filter in filters" :key="filter" :value="filter">
|
||||
{{ $t(`wiki.filters.${filter}`) }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>{{ $t('wiki.labels.sort-by') }}</span>
|
||||
<select name="sorter-type" id="sorter-type" v-model="sorterType">
|
||||
<option v-for="sorter in sorters" :key="sorter" :value="sorter">
|
||||
{{ $t(`wiki.sort-by.${sorter}`) }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>{{ $t('wiki.labels.sort-direction') }}</span>
|
||||
|
||||
<select name="sorter-direction" id="sorter-direction" v-model="sorterDirection">
|
||||
<option value="asc">{{ $t('wiki.sort-direction.asc') }}</option>
|
||||
<option value="desc">{{ $t('wiki.sort-direction.desc') }}</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="table-wrapper" ref="table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="header in visibleHeaders" @click="toggleSorter(header)" :key="header.id">
|
||||
{{ $t(`wiki.header.${header.id}`) }}
|
||||
<ul class="vehicles" ref="vehicles">
|
||||
<li
|
||||
v-for="vehicle in computedVehicles"
|
||||
:key="vehicle.type"
|
||||
:data-preview="vehicle.type === store.chosenVehicle?.type"
|
||||
@click="previewVehicle(vehicle)"
|
||||
@dblclick="addVehicle(vehicle)"
|
||||
@keydown.enter="onVehicleSelect(vehicle)"
|
||||
tabindex="0"
|
||||
>
|
||||
<img loading="lazy" width="120" :src="getThumbnailURL(vehicle.type, 'small')" />
|
||||
|
||||
<span v-if="currentSorter.id == header.id">
|
||||
{{ currentSorter.direction == 1 ? `⇑` : `⇓` }}
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<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)"
|
||||
<span>
|
||||
<span
|
||||
class="vehicle-name"
|
||||
:class="{
|
||||
'sponsor-only':
|
||||
vehicle.sponsorOnlyTimestamp && vehicle.sponsorOnlyTimestamp > Date.now(),
|
||||
'team-only': vehicle.teamOnly,
|
||||
}"
|
||||
>
|
||||
<td style="width: 120px">
|
||||
<img
|
||||
width="120"
|
||||
:src="getThumbnailURL(vehicle.type, 'small')"
|
||||
:alt="`${vehicle.type}`"
|
||||
loading="lazy"
|
||||
@error="(e) => ((e.target as HTMLElement).style.display = 'none')"
|
||||
/>
|
||||
</td>
|
||||
<b>{{ vehicle.type.replace(/_/g, ' ') }}</b>
|
||||
</span>
|
||||
|
||||
<td :data-sponsoronly="vehicle.isSponsorsOnly">{{ vehicle.type }}</td>
|
||||
<div class="vehicle-group">
|
||||
{{ $t(`wiki.${vehicle.group}`) }} |
|
||||
{{ isTractionUnit(vehicle) ? vehicle.cabinType : vehicle.constructionType }}
|
||||
</div>
|
||||
|
||||
<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="currentFilterMode == 'carriages'">{{ !isLocomotive(vehicle) ? vehicle.cargoList.length : '---' }}</td>
|
||||
<td v-if="currentFilterMode == 'tractions'">
|
||||
{{ isLocomotive(vehicle) ? (locoSupportsColdStart(vehicle.constructionType) ? `✓` : '✗') : '---' }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<span ref="table-bottom"></span>
|
||||
</table>
|
||||
</div>
|
||||
<div class="vehicle-props">
|
||||
{{ vehicle.length }}m | {{ (vehicle.weight / 1000).toFixed(1) }}t |
|
||||
{{ vehicle.maxSpeed }}km/h
|
||||
</div>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -82,36 +87,18 @@
|
||||
import { defineComponent } from 'vue';
|
||||
import { useStore } from '../../store';
|
||||
import stockPreviewMixin from '../../mixins/stockPreviewMixin';
|
||||
import { Vehicle } from '../../types';
|
||||
import { isLocomotive } from '../../utils/vehicleUtils';
|
||||
import { IVehicle } from '../../types';
|
||||
import { isTractionUnit } from '../../utils/vehicleUtils';
|
||||
import stockMixin from '../../mixins/stockMixin';
|
||||
import imageMixin from '../../mixins/imageMixin';
|
||||
import { locoSupportsColdStart } from '../../utils/locoUtils';
|
||||
|
||||
type SorterID = 'type' | 'constructionType' | 'image' | 'length' | 'mass' | 'maxSpeed' | 'cargoCount' | 'group' | 'coldStart';
|
||||
const sorters = ['type', 'group', 'length', 'weight', 'maxSpeed'] as const;
|
||||
const filters = ['vehicles-all', 'vehicles-traction', 'vehicles-wagon'] as const;
|
||||
|
||||
interface IWikiHeader {
|
||||
id: SorterID;
|
||||
sortable: boolean;
|
||||
for: 'all' | 'carriages' | 'tractions';
|
||||
}
|
||||
type SorterType = (typeof sorters)[number];
|
||||
type SorterDirection = 'asc' | 'desc';
|
||||
|
||||
interface IWikiRow {
|
||||
vehicle: Vehicle;
|
||||
show: boolean;
|
||||
}
|
||||
|
||||
const headers: IWikiHeader[] = [
|
||||
{ id: 'image', sortable: false, for: 'all' },
|
||||
{ id: 'type', 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' },
|
||||
{ id: 'maxSpeed', sortable: true, for: 'all' },
|
||||
{ id: 'coldStart', sortable: true, for: 'tractions' },
|
||||
{ id: 'cargoCount', sortable: true, for: 'carriages' },
|
||||
];
|
||||
type FilterType = (typeof filters)[number];
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [stockPreviewMixin, stockMixin, imageMixin],
|
||||
@@ -119,105 +106,104 @@ export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
store: useStore(),
|
||||
headers,
|
||||
observer: null as IntersectionObserver | null,
|
||||
|
||||
scrollTop: 0,
|
||||
sorters: sorters,
|
||||
filters: filters,
|
||||
|
||||
searchedVehicleTypeName: '',
|
||||
|
||||
currentSorter: {
|
||||
id: 'type' as SorterID,
|
||||
direction: 1,
|
||||
},
|
||||
sorterType: 'type' as SorterType,
|
||||
sorterDirection: 'asc' as SorterDirection,
|
||||
|
||||
currentFilterMode: 'all' as 'all' | 'tractions' | 'carriages',
|
||||
filterType: 'vehicles-all' as FilterType,
|
||||
|
||||
lastScrollTop: 0,
|
||||
};
|
||||
},
|
||||
|
||||
activated() {
|
||||
const tableWrapperRef = this.$refs['table-wrapper'] as HTMLElement;
|
||||
deactivated() {
|
||||
this.lastScrollTop = (this.$refs['vehicles'] as HTMLUListElement)?.scrollTop || 0;
|
||||
},
|
||||
|
||||
tableWrapperRef.scrollTo({
|
||||
top: this.scrollTop,
|
||||
});
|
||||
activated() {
|
||||
(this.$refs['vehicles'] as HTMLUListElement)?.scrollTo({ top: this.lastScrollTop });
|
||||
},
|
||||
|
||||
watch: {
|
||||
computedVehicles() {
|
||||
const vehiclesRef = this.$refs['vehicles'] as HTMLElement;
|
||||
|
||||
vehiclesRef.scrollTo({
|
||||
top: 0,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
locoSupportsColdStart,
|
||||
isLocomotive,
|
||||
isTractionUnit,
|
||||
|
||||
toggleFilter(name: typeof this.currentFilterMode) {
|
||||
this.currentFilterMode = this.currentFilterMode == name ? 'all' : name;
|
||||
onVehicleSelect(vehicle: IVehicle) {
|
||||
if (this.store.chosenVehicle?.type === vehicle.type) this.addVehicle(vehicle);
|
||||
this.previewVehicle(vehicle);
|
||||
},
|
||||
|
||||
toggleSorter(header: IWikiHeader) {
|
||||
if (!header.sortable) return;
|
||||
filterVehicles(v: IVehicle) {
|
||||
if (this.searchedVehicleTypeName)
|
||||
return v.type
|
||||
.toLocaleLowerCase()
|
||||
.includes(this.searchedVehicleTypeName.toLocaleLowerCase());
|
||||
|
||||
if (header.id == this.currentSorter.id) this.currentSorter.direction *= -1;
|
||||
this.currentSorter.id = header.id;
|
||||
},
|
||||
|
||||
sortTableRows(row1: IWikiRow, row2: IWikiRow) {
|
||||
if (!row1.show) return 0;
|
||||
|
||||
const { id, direction } = this.currentSorter;
|
||||
|
||||
switch (id) {
|
||||
case 'type':
|
||||
case 'constructionType':
|
||||
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(row1.vehicle[id] - row2.vehicle[id]) * direction;
|
||||
|
||||
case 'cargoCount':
|
||||
return (
|
||||
(!isLocomotive(row1.vehicle) ? Math.sign(row1.vehicle.cargoList.length || -1) : -1) -
|
||||
(!isLocomotive(row2.vehicle) ? (row2.vehicle.cargoList.length || -1) * direction : -1)
|
||||
);
|
||||
|
||||
case 'coldStart':
|
||||
return (locoSupportsColdStart(row1.vehicle.constructionType) > locoSupportsColdStart(row2.vehicle.constructionType) ? 1 : -1) * direction;
|
||||
switch (this.filterType) {
|
||||
case 'vehicles-all':
|
||||
return true;
|
||||
case 'vehicles-traction':
|
||||
return isTractionUnit(v);
|
||||
case 'vehicles-wagon':
|
||||
return !isTractionUnit(v);
|
||||
|
||||
default:
|
||||
break;
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
return direction == 1 ? row1.vehicle.type.localeCompare(row2.vehicle.type) : row2.vehicle.type.localeCompare(row1.vehicle.type);
|
||||
sortVehicles(v1: IVehicle, v2: IVehicle) {
|
||||
const direction = this.sorterDirection == 'asc' ? 1 : -1;
|
||||
|
||||
switch (this.sorterType) {
|
||||
case 'type':
|
||||
case 'group':
|
||||
return direction * v1[this.sorterType].localeCompare(v2[this.sorterType]);
|
||||
|
||||
case 'weight':
|
||||
case 'length':
|
||||
case 'maxSpeed':
|
||||
return Math.sign(v1[this.sorterType] - v2[this.sorterType]) * direction;
|
||||
|
||||
// case 'cargoCount':
|
||||
// return (
|
||||
// Math.sign(
|
||||
// (!isTractionUnit(v1) ? v1.cargoTypes.length || -1 : -1) -
|
||||
// (!isTractionUnit(row2.vehicle) ? row2.vehicle.cargoTypes.length || -1 : -1)
|
||||
// ) * direction
|
||||
// );
|
||||
|
||||
// case 'coldStart':
|
||||
// return (
|
||||
// ((isTractionUnit(v1) && v1.coldStart ? 1 : -1) -
|
||||
// (isTractionUnit(row2.vehicle) && row2.vehicle.coldStart ? 1 : -1)) *
|
||||
// direction
|
||||
// );
|
||||
|
||||
default:
|
||||
return v1.type.localeCompare(v2.type) * direction;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
computedTableData(): IWikiRow[] {
|
||||
return this.store.vehicleDataList
|
||||
.map((vehicle) => ({
|
||||
vehicle,
|
||||
show:
|
||||
new RegExp(`${this.searchedVehicleTypeName.trim()}`, 'i').test(vehicle.type) &&
|
||||
(this.currentFilterMode == 'all' ||
|
||||
(this.currentFilterMode == 'tractions' && isLocomotive(vehicle)) ||
|
||||
(this.currentFilterMode == 'carriages' && !isLocomotive(vehicle))),
|
||||
|
||||
// ((this.filters.tractions && isLocomotive(vehicle)) || (this.filters.carriages && !isLocomotive(vehicle))),
|
||||
}))
|
||||
.sort((a, b) => this.sortTableRows(a, b));
|
||||
},
|
||||
|
||||
visibleHeaders() {
|
||||
const filtersActive = this.currentFilterMode;
|
||||
|
||||
return this.headers.filter((header) => header.for == 'all' || header.for == filtersActive);
|
||||
},
|
||||
|
||||
areTractionVehiclesShown() {
|
||||
return this.currentFilterMode == 'all' || this.currentFilterMode == 'tractions';
|
||||
},
|
||||
|
||||
areCarriagesShown() {
|
||||
return this.currentFilterMode == 'all' || this.currentFilterMode == 'carriages';
|
||||
computedVehicles() {
|
||||
return this.store.vehicleDataList.filter(this.filterVehicles).sort(this.sortVehicles);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -226,90 +212,84 @@ export default defineComponent({
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/tab.scss';
|
||||
|
||||
.actions-panel {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.actions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(10em, 1fr));
|
||||
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5em;
|
||||
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.actions-panel_vehicles {
|
||||
.actions > label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25em;
|
||||
|
||||
span {
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
.vehicles {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
gap: 0.5em;
|
||||
overflow: auto;
|
||||
|
||||
max-height: 730px;
|
||||
|
||||
margin-top: 0.75em;
|
||||
padding: 0.25em;
|
||||
}
|
||||
|
||||
.vehicles > li {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.actions-panel_search {
|
||||
input {
|
||||
width: auto;
|
||||
background-color: #161c2e;
|
||||
padding: 0.5em;
|
||||
|
||||
min-height: 75px;
|
||||
cursor: pointer;
|
||||
|
||||
&[data-preview='true'] {
|
||||
background-color: #435288;
|
||||
}
|
||||
|
||||
& > span {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
overflow: auto;
|
||||
height: 750px;
|
||||
max-height: 95vh;
|
||||
.vehicle-name {
|
||||
overflow: hidden;
|
||||
text-wrap: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.wiki-list table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
.sponsor-only {
|
||||
color: $sponsorColor;
|
||||
|
||||
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;
|
||||
|
||||
&:first-child {
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
&:nth-child(odd) {
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
text-align: center;
|
||||
height: 70px;
|
||||
|
||||
&[data-sponsoronly='true'] {
|
||||
color: salmon;
|
||||
}
|
||||
&::after {
|
||||
content: '*';
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $breakpointMd) {
|
||||
.wiki-list table {
|
||||
th {
|
||||
min-width: 100px;
|
||||
}
|
||||
.team-only {
|
||||
color: $teamColor;
|
||||
|
||||
img {
|
||||
max-width: 100px;
|
||||
}
|
||||
&::after {
|
||||
content: '*';
|
||||
}
|
||||
}
|
||||
|
||||
.vehicle-props {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
@media screen and (max-width: $breakpointSm) {
|
||||
.actions-panel {
|
||||
align-items: stretch;
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { useStore } from "../../store";
|
||||
import { defineComponent } from 'vue';
|
||||
import { useStore } from '../../store';
|
||||
|
||||
export default defineComponent({
|
||||
data() {
|
||||
|
||||
@@ -1,29 +1,31 @@
|
||||
<template>
|
||||
<div class="stock_thumbnails" ref="thumbnailsRef">
|
||||
<div class="stock-thumbnails" ref="thumbnailsRef">
|
||||
<div
|
||||
class="thumbnail-item"
|
||||
v-for="(stock, stockIndex) in store.stockList"
|
||||
:key="stockIndex"
|
||||
:data-selected="store.chosenStockListIndex == stockIndex"
|
||||
:data-sponsor-only="
|
||||
stock.vehicleRef.sponsorOnlyTimestamp && stock.vehicleRef.sponsorOnlyTimestamp > Date.now()
|
||||
"
|
||||
:data-team-only="stock.vehicleRef.teamOnly"
|
||||
draggable="true"
|
||||
@dragstart="onDragStart(stockIndex)"
|
||||
@drop="onDrop($event, stockIndex)"
|
||||
@dragover="allowDrop"
|
||||
@click="onListItemClick(stockIndex)"
|
||||
>
|
||||
<span @click="onListItemClick(stockIndex)" :key="stock.id">
|
||||
<b :class="{ sponsor: stock.isSponsorsOnly }">
|
||||
{{ stock.type }}
|
||||
</b>
|
||||
<b>
|
||||
{{ stock.vehicleRef.type }}
|
||||
</b>
|
||||
|
||||
<span>
|
||||
<img
|
||||
draggable="false"
|
||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stock.type}.png`"
|
||||
:alt="stock.type"
|
||||
:title="stock.type"
|
||||
@error="stockImageError($event, stock)"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<img
|
||||
draggable="false"
|
||||
:src="`https://rj.td2.info.pl/dist/img/thumbnails/${stock.vehicleRef.type}.png`"
|
||||
:alt="stock.vehicleRef.type"
|
||||
:title="stock.vehicleRef.type"
|
||||
@error="stockImageError($event, stock)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -44,7 +46,7 @@ const onListItemClick = (index: number) => {
|
||||
};
|
||||
|
||||
const stockImageError = (e: Event, stock: IStock) => {
|
||||
(e.target as HTMLImageElement).src = `images/${stock.useType}-unknown.png`;
|
||||
(e.target as HTMLImageElement).src = `images/${stock.vehicleRef.group}-unknown.png`;
|
||||
};
|
||||
|
||||
watch(
|
||||
@@ -53,11 +55,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',
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -87,41 +91,52 @@ const allowDrop = (e: DragEvent) => {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.stock_thumbnails {
|
||||
@import '../../styles/global.scss';
|
||||
|
||||
.stock-thumbnails {
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
background-color: #353a57;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
cursor: pointer;
|
||||
min-height: 100px;
|
||||
.thumbnail-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&[data-selected='true'] {
|
||||
background-color: rebeccapurple;
|
||||
}
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
|
||||
> span {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
padding: 0.5em 0;
|
||||
padding-top: 0.5em;
|
||||
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
min-height: 100px;
|
||||
font-size: 0.85em;
|
||||
|
||||
font-size: 0.85em;
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
|
||||
&[data-selected='true'] {
|
||||
background-color: rebeccapurple;
|
||||
}
|
||||
|
||||
b {
|
||||
color: #ccc;
|
||||
margin: 0 1em;
|
||||
}
|
||||
|
||||
&[data-sponsor-only='true'] > b {
|
||||
color: $sponsorColor;
|
||||
}
|
||||
|
||||
&[data-team-only='true'] > b {
|
||||
color: $teamColor;
|
||||
}
|
||||
|
||||
img {
|
||||
max-height: 60px;
|
||||
}
|
||||
}
|
||||
|
||||
.sponsor {
|
||||
color: salmon;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"EU06": [650000, 2000000],
|
||||
"EU07": [650000, 2000000],
|
||||
"4E": [650000, 2000000],
|
||||
"EU07E": [650000, 2000000],
|
||||
"EP07": [650000, 650000],
|
||||
"EP08": [650000, 650000],
|
||||
"EP09": [800000, 800000],
|
||||
"ET41": [700000, 4000000],
|
||||
"SM42": [2400000, 2400000]
|
||||
}
|
||||
@@ -1,37 +1,73 @@
|
||||
{
|
||||
"regionNumbers": {
|
||||
"Warszawa": 1,
|
||||
"Lublin": 2,
|
||||
"Kraków": 3,
|
||||
"Sosnowiec": 4,
|
||||
"Gdańsk": 5,
|
||||
"Wrocław": 6,
|
||||
"Poznań": 7,
|
||||
"Szczecin": 8,
|
||||
"Rezerwa": 9
|
||||
"Warszawa (1)": 1,
|
||||
"Lublin (2)": 2,
|
||||
"Kraków (3)": 3,
|
||||
"Sosnowiec (4)": 4,
|
||||
"Gdańsk (5)": 5,
|
||||
"Wrocław (6)": 6,
|
||||
"Poznań (7)": 7,
|
||||
"Szczecin (8)": 8,
|
||||
"Rezerwa (9)": 9
|
||||
},
|
||||
"sameRegions": {
|
||||
"Losowy": [
|
||||
10, 11, 19, 91, 93, 97, 99, 20, 22, 29, 30, 33, 39, 40, 44, 49, 94, 50,
|
||||
55, 59, 90, 95, 96, 66, 60, 69, 77, 70, 79, 88, 80, 89, 92, 98
|
||||
10, 11, 19, 91, 93, 97, 99, 20, 22, 29, 30, 33, 39, 40, 44, 49, 94, 50, 55, 59, 90, 95, 96,
|
||||
66, 60, 69, 77, 70, 79, 88, 80, 89, 92, 98
|
||||
],
|
||||
"Warszawa": [10, 11, 19, 91, 93, 97, 99],
|
||||
"Lublin": [20, 22, 29],
|
||||
"Kraków": [30, 33, 39],
|
||||
"Sosnowiec": [40, 44, 49, 94],
|
||||
"Gdańsk": [50, 55, 59, 90, 95, 96],
|
||||
"Wrocław": [66, 60, 69],
|
||||
"Poznań": [77, 70, 79],
|
||||
"Szczecin": [88, 80],
|
||||
"Rezerwa": [89, 92, 98]
|
||||
"Warszawa (1)": [10, 11, 19, 91, 93, 97, 99],
|
||||
"Lublin (2)": [20, 22, 29],
|
||||
"Kraków (3)": [30, 33, 39],
|
||||
"Sosnowiec (4)": [40, 44, 49, 94],
|
||||
"Gdańsk (5)": [50, 55, 59, 90, 95, 96],
|
||||
"Wrocław (6)": [66, 60, 69],
|
||||
"Poznań (7)": [77, 70, 79],
|
||||
"Szczecin (8)": [88, 80],
|
||||
"Rezerwa (9)": [89, 92, 98]
|
||||
},
|
||||
"categories": {
|
||||
"EI": "2:00-99:2",
|
||||
"MP/RP": "2:050-169:3",
|
||||
"RO": "2:200-999:3",
|
||||
"PW": "2:6;3:0-899:3",
|
||||
"TM": "2:4;3:0-899:3",
|
||||
"TK": "2:3;3:0-899:3",
|
||||
"LT": "2:5;3:0-899:3"
|
||||
"categoriesRules": {
|
||||
"EI": [null, "00", "99"],
|
||||
"EC": [null, "001", "049"],
|
||||
"EN": [null, "001", "049"],
|
||||
"MP": [null, "050", "169"],
|
||||
"RO": [null, "200", "999"],
|
||||
"RP": [null, "050", "169"],
|
||||
"PW": ["6", "000", "899"],
|
||||
"TK": ["3", "000", "899"],
|
||||
"TM": ["4", "000", "899"],
|
||||
"LT": ["5", "000", "899"]
|
||||
},
|
||||
"categoriesNextVersion": {
|
||||
"EI": [null, "00", "99"],
|
||||
"EC": [null, "001", "049"],
|
||||
"EN": [null, "001", "049"],
|
||||
|
||||
"RO": [null, "200", "999"],
|
||||
"RP": [null, "050", "169"],
|
||||
"RM": [null, "200", "999"],
|
||||
"RA": [null, "200", "999"],
|
||||
|
||||
"MO": [null, "200", "999"],
|
||||
"MP": [null, "050", "169"],
|
||||
"MM": [null, "001", "049"],
|
||||
"MH": [null, "170", "199"],
|
||||
|
||||
"PW": ["6", "000", "899"],
|
||||
"PX": ["6", "000", "899"],
|
||||
|
||||
"TC": ["0", "000", "899"],
|
||||
"TG": ["1", "000", "899"],
|
||||
"TR": ["1", "000", "899"],
|
||||
"TD": ["2", "000", "899"],
|
||||
"TK": ["3", "000", "899"],
|
||||
"TN": ["3", "000", "899"],
|
||||
"TM": ["4", "000", "899"],
|
||||
"TS": ["5", "000", "899"],
|
||||
|
||||
"LT": ["5", "000", "899"],
|
||||
"LP": ["6", "000", "899"],
|
||||
"LS": ["9", "000", "899"],
|
||||
|
||||
"ZN": ["9", "000", "899"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +1,82 @@
|
||||
{
|
||||
"EU07": {
|
||||
"passenger": {
|
||||
"650": 125
|
||||
"650000": 125
|
||||
},
|
||||
"cargo": {
|
||||
"2000": 70
|
||||
}
|
||||
"2000000": 70
|
||||
},
|
||||
"none": 110
|
||||
},
|
||||
"4E": {
|
||||
"passenger": {
|
||||
"650000": 125
|
||||
},
|
||||
"cargo": {
|
||||
"2000000": 70
|
||||
},
|
||||
"none": 110
|
||||
},
|
||||
"EU07E": {
|
||||
"passenger": {
|
||||
"650": 125
|
||||
"650000": 125
|
||||
},
|
||||
"cargo": {
|
||||
"2000": 70
|
||||
}
|
||||
"2000000": 70
|
||||
},
|
||||
"none": 110
|
||||
},
|
||||
"EP07": {
|
||||
"passenger": {
|
||||
"650": 125
|
||||
"650000": 125
|
||||
},
|
||||
"cargo": null
|
||||
"cargo": null,
|
||||
"none": 110
|
||||
},
|
||||
"EP08": {
|
||||
"passenger": {
|
||||
"650": 140
|
||||
"650000": 140
|
||||
},
|
||||
"cargo": null
|
||||
"cargo": null,
|
||||
"none": 110
|
||||
},
|
||||
"EP09": {
|
||||
"passenger": {
|
||||
"650": 160
|
||||
"650000": 160
|
||||
},
|
||||
"cargo": null
|
||||
"cargo": null,
|
||||
"none": 160
|
||||
},
|
||||
"ET41": {
|
||||
"passenger": {
|
||||
"700": 125
|
||||
"700000": 125
|
||||
},
|
||||
"cargo": {
|
||||
"4000": 70
|
||||
}
|
||||
"4000000": 70
|
||||
},
|
||||
"none": 110
|
||||
},
|
||||
"SM42": {
|
||||
"passenger": {
|
||||
"95": 90,
|
||||
"200": 80,
|
||||
"300": 70,
|
||||
"450": 60,
|
||||
"750": 50,
|
||||
"1130": 40,
|
||||
"1720": 30,
|
||||
"2400": 20
|
||||
"95000": 90,
|
||||
"200000": 80,
|
||||
"300000": 70,
|
||||
"450000": 60,
|
||||
"750000": 50,
|
||||
"1130000": 40,
|
||||
"1720000": 30,
|
||||
"2400000": 20
|
||||
},
|
||||
"cargo": {
|
||||
"95": 90,
|
||||
"200": 80,
|
||||
"300": 70,
|
||||
"450": 60,
|
||||
"750": 50,
|
||||
"1130": 40,
|
||||
"1720": 30,
|
||||
"2400": 20
|
||||
}
|
||||
"95000": 90,
|
||||
"200000": 80,
|
||||
"300000": 70,
|
||||
"450000": 60,
|
||||
"750000": 50,
|
||||
"1130000": 40,
|
||||
"1720000": 30,
|
||||
"2400000": 20
|
||||
},
|
||||
"none": 90
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
export const enum EVehicleUseType {
|
||||
LOCO_ELECTRICAL = "loco-e",
|
||||
LOCO_DIESEL = "loco-s",
|
||||
EMU = "loco-ezt",
|
||||
DMU = "loco-szt",
|
||||
|
||||
CAR_PASSENGER = "car-passenger",
|
||||
CAR_CARGO = "car-cargo",
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import axios from "axios";
|
||||
import axios from 'axios';
|
||||
|
||||
const http = axios.create({
|
||||
baseURL:
|
||||
import.meta.env.VITE_API_DEV === "1" && import.meta.env.DEV
|
||||
? "http://localhost:5500"
|
||||
: "https://spythere.github.io/api",
|
||||
import.meta.env.VITE_API_DEV === '1' && import.meta.env.DEV
|
||||
? 'http://localhost:3001'
|
||||
: 'https://stacjownik.spythere.eu',
|
||||
});
|
||||
|
||||
export default http;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import localePL from "./locales/pl.json";
|
||||
import localeEN from "./locales/en.json";
|
||||
import { createI18n } from "vue-i18n";
|
||||
import http from "./http";
|
||||
import localePL from './locales/pl.json';
|
||||
import localeEN from './locales/en.json';
|
||||
import { createI18n } from 'vue-i18n';
|
||||
|
||||
type LocaleMessageSchema = typeof localePL;
|
||||
type LocaleKey = "en" | "pl";
|
||||
type LocaleKey = 'en' | 'pl';
|
||||
|
||||
const locales: { [key in LocaleKey]: LocaleMessageSchema } = {
|
||||
en: localeEN,
|
||||
@@ -12,24 +11,14 @@ const locales: { [key in LocaleKey]: LocaleMessageSchema } = {
|
||||
};
|
||||
|
||||
const locale =
|
||||
window.localStorage.getItem("locale") ||
|
||||
(/^pl\b/.test(navigator.language) ? "pl" : "en");
|
||||
window.localStorage.getItem('locale') || (/^pl\b/.test(navigator.language) ? 'pl' : 'en');
|
||||
|
||||
const i18n = createI18n<[LocaleMessageSchema], "en" | "pl">({
|
||||
const i18n = createI18n<[LocaleMessageSchema], 'en' | 'pl'>({
|
||||
locale,
|
||||
fallbackLocale: "pl",
|
||||
fallbackLocale: 'pl',
|
||||
legacy: false,
|
||||
globalInjection: true,
|
||||
messages: locales,
|
||||
});
|
||||
|
||||
async function fetchBackendTranslations() {
|
||||
const localeData = (await http.get(`td2/data/locales.json`)).data;
|
||||
|
||||
i18n.global.mergeLocaleMessage("pl", localeData.pl);
|
||||
i18n.global.mergeLocaleMessage("en", localeData.en);
|
||||
}
|
||||
|
||||
fetchBackendTranslations();
|
||||
|
||||
export default i18n;
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
"cargo-title": "Cargo (only selected freight cars)",
|
||||
"no-cargo-available": "no cargo available",
|
||||
"cargo-empty": "empty",
|
||||
"loco-e": "ELECTR.",
|
||||
"loco-s": "DIESEL",
|
||||
"loco-ezt": "EMU",
|
||||
"loco-szt": "DMU",
|
||||
"car-passenger": "PASSENGER",
|
||||
"car-cargo": "FREIGHT",
|
||||
"loco-electric": "ELECTR.",
|
||||
"loco-diesel": "DIESEL",
|
||||
"unit-electric": "EMU",
|
||||
"unit-diesel": "DMU",
|
||||
"wagon-passenger": "PASSENGER",
|
||||
"wagon-freight": "FREIGHT",
|
||||
"action-add": "ADD NEW",
|
||||
"action-swap": "SWAP WITH",
|
||||
"real-stock": "POLISH TRAIN COMPOSITIONS"
|
||||
@@ -30,12 +30,13 @@
|
||||
"loading": "IMAGE LOADING...",
|
||||
"desc": "Choose a railway vehicle above to see its preview",
|
||||
"sponsor-only": "* SPONSORS ONLY UNTIL {0}",
|
||||
"loco-e": "ELECTRIC LOCO",
|
||||
"loco-s": "DIESEL LOCO",
|
||||
"loco-ezt": "ELECTRIC M.U.",
|
||||
"loco-szt": "DIESEL M.U.",
|
||||
"car-passenger": "PASSENGER CARRIAGE",
|
||||
"car-cargo": "FREIGHT CARRIAGE",
|
||||
"team-only": "* TD2 TEAM ONLY",
|
||||
"loco-electric": "ELECTRIC LOCO",
|
||||
"loco-diesel": "DIESEL LOCO",
|
||||
"unit-electric": "ELECTRIC M.U.",
|
||||
"unit-diesel": "DIESEL M.U.",
|
||||
"wagon-passenger": "PASSENGER CARRIAGE",
|
||||
"wagon-freight": "FREIGHT CARRIAGE",
|
||||
"cabin": "Cabin type:",
|
||||
"construction": "Construction type:"
|
||||
},
|
||||
@@ -47,6 +48,7 @@
|
||||
},
|
||||
"stocklist": {
|
||||
"title": "STOCK EDITOR",
|
||||
"disclaimer": "Theorethical value based on vehicles maximum speed in the current composition. It may be inaccurate in relation to the correct operational speed in certain configurations.",
|
||||
"alert-copied": "The rolling stock has been copied to your clipboard!",
|
||||
"alert-empty": "Lista pojazdów jest pusta!",
|
||||
"prompt-file": "Name a file and download it to the Presets folder (Documents/TTSK/TrainDriver2):",
|
||||
@@ -64,13 +66,16 @@
|
||||
"mass-accepted": "accepted",
|
||||
"length": "Length",
|
||||
"vmax": "vMax",
|
||||
"coldstart-info": "Cold start heading locomotive (only locos 303E & 203E type)",
|
||||
"coldstart-info": "Locomotive cold start",
|
||||
"doublemanning-info": "Double manning",
|
||||
"list-empty": "Stock list is empty!",
|
||||
"warning-not-suitable": "EP07 & EP08 type locomotives are designed for passenger traffic only!",
|
||||
|
||||
"warning-not-suitable": "EP series locomotives are designed for passenger traffic only!",
|
||||
"warning-passenger-too-long": "Maximum length of a passenger train may not be greater than 350m!",
|
||||
"warning-freight-too-long": "Maximum length of a freight train may not be greater than 650m!",
|
||||
"warning-too-many-locos": "This train has too many traction units!",
|
||||
"warning-too-heavy": "This train is too heavy! Check {href}",
|
||||
"warning-team-only-vehicle": "There's at least one vehicle available only for TD2 team members in your stock composition! ({0})",
|
||||
"acceptable-mass-docs": "acceptable rolling stock masses (PL)"
|
||||
},
|
||||
"stockgen": {
|
||||
@@ -91,57 +96,103 @@
|
||||
},
|
||||
"numgen": {
|
||||
"title": "TRAIN NUMBER GENERATOR",
|
||||
"subtitle": "Generates real train number based on Polish railway instruction Ir-11",
|
||||
"alert": "The number has been copied to your clipboard!",
|
||||
"start-region": "Beginning construction region",
|
||||
"end-region": "Terminating construction region",
|
||||
"train-category": "Train category",
|
||||
"number-info": "Generated train number:",
|
||||
"warning": "Choose category and (optionally) construction regions",
|
||||
"td2-wiki": "> Polish rules of train numbering (TD2 wiki)",
|
||||
"td2-wiki-link": "https://wiki.td2.info.pl/index.php?title=Zasady_numeracji_poci%C4%85g%C3%B3w/en",
|
||||
"td2-wiki": "> Polish rules of train numbering (forum thread)",
|
||||
"td2-wiki-link": "https://td2.info.pl/english-boards/new-train-categories-in-the-simulator/",
|
||||
"action-random-region": "DRAW REGIONS",
|
||||
"action-random-number": "DRAW A NUMBER",
|
||||
"categories": {
|
||||
"EI": "domestic express (EI)",
|
||||
"MP/RP": "(inter)voivodeship bullet (MP/RP)",
|
||||
"RO": "regional passenger (RO)",
|
||||
"PW": "empty passenger (PW)",
|
||||
"TM": "mass transport freight (TM)",
|
||||
"TK": "non-mass transport freight (TK)",
|
||||
"LT": "locomotive alone (LT)"
|
||||
},
|
||||
"action-random-number": "DRAW LAST DIGITS",
|
||||
"action-random-category": "DRAW A CATEGORY",
|
||||
|
||||
"rules": {
|
||||
"EI": "4 digits - ends within the range of 00-99",
|
||||
"MP/RP": "5 digits - ends within the range of 050-169",
|
||||
"RO": "5 digits - ends within the range of 200-999",
|
||||
"PW": "6 digits - '6' on the 3rd place; ends within the range of 000-899",
|
||||
"TM": "6 digits - '4' on the 3rd place; ends within the range of 000-899",
|
||||
"TK": "6 digits - '3' on the 3rd place; ends within the range of 000-899",
|
||||
"LT": "6 digits - '5' on the 3rd place; ends within the range of 000-899"
|
||||
"first-digit": "First digit:",
|
||||
"second-digit": "Second digit:",
|
||||
"third-digit": "Third digit:",
|
||||
"two-first-digits": "Two first digits:",
|
||||
"two-last-digits": "Two last digits:",
|
||||
"three-last-digits": "Three last digits:",
|
||||
"from-pool": "from pool of",
|
||||
"for-category": "for category",
|
||||
"for-region": "for region",
|
||||
"for-region-begin": "for the beginning construction region",
|
||||
"for-region-end": "for the terminating construction region",
|
||||
"from-range": "from range of"
|
||||
},
|
||||
|
||||
"categories": {
|
||||
"EI": "EI - domestic express",
|
||||
"EC": "EC - international express",
|
||||
"EN": "EN - domestic night express",
|
||||
|
||||
"MP": "MP - intervoivodeship bullet",
|
||||
"RP": "RP - voivodeship bullet",
|
||||
"MO": "MO - intervoivodeship regio",
|
||||
"RO": "RO - voivodeship regio",
|
||||
|
||||
"MM": "MM - international bullet",
|
||||
"MH": "MH - intervoivodeship bullet (night / hotel)",
|
||||
"RM": "RM - international voivodeship regio",
|
||||
"RA": "RA - voivodeship regio (urban)",
|
||||
|
||||
"PW": "PW - empty passenger",
|
||||
"PX": "PX - empty passenger test drive",
|
||||
|
||||
"TC": "TC - international freight (intermodal)",
|
||||
"TG": "TG - international freight (cargo)",
|
||||
"TR": "TR - international freight (no cargo)",
|
||||
"TD": "TD - domestic freight (intermodal)",
|
||||
"TM": "TM - domestic freight (cargo)",
|
||||
"TN": "TN - domestic freight (no cargo)",
|
||||
"TK": "TK - freight (stations & sidings)",
|
||||
"TS": "TS - empty freight test drive",
|
||||
|
||||
"LT": "LT - locomotive only",
|
||||
"LT-new": "LT - freight locomotive only",
|
||||
|
||||
"LP": "LP - passenger locomotive only",
|
||||
"LS": "LS - shunting locomotive",
|
||||
|
||||
"ZN": "ZN - inspection / diagnostic"
|
||||
}
|
||||
},
|
||||
"wiki": {
|
||||
"title": "LIST OF AVAILABLE VEHICLES",
|
||||
"action-vehicles": "TRACTION UNITS",
|
||||
"action-carriages": "CARRIAGES",
|
||||
"search": "Search for a vehicle...",
|
||||
"header": {
|
||||
"image": "Image",
|
||||
"type": "Name",
|
||||
"group": "Type group",
|
||||
"constructionType": "Construction",
|
||||
"coldStart": "Cold start",
|
||||
"length": "Length",
|
||||
"mass": "Mass",
|
||||
"maxSpeed": "Speed",
|
||||
"cargoCount": "Cargo count"
|
||||
"labels": {
|
||||
"vehicles": "Vehicles",
|
||||
"sort-by": "Sort by",
|
||||
"sort-direction": "Sort direction",
|
||||
"search-vehicle": "Find a vehicle",
|
||||
"search-vehicle-placeholder": "Input a vehicle name"
|
||||
},
|
||||
"loco-ezt": "EMU",
|
||||
"loco-szt": "DMU",
|
||||
"loco-s": "Diesel locomotive",
|
||||
"loco-e": "Electric locomotive",
|
||||
"car-passenger": "Passenger carriage",
|
||||
"car-cargo": "Frieght carriage"
|
||||
"filters": {
|
||||
"vehicles-all": "all",
|
||||
"vehicles-traction": "traction units",
|
||||
"vehicles-wagon": "wagons"
|
||||
},
|
||||
"sort-by": {
|
||||
"type": "name",
|
||||
"group": "type group",
|
||||
"length": "length",
|
||||
"weight": "mass",
|
||||
"maxSpeed": "speed",
|
||||
"coldStart": "cold start",
|
||||
"cargoCount": "cargo count"
|
||||
},
|
||||
"sort-direction": {
|
||||
"asc": "ascending",
|
||||
"desc": "descending"
|
||||
},
|
||||
"unit-electric": "EMU",
|
||||
"unit-diesel": "DMU",
|
||||
"loco-diesel": "Diesel locomotive",
|
||||
"loco-electric": "Electric locomotive",
|
||||
"wagon-passenger": "Passenger carriage",
|
||||
"wagon-freight": "Frieght carriage"
|
||||
},
|
||||
"realstock": {
|
||||
"title": "POLISH TRAIN COMPOSITIONS by",
|
||||
|
||||
@@ -9,18 +9,18 @@
|
||||
"version-check": "Strona jest kompletna dla wersji {version} symulatora TD2"
|
||||
},
|
||||
"inputs": {
|
||||
"title": "WYBIERZ POJAZD SZYNOWY",
|
||||
"title": "WYBIERZ POJAZD",
|
||||
"input-vehicle": "Wybierz pojazd trakcyjny",
|
||||
"input-carwagon": "Wybierz wagon",
|
||||
"cargo-title": "Ładunek (tylko wybrane towarowe)",
|
||||
"no-cargo-available": "brak dostępnych ładunków",
|
||||
"cargo-empty": "próżny",
|
||||
"loco-e": "ELEKTR.",
|
||||
"loco-s": "SPAL.",
|
||||
"loco-ezt": "EZT",
|
||||
"loco-szt": "SZT",
|
||||
"car-passenger": "PASAŻERSKIE",
|
||||
"car-cargo": "TOWAROWE",
|
||||
"loco-electric": "ELEKTR.",
|
||||
"loco-diesel": "SPAL.",
|
||||
"unit-electric": "EZT",
|
||||
"unit-diesel": "SZT",
|
||||
"wagon-passenger": "PASAŻERSKIE",
|
||||
"wagon-freight": "TOWAROWE",
|
||||
"action-add": "DODAJ NOWY",
|
||||
"action-swap": "ZAMIEŃ ZA",
|
||||
"real-stock": "REALNE ZESTAWIENIA"
|
||||
@@ -29,13 +29,14 @@
|
||||
"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 DO {0}",
|
||||
"loco-e": "ELEKTROWÓZ",
|
||||
"loco-s": "SPALINOWÓZ",
|
||||
"loco-ezt": "EZT",
|
||||
"loco-szt": "SZT",
|
||||
"car-passenger": "WAGON PASAŻERSKI",
|
||||
"car-cargo": "WAGON TOWAROWY",
|
||||
"sponsor-only": "* TYLKO DLA SPONSORÓW DO {0}",
|
||||
"team-only": "* TYLKO DLA ZESPOŁU TD2",
|
||||
"loco-electric": "ELEKTROWÓZ",
|
||||
"loco-diesel": "SPALINOWÓZ",
|
||||
"unit-electric": "EZT",
|
||||
"unit-diesel": "SZT",
|
||||
"wagon-passenger": "WAGON PASAŻERSKI",
|
||||
"wagon-freight": "WAGON TOWAROWY",
|
||||
"cabin": "Typ kabiny:",
|
||||
"construction": "Typ konstrukcji:"
|
||||
},
|
||||
@@ -47,6 +48,7 @@
|
||||
},
|
||||
"stocklist": {
|
||||
"title": "EDYTOR SKŁADU",
|
||||
"disclaimer": "Wartość poglądowa wzorowana na prędkościach maksymalnych poszczególnych pojazdów w zestawieniu. Może nie zgadzać się z prawdziwymi prędkościami eksploatacyjnymi w konkretnych konfiguracjach.",
|
||||
"alert-copied": "Skład został skopiowany do twojego schowka!",
|
||||
"alert-empty": "Lista pojazdów jest pusta!",
|
||||
"prompt-file": "Nazwij plik, a następnie pobierz do folderu Presets (Dokumenty/TTSK/TrainDriver2):",
|
||||
@@ -64,13 +66,16 @@
|
||||
"mass-accepted": "dopuszczalna",
|
||||
"length": "Długość",
|
||||
"vmax": "vMax",
|
||||
"coldstart-info": "Zimny start lokomotywy czołowej (tylko elektrowozy typów 303E i 203E)",
|
||||
"coldstart-info": "Zimny start",
|
||||
"doublemanning-info": "Podwójna obsada",
|
||||
"list-empty": "Lista pojazdów jest pusta!",
|
||||
"warning-not-suitable": "Lokomotywy EP07 i EP08 są przeznaczone jedynie do ruchu pasażerskiego!",
|
||||
|
||||
"warning-not-suitable": "Lokomotywy serii EP są przeznaczone jedynie do ruchu pasażerskiego!",
|
||||
"warning-passenger-too-long": "Maksymalna długość składów pasażerskich nie może przekraczać 350m!",
|
||||
"warning-freight-too-long": "Maksymalna długość składów innych niż pasażerskie nie może przekraczać 650m!",
|
||||
"warning-too-many-locos": "Ten skład posiada za dużo pojazdów trakcyjnych!",
|
||||
"warning-too-heavy": "Ten skład jest za ciężki! Sprawdź {href}",
|
||||
"warning-team-only-vehicle": "W zestawieniu znajduje się co najmniej jeden pojazd dostępny tylko dla członków zespołu TD2! ({0})",
|
||||
"acceptable-mass-docs": "dopuszczalne masy składów"
|
||||
},
|
||||
"stockgen": {
|
||||
@@ -91,57 +96,103 @@
|
||||
},
|
||||
"numgen": {
|
||||
"title": "GENERATOR NUMERU POCIĄGU",
|
||||
"subtitle": "Generuje realny numer pociągu na podstawie instrukcji Ir-11",
|
||||
"alert": "Numer został skopiowany do twojego schowka!",
|
||||
"start-region": "Początkowy obszar konstrukcyjny",
|
||||
"end-region": "Końcowy obszar konstrukcyjny",
|
||||
"start-region": "Obszar początkowy",
|
||||
"end-region": "Obszar końcowy",
|
||||
"train-category": "Kategoria pociągu",
|
||||
"number-info": "Wygenerowany numer pociągu:",
|
||||
"warning": "Wybierz kategorię oraz (opcjonalnie) obszary konstrukcyjne",
|
||||
"td2-wiki": "> Szczegółowe zasady numeracji (wikipedia TD2)",
|
||||
"td2-wiki-link": "https://wiki.td2.info.pl/index.php?title=Zasady_numeracji_poci%C4%85g%C3%B3w",
|
||||
"td2-wiki": "> Szczegółowe zasady numeracji (wątek forum)",
|
||||
"td2-wiki-link": "https://td2.info.pl/ogloszenia/nowe-kategorie-pociagow-w-symulatorze/",
|
||||
"action-random-region": "LOSUJ OBSZARY",
|
||||
"action-random-number": "LOSUJ NUMER",
|
||||
"categories": {
|
||||
"EI": "ekspres krajowy (EI)",
|
||||
"MP/RP": "(między)wojewódzki pośpieszny (MP/RP)",
|
||||
"RO": "wojewódzki osobowy (RO)",
|
||||
"PW": "próżny \"służbowy\" (PW)",
|
||||
"TM": "towarowy do przewozów masowych (TM)",
|
||||
"TK": "towarowy do obsługi stacji (TK)",
|
||||
"LT": "lokomotywa luzem (LT)"
|
||||
},
|
||||
"action-random-number": "LOSUJ KOŃCÓWKĘ",
|
||||
"action-random-category": "LOSUJ KATEGORIĘ",
|
||||
|
||||
"rules": {
|
||||
"EI": "4 cyfry - końcówka z przedziału 00-99",
|
||||
"MP/RP": "5 cyfr - końcówka z przedziału 050-169",
|
||||
"RO": "5 cyfr - końcówka z przedziału 200-999",
|
||||
"PW": "6 cyfr - '6' na 3. miejscu; końcówka z przedziału 000-899",
|
||||
"TM": "6 cyfr - '4' na 3. miejscu; końcówka z przedziału 000-899",
|
||||
"TK": "6 cyfr - '3' na 3. miejscu; końcówka z przedziału 000-899",
|
||||
"LT": "6 cyfr - '5' na 3. miejscu; końcówka z przedziału 000-899"
|
||||
"first-digit": "Pierwsza cyfra:",
|
||||
"second-digit": "Druga cyfra:",
|
||||
"third-digit": "Trzecia cyfra:",
|
||||
"two-first-digits": "Dwie pierwsze cyfry:",
|
||||
"two-last-digits": "Dwie ostatnie cyfry:",
|
||||
"three-last-digits": "Trzy ostatnie cyfry:",
|
||||
"from-pool": "z puli",
|
||||
"for-category": "dla kategorii",
|
||||
"for-region": "dla obszaru",
|
||||
"for-region-begin": "dla początkowego obszaru konstrukcyjnego",
|
||||
"for-region-end": "dla końcowego obszaru konstrukcyjnego",
|
||||
"from-range": "z przedziału"
|
||||
},
|
||||
|
||||
"categories": {
|
||||
"EI": "EI - ekspres krajowy",
|
||||
"EC": "EC - ekspres międzynarodowy",
|
||||
"EN": "EN - ekspres krajowy nocny",
|
||||
|
||||
"MP": "MP - międzywoj. pośpieszny",
|
||||
"RP": "RP - wojewódzki pośpieszny",
|
||||
"MO": "MO - międzywoj. osobowy",
|
||||
"RO": "RO - wojewódzki osobowy",
|
||||
|
||||
"MM": "MM - międzynar. pośpieszny",
|
||||
"MH": "MH - międzywoj. pośpieszny hotelowy",
|
||||
"RM": "RM - woj. osobowy międzynarodowy",
|
||||
"RA": "RA - woj. osobowy algomeracyjny",
|
||||
|
||||
"PW": "PW - pasażerski próżny - służbowy",
|
||||
"PX": "PX - pasażerski próżny próbny",
|
||||
|
||||
"TC": "TC - towarowy międzynarodowy intermodalny",
|
||||
"TG": "TG - towarowy międzynarodowy masowy",
|
||||
"TR": "TR - towarowy międzynarodowy niemasowy",
|
||||
"TD": "TD - towarowy krajowy intermodalny",
|
||||
"TM": "TM - towarowy krajowy masowy",
|
||||
"TN": "TN - towarowy krajowy niemasowy",
|
||||
"TK": "TK - towarowy (stacje i bocznice)",
|
||||
"TS": "TS - towarowy próżny próbny",
|
||||
|
||||
"LT": "LT - lokomotywa luzem",
|
||||
"LT-new": "LT - lokomotywa towarowa luzem",
|
||||
|
||||
"LP": "LP - lokomotywa pasażerska luzem",
|
||||
"LS": "LS - lokomotywa manewrowa",
|
||||
|
||||
"ZN": "ZN - inspekcyjny / diagnostyczny"
|
||||
}
|
||||
},
|
||||
"wiki": {
|
||||
"title": "LISTA DOSTĘPNYCH POJAZDÓW",
|
||||
"action-vehicles": "POJ. TRAKCYJNE",
|
||||
"action-carriages": "WAGONY",
|
||||
"search": "Wyszukaj pojazd...",
|
||||
"header": {
|
||||
"image": "Zdjęcie",
|
||||
"type": "Nazwa",
|
||||
"group": "Rodzaj",
|
||||
"constructionType": "Konstrukcja",
|
||||
"coldStart": "Zimny start",
|
||||
"length": "Długość",
|
||||
"mass": "Masa",
|
||||
"maxSpeed": "Prędkość",
|
||||
"cargoCount": "Ładunki"
|
||||
"labels": {
|
||||
"vehicles": "Pojazdy",
|
||||
"sort-by": "Sortuj wg",
|
||||
"sort-direction": "Kierunek sortowania",
|
||||
"search-vehicle": "Wyszukaj pojazd",
|
||||
"search-vehicle-placeholder": "Wpisz nazwę pojazdu"
|
||||
},
|
||||
"loco-ezt": "EZT",
|
||||
"loco-szt": "SZT",
|
||||
"loco-s": "Spalinowóz",
|
||||
"loco-e": "Elektrowóz",
|
||||
"car-passenger": "Wagon pasażerski",
|
||||
"car-cargo": "Wagon towarowy"
|
||||
"filters": {
|
||||
"vehicles-all": "wszystkie",
|
||||
"vehicles-traction": "trakcyjne",
|
||||
"vehicles-wagon": "wagony"
|
||||
},
|
||||
"sort-by": {
|
||||
"type": "nazwa",
|
||||
"group": "rodzaj",
|
||||
"length": "długość",
|
||||
"weight": "masa",
|
||||
"maxSpeed": "prędkość",
|
||||
"coldStart": "zimny start",
|
||||
"cargoCount": "ładunki"
|
||||
},
|
||||
"sort-direction": {
|
||||
"asc": "rosnąco",
|
||||
"desc": "malejąco"
|
||||
},
|
||||
"loco-diesel": "Spalinowóz",
|
||||
"loco-electric": "Elektrowóz",
|
||||
"unit-electric": "EZT",
|
||||
"unit-diesel": "SZT",
|
||||
"wagon-passenger": "Wagon pasażerski",
|
||||
"wagon-freight": "Wagon towarowy"
|
||||
},
|
||||
"realstock": {
|
||||
"title": "ZESTAWIENIA REALNE by",
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
import { registerSW } from "virtual:pwa-register";
|
||||
import { createApp } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
|
||||
import App from "./App.vue";
|
||||
import i18n from "./i18n-setup";
|
||||
import App from './App.vue';
|
||||
import i18n from './i18n-setup';
|
||||
const pinia = createPinia();
|
||||
|
||||
registerSW({
|
||||
immediate: true,
|
||||
});
|
||||
|
||||
createApp(App).use(pinia).use(i18n).mount("#app");
|
||||
createApp(App).use(pinia).use(i18n).mount('#app');
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import { defineComponent } from "vue";
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
methods: {
|
||||
getIconURL(name: string, ext = "svg"): string {
|
||||
getIconURL(name: string, ext = 'svg'): string {
|
||||
return `/images/icon-${name}.${ext}`;
|
||||
},
|
||||
|
||||
getThumbnailURL(vehicleType: string, size: "small" | "large") {
|
||||
return `${
|
||||
import.meta.env.VITE_API_DEV === "1"
|
||||
? "http://localhost:5500"
|
||||
: "https://spythere.github.io/api"
|
||||
}/td2/images/${vehicleType}--${size == "small" ? 300 : 800}px.jpg`;
|
||||
getThumbnailURL(vehicleType: string, size: 'small' | 'large') {
|
||||
return `https://static.spythere.eu/images/${vehicleType}--${size == 'small' ? 300 : 800}px.jpg`;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { defineComponent } from 'vue';
|
||||
import { useStore } from '../store';
|
||||
import { ICargo, ICarWagon, ILocomotive, IStock, Vehicle } from '../types';
|
||||
import { isLocomotive } from '../utils/vehicleUtils';
|
||||
import { ICarWagon, ILocomotive, IStock, ICargo, IVehicle } from '../types';
|
||||
import { isTractionUnit } from '../utils/vehicleUtils';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
@@ -15,39 +15,33 @@ export default defineComponent({
|
||||
return `${Math.random().toString(36).slice(5)}`;
|
||||
},
|
||||
|
||||
getStockObject(vehicle: Vehicle, cargo?: ICargo | null, count = 1): IStock {
|
||||
const isLoco = isLocomotive(vehicle);
|
||||
|
||||
getStockObject(vehicle: IVehicle, cargo?: ICargo | null): IStock {
|
||||
return {
|
||||
id: this.getStockId(),
|
||||
type: vehicle.type,
|
||||
length: vehicle.length,
|
||||
mass: vehicle.mass,
|
||||
maxSpeed: vehicle.maxSpeed,
|
||||
isLoco,
|
||||
cargo: !isLoco && vehicle.loadable && cargo ? cargo : undefined,
|
||||
count,
|
||||
imgSrc: vehicle.imageSrc,
|
||||
useType: isLoco ? vehicle.power : vehicle.useType,
|
||||
isSponsorsOnly: vehicle.isSponsorsOnly,
|
||||
constructionType: vehicle.constructionType,
|
||||
sponsorsOnlyTimestamp: vehicle.sponsorsOnlyTimestamp,
|
||||
vehicleRef: vehicle,
|
||||
cargo: !isTractionUnit(vehicle) && vehicle.loadable && cargo ? cargo : undefined,
|
||||
};
|
||||
},
|
||||
|
||||
addVehicle(vehicle: Vehicle | null, cargo?: ICargo | null) {
|
||||
addVehicle(vehicle: IVehicle | null, cargo?: ICargo | null) {
|
||||
if (!vehicle) return;
|
||||
|
||||
const stock = this.getStockObject(vehicle, cargo);
|
||||
|
||||
if (stock.isLoco && !this.store.stockList[0]?.isLoco) this.store.stockList.unshift(stock);
|
||||
if (
|
||||
isTractionUnit(stock.vehicleRef) &&
|
||||
this.store.stockList.length > 0 &&
|
||||
!isTractionUnit(this.store.stockList[0].vehicleRef)
|
||||
)
|
||||
this.store.stockList.unshift(stock);
|
||||
else this.store.stockList.push(stock);
|
||||
},
|
||||
|
||||
addLocomotive(loco: ILocomotive) {
|
||||
const stockObj = this.getStockObject(loco);
|
||||
|
||||
if (this.store.stockList.length > 0 && !this.store.stockList[0].isLoco) this.store.stockList.unshift(stockObj);
|
||||
if (this.store.stockList.length > 0 && !isTractionUnit(this.store.stockList[0].vehicleRef))
|
||||
this.store.stockList.unshift(stockObj);
|
||||
else this.store.stockList.push(stockObj);
|
||||
},
|
||||
|
||||
@@ -70,22 +64,30 @@ export default defineComponent({
|
||||
this.store.swapVehicles = false;
|
||||
|
||||
stockArray.forEach((type, i) => {
|
||||
let vehicle: Vehicle | null = null;
|
||||
let vehicle: IVehicle | null = null;
|
||||
let vehicleCargo: ICargo | null = null;
|
||||
|
||||
if (/^(EU|EP|ET|SM|EN|2EN|SN)/.test(type)) {
|
||||
const [locoType, coldStart] = type.split(',');
|
||||
const isTractionUnit = /^([a-zA-Z\d]{0,}-\d{0,})/.test(type);
|
||||
|
||||
console.log(type, isTractionUnit);
|
||||
|
||||
if (isTractionUnit) {
|
||||
const [locoType, spawnProps] = type.split(',');
|
||||
vehicle = this.store.locoDataList.find((loco) => loco.type == locoType) || null;
|
||||
|
||||
if (i == 0 && coldStart == 'c') this.store.isColdStart = true;
|
||||
// Spawn settings
|
||||
if (i == 0 && spawnProps) {
|
||||
this.store.isColdStart = spawnProps.includes('c');
|
||||
this.store.isDoubleManned = spawnProps.includes('d');
|
||||
}
|
||||
} else {
|
||||
const [carType, cargo] = type.split(':');
|
||||
vehicle = this.store.carDataList.find((car) => car.type == carType) || null;
|
||||
|
||||
if (cargo) vehicleCargo = vehicle?.cargoList.find((c) => c.id == cargo) || null;
|
||||
if (cargo) vehicleCargo = vehicle?.cargoTypes.find((c) => c.id == cargo) || null;
|
||||
}
|
||||
|
||||
if (!vehicle) console.log('Brak pojazdu:', type);
|
||||
if (!vehicle) console.warn('Brak pojazdu / rodzaj pojazdu źle wczytany:', type);
|
||||
|
||||
this.addVehicle(vehicle, vehicleCargo);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { defineComponent } from 'vue';
|
||||
import { useStore } from '../store';
|
||||
import { ICarWagon, ILocomotive, IStock, Vehicle } from '../types';
|
||||
import { isLocomotive } from '../utils/vehicleUtils';
|
||||
import { ICarWagon, ILocomotive, IStock, IVehicle, LocoGroupType, WagonGroupType } from '../types';
|
||||
import { isTractionUnit } from '../utils/vehicleUtils';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
@@ -14,40 +14,37 @@ export default defineComponent({
|
||||
|
||||
methods: {
|
||||
previewStock(stock: IStock) {
|
||||
if (this.store.chosenVehicle?.imageSrc != stock.imgSrc) this.store.imageLoading = true;
|
||||
const vehicleRef = stock.vehicleRef;
|
||||
|
||||
if (stock.isLoco) {
|
||||
const chosenLoco = this.store.locoDataList.find((v) => v.type == stock.type) || null;
|
||||
this.store.chosenVehicle = chosenLoco;
|
||||
this.store.chosenLoco = chosenLoco;
|
||||
this.store.chosenVehicle = vehicleRef;
|
||||
|
||||
if (isTractionUnit(vehicleRef)) {
|
||||
this.store.chosenLoco = vehicleRef;
|
||||
this.store.chosenCargo = null;
|
||||
this.store.chosenLocoPower = stock.useType;
|
||||
this.store.chosenLocoGroup = vehicleRef.group as LocoGroupType;
|
||||
} else {
|
||||
const chosenCar = this.store.carDataList.find((v) => v.type == stock.type) || null;
|
||||
this.store.chosenVehicle = chosenCar;
|
||||
this.store.chosenCar = chosenCar;
|
||||
|
||||
this.store.chosenCar = vehicleRef;
|
||||
this.store.chosenCargo = stock.cargo || null;
|
||||
this.store.chosenCarUseType = stock.useType;
|
||||
this.store.chosenCarGroup = vehicleRef.group as WagonGroupType;
|
||||
}
|
||||
},
|
||||
|
||||
previewLocomotive(loco: ILocomotive) {
|
||||
this.store.chosenLoco = loco;
|
||||
this.store.chosenVehicle = loco;
|
||||
this.store.chosenLocoPower = loco.power;
|
||||
this.store.chosenLocoGroup = loco.group;
|
||||
},
|
||||
|
||||
previewCarWagon(carWagon: ICarWagon) {
|
||||
this.store.chosenCar = carWagon;
|
||||
this.store.chosenCarUseType = carWagon.useType;
|
||||
this.store.chosenCarGroup = carWagon.group;
|
||||
this.store.chosenVehicle = carWagon;
|
||||
|
||||
this.store.chosenCargo = null;
|
||||
},
|
||||
|
||||
previewVehicle(vehicle: Vehicle) {
|
||||
if (isLocomotive(vehicle)) this.previewLocomotive(vehicle);
|
||||
previewVehicle(vehicle: IVehicle) {
|
||||
if (isTractionUnit(vehicle)) this.previewLocomotive(vehicle);
|
||||
else this.previewCarWagon(vehicle);
|
||||
},
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import { defineComponent } from "vue";
|
||||
import { useStore } from "../store";
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const store = useStore();
|
||||
|
||||
return {
|
||||
store,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
trainTooLong() {
|
||||
return (
|
||||
(this.store.totalLength > 350 && this.store.isTrainPassenger) ||
|
||||
(this.store.totalLength > 650 && !this.store.isTrainPassenger)
|
||||
);
|
||||
},
|
||||
|
||||
trainTooHeavy() {
|
||||
return (
|
||||
this.store.acceptableMass &&
|
||||
this.store.totalMass > this.store.acceptableMass
|
||||
);
|
||||
},
|
||||
|
||||
locoNotSuitable() {
|
||||
return (
|
||||
!this.store.isTrainPassenger &&
|
||||
this.store.stockList.length > 1 &&
|
||||
!this.store.stockList.every((stock) => stock.isLoco) &&
|
||||
this.store.stockList.some(
|
||||
(stock) => stock.isLoco && stock.type.startsWith("EP"),
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
tooManyLocomotives() {
|
||||
return (
|
||||
this.store.stockList.reduce((acc, stock) => {
|
||||
if (stock.isLoco) acc += stock.count;
|
||||
return acc;
|
||||
}, 0) > 2
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,72 +1,147 @@
|
||||
import { IStockData, IStore } from './types';
|
||||
import {
|
||||
IVehiclesData,
|
||||
ICarWagon,
|
||||
ILocomotive,
|
||||
ICargo,
|
||||
IVehicle,
|
||||
IStock,
|
||||
IRealComposition,
|
||||
LocoGroupType,
|
||||
WagonGroupType,
|
||||
} from './types';
|
||||
import { defineStore } from 'pinia';
|
||||
import {
|
||||
acceptableMass,
|
||||
acceptableWeight,
|
||||
carDataList,
|
||||
chosenRealStock,
|
||||
isTractionUnit,
|
||||
isTrainPassenger,
|
||||
locoDataList,
|
||||
maxStockSpeed,
|
||||
totalLength,
|
||||
totalMass,
|
||||
totalWeight,
|
||||
} from './utils/vehicleUtils';
|
||||
|
||||
import i18n from './i18n-setup';
|
||||
import http from './http';
|
||||
|
||||
export const useStore = defineStore({
|
||||
id: 'store',
|
||||
state: () =>
|
||||
({
|
||||
chosenCar: null,
|
||||
chosenLoco: null,
|
||||
chosenCargo: null,
|
||||
chosenVehicle: null,
|
||||
state: () => ({
|
||||
chosenCar: null as ICarWagon | null,
|
||||
chosenLoco: null as ILocomotive | null,
|
||||
chosenCargo: null as ICargo | null,
|
||||
chosenVehicle: null as IVehicle | null,
|
||||
|
||||
isColdStart: false,
|
||||
isColdStart: false,
|
||||
isDoubleManned: false,
|
||||
|
||||
showSupporter: false,
|
||||
imageLoading: false,
|
||||
chosenLocoGroup: 'loco-electric' as LocoGroupType,
|
||||
chosenCarGroup: 'wagon-passenger' as WagonGroupType,
|
||||
|
||||
chosenLocoPower: 'loco-e',
|
||||
chosenCarUseType: 'car-passenger',
|
||||
stockList: [] as IStock[],
|
||||
cargoOptions: [] as any[][],
|
||||
|
||||
stockList: [],
|
||||
cargoOptions: [],
|
||||
swapVehicles: false,
|
||||
|
||||
readyStockList: [],
|
||||
chosenStockListIndex: -1,
|
||||
|
||||
swapVehicles: false,
|
||||
vehiclePreviewSrc: '',
|
||||
|
||||
chosenStockListIndex: -1,
|
||||
chosenRealStockName: undefined,
|
||||
stockSectionMode: 'stock-list',
|
||||
|
||||
vehiclePreviewSrc: '',
|
||||
isRandomizerCardOpen: false,
|
||||
isRealStockListCardOpen: false,
|
||||
|
||||
stockSectionMode: 'stock-list',
|
||||
vehiclesData: undefined as IVehiclesData | undefined,
|
||||
|
||||
isRandomizerCardOpen: false,
|
||||
isRealStockListCardOpen: false,
|
||||
|
||||
stockData: undefined,
|
||||
|
||||
lastFocusedElement: null,
|
||||
}) as IStore,
|
||||
lastFocusedElement: null as HTMLElement | null,
|
||||
}),
|
||||
|
||||
getters: {
|
||||
locoDataList: (state) => locoDataList(state),
|
||||
carDataList: (state) => carDataList(state),
|
||||
vehicleDataList: (state) => [...locoDataList(state), ...carDataList(state)],
|
||||
totalMass: (state) => totalMass(state),
|
||||
totalLength: (state) => totalLength(state),
|
||||
maxStockSpeed: (state) => maxStockSpeed(state),
|
||||
isTrainPassenger: (state) => isTrainPassenger(state),
|
||||
chosenRealStock: (state) => chosenRealStock(state),
|
||||
acceptableMass: (state) => acceptableMass(state),
|
||||
locoDataList: (state) => locoDataList(state.vehiclesData),
|
||||
carDataList: (state) => carDataList(state.vehiclesData),
|
||||
vehicleDataList: (state) => [
|
||||
...locoDataList(state.vehiclesData),
|
||||
...carDataList(state.vehiclesData),
|
||||
],
|
||||
totalWeight: (state) => totalWeight(state.stockList),
|
||||
totalLength: (state) => totalLength(state.stockList),
|
||||
maxStockSpeed: (state) => maxStockSpeed(state.stockList),
|
||||
isTrainPassenger: (state) => isTrainPassenger(state.stockList),
|
||||
acceptableWeight: (state) => acceptableWeight(state.stockList),
|
||||
|
||||
realCompositionList: (state) => {
|
||||
if (!state.vehiclesData) return [];
|
||||
|
||||
return Object.keys(state.vehiclesData.realCompositions).reduce<IRealComposition[]>(
|
||||
(acc, key) => {
|
||||
const [type, number, ...name] = key.split(' ');
|
||||
|
||||
const obj = {
|
||||
number: number.replace(/_/g, '/'),
|
||||
name: name.join(' '),
|
||||
stockString: state.vehiclesData!.realCompositions[key],
|
||||
type,
|
||||
};
|
||||
|
||||
acc.push({
|
||||
stockId: `${obj.type} ${obj.number} ${obj.name}`,
|
||||
...obj,
|
||||
});
|
||||
|
||||
return acc;
|
||||
},
|
||||
[]
|
||||
);
|
||||
},
|
||||
|
||||
stockSupportsColdStart: (state) => {
|
||||
if (state.stockList.length == 0) return false;
|
||||
if (!isTractionUnit(state.stockList[0].vehicleRef)) return false;
|
||||
|
||||
const headingLoco = state.stockList[0];
|
||||
|
||||
return (
|
||||
state.vehiclesData?.vehicleProps.find(
|
||||
(stock) => stock.type == headingLoco.vehicleRef.constructionType
|
||||
)?.coldStart ?? false
|
||||
);
|
||||
},
|
||||
|
||||
stockSupportsDoubleManning: (state) => {
|
||||
if (state.stockList.length == 0) return false;
|
||||
if (!isTractionUnit(state.stockList[0].vehicleRef)) return false;
|
||||
|
||||
const headingLoco = state.stockList[0];
|
||||
|
||||
return (
|
||||
state.vehiclesData?.vehicleProps.find(
|
||||
(stock) => stock.type == headingLoco.vehicleRef.constructionType
|
||||
)?.doubleManned ?? false
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
actions: {
|
||||
async fetchStockInfoData() {
|
||||
const stockData = (await http.get<IStockData>('td2/data/stockInfo.json')).data;
|
||||
this.stockData = stockData;
|
||||
async fetchVehiclesAPI() {
|
||||
try {
|
||||
const vehiclesData = (await http.get<IVehiclesData>('/vehicles')).data;
|
||||
this.vehiclesData = vehiclesData;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
|
||||
async setupAPIData() {
|
||||
await this.fetchVehiclesAPI();
|
||||
this.mergeBackendTranslations();
|
||||
},
|
||||
|
||||
async mergeBackendTranslations() {
|
||||
if (!this.vehiclesData) return;
|
||||
|
||||
i18n.global.mergeLocaleMessage('pl', this.vehiclesData.vehicleLocales.pl);
|
||||
i18n.global.mergeLocaleMessage('en', this.vehiclesData.vehicleLocales.en);
|
||||
},
|
||||
|
||||
handleRouting() {
|
||||
|
||||
@@ -3,14 +3,17 @@ $breakpointSm: 550px;
|
||||
|
||||
$bgColor: #2b3552;
|
||||
$textColor: #fff;
|
||||
$secondaryColor: #222;
|
||||
$secondaryColor: #1b1b1b;
|
||||
$accentColor: #e4c428;
|
||||
|
||||
$sponsorColor: gold;
|
||||
$teamColor: #ff4848;
|
||||
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src:
|
||||
url('$fonts/Lato-Light.woff2') format('woff2'),
|
||||
url('$fonts/Lato-Light.woff') format('woff');
|
||||
url('/fonts/Lato-Light.woff2') format('woff2'),
|
||||
url('/fonts/Lato-Light.woff') format('woff');
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
@@ -19,8 +22,8 @@ $accentColor: #e4c428;
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src:
|
||||
url('$fonts/Lato-Bold.woff2') format('woff2'),
|
||||
url('$fonts/Lato-Bold.woff') format('woff');
|
||||
url('/fonts/Lato-Bold.woff2') format('woff2'),
|
||||
url('/fonts/Lato-Bold.woff') format('woff');
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
@@ -29,8 +32,8 @@ $accentColor: #e4c428;
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src:
|
||||
url('$fonts/Lato-Regular.woff2') format('woff2'),
|
||||
url('$fonts/Lato-Regular.woff') format('woff');
|
||||
url('/fonts/Lato-Regular.woff2') format('woff2'),
|
||||
url('/fonts/Lato-Regular.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
@@ -114,11 +117,28 @@ button {
|
||||
}
|
||||
}
|
||||
|
||||
[data-tooltip]:hover::after,
|
||||
[data-tooltip]:focus::after {
|
||||
position: absolute;
|
||||
transform: translateX(10px);
|
||||
|
||||
content: attr(data-tooltip);
|
||||
color: white;
|
||||
background: black;
|
||||
padding: 0.5em;
|
||||
max-width: 300px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
[data-tooltip] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.4em 0.75em;
|
||||
|
||||
outline: none;
|
||||
background-color: #222;
|
||||
background-color: $secondaryColor;
|
||||
border-radius: 8px;
|
||||
font-weight: bold;
|
||||
|
||||
@@ -190,12 +210,11 @@ input[type='number'] {
|
||||
outline: none;
|
||||
|
||||
padding: 0.25em 0.35em;
|
||||
height: 100%;
|
||||
|
||||
color: white;
|
||||
font-size: 1em;
|
||||
|
||||
width: 18em;
|
||||
|
||||
&:focus-visible {
|
||||
border-color: $accentColor;
|
||||
}
|
||||
@@ -227,6 +246,13 @@ ul {
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 3px;
|
||||
background-color: white;
|
||||
outline: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.g-card {
|
||||
position: fixed;
|
||||
top: 1em;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import "./global.scss";
|
||||
@import './global.scss';
|
||||
|
||||
.tab {
|
||||
height: 100%;
|
||||
@@ -8,12 +8,18 @@
|
||||
padding: 0.5em 1em;
|
||||
|
||||
background-color: $secondaryColor;
|
||||
text-align: center;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
color: white;
|
||||
font-size: 1.35em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0.5em 0 0 0;
|
||||
font-size: 1.15em;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
button {
|
||||
@@ -58,13 +64,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 3px;
|
||||
background-color: white;
|
||||
outline: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 470px) {
|
||||
.tab_attributes {
|
||||
label {
|
||||
|
||||
@@ -1,126 +1,87 @@
|
||||
export type Vehicle = ILocomotive | ICarWagon;
|
||||
export type IVehicle = ILocomotive | ICarWagon;
|
||||
export type StockSectionMode = 'STOCK_LIST' | 'STOCK_GENERATOR';
|
||||
|
||||
export interface IStore {
|
||||
chosenCar: ICarWagon | null;
|
||||
chosenLoco: ILocomotive | null;
|
||||
chosenCargo: ICargo | null;
|
||||
chosenVehicle: Vehicle | null;
|
||||
export type LocoGroupType = 'loco-electric' | 'loco-diesel' | 'unit-electric' | 'unit-diesel';
|
||||
export type WagonGroupType = 'wagon-passenger' | 'wagon-freight';
|
||||
export type VehicleGroupType = LocoGroupType | WagonGroupType;
|
||||
export type RestrictionType = 'sponsorOnly' | 'teamOnly';
|
||||
|
||||
isColdStart: boolean;
|
||||
|
||||
showSupporter: boolean;
|
||||
imageLoading: boolean;
|
||||
|
||||
chosenLocoPower: string;
|
||||
chosenCarUseType: string;
|
||||
|
||||
stockList: IStock[];
|
||||
readyStockList: IReadyStockItem[];
|
||||
cargoOptions: any[][];
|
||||
|
||||
chosenStockListIndex: number;
|
||||
chosenRealStockName?: string;
|
||||
|
||||
swapVehicles: boolean;
|
||||
vehiclePreviewSrc: string;
|
||||
|
||||
isRandomizerCardOpen: boolean;
|
||||
isRealStockListCardOpen: boolean;
|
||||
|
||||
stockSectionMode: 'stock-list' | 'stock-generator' | 'number-generator' | 'wiki-list';
|
||||
stockData?: IStockData;
|
||||
|
||||
lastFocusedElement: HTMLElement | null;
|
||||
}
|
||||
|
||||
export type TLocoGroup = 'loco-e' | 'loco-s' | 'loco-ezt' | 'loco-szt';
|
||||
export type TCarWagonGroup = 'car-passenger' | 'car-cargo';
|
||||
|
||||
export interface IStockProps {
|
||||
export interface IVehicleProps {
|
||||
type: string;
|
||||
speed: number;
|
||||
length: number;
|
||||
mass: number;
|
||||
cargo: string;
|
||||
weight: number;
|
||||
cargoTypes?: ICargo[];
|
||||
coldStart?: boolean;
|
||||
doubleManned?: boolean;
|
||||
}
|
||||
|
||||
export interface IStockData {
|
||||
version: string;
|
||||
export interface ICargo {
|
||||
id: string;
|
||||
weight: number;
|
||||
}
|
||||
|
||||
export interface IVehiclesData {
|
||||
simulatorVersion: string;
|
||||
|
||||
generator: {
|
||||
passenger: any;
|
||||
cargo: {
|
||||
[key: string]: string[];
|
||||
};
|
||||
};
|
||||
|
||||
info: {
|
||||
'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][];
|
||||
vehicleList: any[][];
|
||||
|
||||
vehicleProps: IVehicleProps[];
|
||||
|
||||
vehicleLocales: {
|
||||
pl: {
|
||||
cargo: Record<string, string>;
|
||||
usage: Record<string, string>;
|
||||
};
|
||||
en: {
|
||||
cargo: Record<string, string>;
|
||||
usage: Record<string, string>;
|
||||
};
|
||||
};
|
||||
|
||||
props: IStockProps[];
|
||||
|
||||
usage: { [key: string]: string };
|
||||
realCompositions: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface ILocomotive {
|
||||
type: string;
|
||||
power: TLocoGroup;
|
||||
group: TLocoGroup;
|
||||
group: LocoGroupType;
|
||||
constructionType: string;
|
||||
cabinType: string;
|
||||
maxSpeed: number;
|
||||
isSponsorsOnly: boolean;
|
||||
sponsorsOnlyTimestamp: number;
|
||||
imageSrc: string;
|
||||
|
||||
mass: number;
|
||||
weight: number;
|
||||
length: number;
|
||||
coldStart: boolean;
|
||||
doubleManned: boolean;
|
||||
sponsorOnlyTimestamp: number;
|
||||
teamOnly: boolean;
|
||||
}
|
||||
|
||||
export interface ICarWagon {
|
||||
type: string;
|
||||
useType: TCarWagonGroup;
|
||||
group: TCarWagonGroup;
|
||||
group: WagonGroupType;
|
||||
constructionType: string;
|
||||
loadable: boolean;
|
||||
isSponsorsOnly: boolean;
|
||||
sponsorsOnlyTimestamp: number;
|
||||
maxSpeed: number;
|
||||
imageSrc: string;
|
||||
|
||||
mass: number;
|
||||
weight: number;
|
||||
length: number;
|
||||
cargoList: { id: string; totalMass: number }[];
|
||||
}
|
||||
|
||||
export interface ICargo {
|
||||
id: string;
|
||||
totalMass: number;
|
||||
cargoTypes: ICargo[];
|
||||
sponsorOnlyTimestamp: number;
|
||||
teamOnly: boolean;
|
||||
}
|
||||
|
||||
export interface IStock {
|
||||
id: string;
|
||||
type: string;
|
||||
useType: string;
|
||||
constructionType: string;
|
||||
length: number;
|
||||
mass: number;
|
||||
maxSpeed: number;
|
||||
cargo?: { id: string; totalMass: number };
|
||||
isLoco: boolean;
|
||||
isSponsorsOnly: boolean;
|
||||
sponsorsOnlyTimestamp: number;
|
||||
count: number;
|
||||
imgSrc?: string;
|
||||
vehicleRef: IVehicle;
|
||||
cargo?: ICargo;
|
||||
}
|
||||
|
||||
export interface IReadyStockItem {
|
||||
export interface IRealComposition {
|
||||
stockId: string;
|
||||
stockString: string;
|
||||
type: string;
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
const supportedConstructions = ["303e", "203e"];
|
||||
|
||||
export function locoSupportsColdStart(constructionType: string) {
|
||||
return new RegExp(`(${supportedConstructions.join("|")})`).test(
|
||||
constructionType,
|
||||
);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import speedLimitTable from "../constants/speedLimits.json";
|
||||
export type LocoType = keyof typeof speedLimitTable;
|
||||
|
||||
export const calculateSpeedLimit = (
|
||||
locoType: LocoType,
|
||||
stockMass: number,
|
||||
isTrainPassenger: boolean,
|
||||
) => {
|
||||
const speedTable =
|
||||
speedLimitTable[locoType][isTrainPassenger ? "passenger" : "cargo"];
|
||||
|
||||
if (!speedTable) return undefined;
|
||||
|
||||
let speedLimit = 0;
|
||||
for (const mass in speedTable)
|
||||
if (stockMass > Number(mass)) speedLimit = (speedTable as any)[mass];
|
||||
|
||||
return speedLimit;
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
import speedLimits from '../constants/speedLimits.json';
|
||||
import massLimits from '../constants/massLimits.json';
|
||||
|
||||
export type SpeedLimitLocoType = keyof typeof speedLimits;
|
||||
export type MassLimitLocoType = keyof typeof massLimits;
|
||||
|
||||
export function calculateSpeedLimit(
|
||||
locoType: SpeedLimitLocoType,
|
||||
stockTotalWeight: number,
|
||||
stockCount: number,
|
||||
isTrainPassenger: boolean
|
||||
) {
|
||||
if (speedLimits[locoType] === undefined) return 0;
|
||||
|
||||
if (stockCount == 1) return speedLimits[locoType]['none'];
|
||||
|
||||
const stockType = isTrainPassenger ? 'passenger' : 'cargo';
|
||||
const speedTable = speedLimits[locoType][stockType];
|
||||
|
||||
if (!speedTable) return undefined;
|
||||
|
||||
let speedLimit = 0;
|
||||
for (const mass in speedTable)
|
||||
if (stockTotalWeight > Number(mass)) speedLimit = (speedTable as any)[mass];
|
||||
|
||||
return speedLimit;
|
||||
}
|
||||
|
||||
export function calculateMassLimit(locoType: MassLimitLocoType, isTrainPassenger: boolean) {
|
||||
if (massLimits[locoType] === undefined) return 0;
|
||||
|
||||
return massLimits[locoType][isTrainPassenger ? 0 : 1] || 0;
|
||||
}
|
||||
@@ -1,158 +1,146 @@
|
||||
import { EVehicleUseType } from '../enums/EVehicleUseType';
|
||||
import { ICarWagon, ILocomotive, IStore, TCarWagonGroup, TLocoGroup } from '../types';
|
||||
import { LocoType, calculateSpeedLimit } from './speedLimitUtils';
|
||||
import {
|
||||
ICarWagon,
|
||||
ILocomotive,
|
||||
IStock,
|
||||
IVehiclesData,
|
||||
LocoGroupType,
|
||||
WagonGroupType,
|
||||
} from '../types';
|
||||
import {
|
||||
MassLimitLocoType,
|
||||
SpeedLimitLocoType,
|
||||
calculateMassLimit,
|
||||
calculateSpeedLimit,
|
||||
} from './vehicleLimitsUtils';
|
||||
|
||||
export function isLocomotive(vehicle: ILocomotive | ICarWagon): vehicle is ILocomotive {
|
||||
return (vehicle as ILocomotive).power !== undefined;
|
||||
export function isTractionUnit(vehicle: ILocomotive | ICarWagon): vehicle is ILocomotive {
|
||||
return (vehicle as ILocomotive).cabinType !== undefined;
|
||||
}
|
||||
|
||||
export function locoDataList(state: IStore) {
|
||||
if (!state.stockData) return [];
|
||||
export function locoDataList(vehiclesData: IVehiclesData | undefined) {
|
||||
if (!vehiclesData) return [];
|
||||
|
||||
const stockData = state.stockData;
|
||||
return vehiclesData.vehicleList.reduce<ILocomotive[]>((acc, vehicleInfoArray) => {
|
||||
// check if data array has 5 elements (locos & units only)
|
||||
if (vehicleInfoArray.length != 5) return acc;
|
||||
|
||||
return Object.keys(stockData.info).reduce((acc, vehiclePower) => {
|
||||
if (!vehiclePower.startsWith('loco')) return acc;
|
||||
const [type, constructionType, cabinType, group, restrictions] = vehicleInfoArray;
|
||||
const locoProps = vehiclesData.vehicleProps.find((prop) => constructionType == prop.type);
|
||||
|
||||
const locoVehiclesData = stockData.info[vehiclePower as TLocoGroup];
|
||||
if (!locoProps) {
|
||||
console.warn('Brak atrybutów dla pojazdu:', type);
|
||||
return acc;
|
||||
}
|
||||
|
||||
locoVehiclesData.forEach((loco) => {
|
||||
if (state.showSupporter && !loco[4]) return;
|
||||
acc.push({
|
||||
group: group as LocoGroupType,
|
||||
|
||||
const [type, constructionType, cabinType, maxSpeed, sponsorsTimestamp] = loco;
|
||||
const locoProps = stockData.props.find((prop) => constructionType == prop.type);
|
||||
type,
|
||||
constructionType,
|
||||
cabinType,
|
||||
|
||||
acc.push({
|
||||
power: vehiclePower as TLocoGroup,
|
||||
group: vehiclePower as TLocoGroup,
|
||||
type,
|
||||
constructionType,
|
||||
cabinType,
|
||||
maxSpeed: Number(maxSpeed),
|
||||
isSponsorsOnly: Number(sponsorsTimestamp) > Date.now(),
|
||||
sponsorsOnlyTimestamp: Number(sponsorsTimestamp),
|
||||
imageSrc: '',
|
||||
sponsorOnlyTimestamp: restrictions?.sponsorOnly ?? 0,
|
||||
teamOnly: restrictions?.teamOnly ?? false,
|
||||
|
||||
length: locoProps?.length && type.startsWith('2EN') ? locoProps.length * 2 : locoProps?.length || 0,
|
||||
mass: locoProps?.mass && type.startsWith('2EN') ? 253 : locoProps?.mass || 0,
|
||||
});
|
||||
maxSpeed: locoProps.speed,
|
||||
length: locoProps.length,
|
||||
weight: locoProps.weight,
|
||||
|
||||
coldStart: locoProps.coldStart ?? false,
|
||||
doubleManned: locoProps.doubleManned ?? false,
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, [] as ILocomotive[]);
|
||||
}, []);
|
||||
}
|
||||
|
||||
export function carDataList(state: IStore) {
|
||||
if (!state.stockData) return [];
|
||||
export function carDataList(vehiclesData: IVehiclesData | undefined) {
|
||||
if (!vehiclesData) return [];
|
||||
|
||||
const stockData = state.stockData;
|
||||
return vehiclesData.vehicleList.reduce<ICarWagon[]>((acc, vehicleInfoArray) => {
|
||||
// check if data array has 4 elements (wagons only)
|
||||
if (vehicleInfoArray.length != 4) return acc;
|
||||
|
||||
return Object.keys(stockData.info).reduce((acc, vehicleUseType) => {
|
||||
if (!vehicleUseType.startsWith('car')) return acc;
|
||||
const [type, constructionType, group, restrictions] = vehicleInfoArray;
|
||||
const wagonProps = vehiclesData.vehicleProps.find((prop) => constructionType == prop.type);
|
||||
|
||||
const carVehiclesData = stockData.info[vehicleUseType as TCarWagonGroup];
|
||||
if (!wagonProps) {
|
||||
console.warn('Brak atrybutów dla pojazdu:', type);
|
||||
return acc;
|
||||
}
|
||||
|
||||
carVehiclesData.forEach((car) => {
|
||||
const [type, constructionType, loadable, sponsorsOnlyTimestamp, maxSpeed] = car;
|
||||
acc.push({
|
||||
group: group as WagonGroupType,
|
||||
type,
|
||||
constructionType,
|
||||
loadable: wagonProps.cargoTypes ? wagonProps.cargoTypes.length > 0 : false,
|
||||
cargoTypes: wagonProps?.cargoTypes ?? [],
|
||||
|
||||
if (state.showSupporter && Number(sponsorsOnlyTimestamp) <= Date.now()) return;
|
||||
sponsorOnlyTimestamp: restrictions?.sponsorOnly ?? 0,
|
||||
teamOnly: restrictions?.teamOnly ?? false,
|
||||
|
||||
const carPropsData = stockData.props.find((v) => type.toString().startsWith(v.type));
|
||||
|
||||
acc.push({
|
||||
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]),
|
||||
})),
|
||||
|
||||
mass: carPropsData?.mass || 0,
|
||||
length: carPropsData?.length || 0,
|
||||
});
|
||||
maxSpeed: wagonProps.speed,
|
||||
weight: wagonProps?.weight || 0,
|
||||
length: wagonProps?.length || 0,
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, [] as ICarWagon[]);
|
||||
}, []);
|
||||
}
|
||||
|
||||
export function totalMass(state: IStore) {
|
||||
return ~~state.stockList.reduce((acc, stock) => acc + (stock.cargo ? stock.cargo.totalMass : stock.mass) * stock.count, 0);
|
||||
export function totalWeight(stockList: IStock[]) {
|
||||
return stockList.reduce(
|
||||
(acc, stock) => acc + (stock.vehicleRef.weight + (stock.cargo?.weight ?? 0)),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
export function totalLength(state: IStore) {
|
||||
return state.stockList.reduce((acc, stock) => acc + stock.length * stock.count, 0);
|
||||
export function totalLength(stockList: IStock[]) {
|
||||
return stockList.reduce((acc, stock) => acc + stock.vehicleRef.length, 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;
|
||||
export function maxStockSpeed(stockList: IStock[]) {
|
||||
const stockSpeedLimit = stockList.reduce(
|
||||
(acc, stock) => (stock.vehicleRef.maxSpeed < acc || acc == 0 ? stock.vehicleRef.maxSpeed : acc),
|
||||
0
|
||||
);
|
||||
const headingLoco =
|
||||
stockList[0] && isTractionUnit(stockList[0].vehicleRef) ? stockList[0] : undefined;
|
||||
|
||||
if (!headingLoco) return stockSpeedLimit;
|
||||
|
||||
const locoType = headingLoco.type.split('-')[0];
|
||||
const locoType = headingLoco.vehicleRef.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 SpeedLimitLocoType,
|
||||
totalWeight(stockList),
|
||||
stockList.length,
|
||||
isTrainPassenger(stockList)
|
||||
);
|
||||
|
||||
return speedLimitByMass ? Math.min(stockSpeedLimit, speedLimitByMass) : stockSpeedLimit;
|
||||
}
|
||||
|
||||
export function acceptableMass(state: IStore) {
|
||||
if (state.stockList.length == 0 || !state.stockList[0].isLoco) return 0;
|
||||
const activeLocomotiveType = state.stockList[0].type;
|
||||
export function acceptableWeight(stockList: IStock[]) {
|
||||
if (stockList.length == 0 || !isTractionUnit(stockList[0].vehicleRef)) return 0;
|
||||
|
||||
if (/^SM/.test(activeLocomotiveType)) return 2400;
|
||||
const activeLocomotiveType = stockList[0].vehicleRef.type.split('-')[0];
|
||||
|
||||
// Elektryczne EU07 / EP07 / EP08 / ET41
|
||||
const locoMassLimit = calculateMassLimit(
|
||||
activeLocomotiveType as MassLimitLocoType,
|
||||
isTrainPassenger(stockList)
|
||||
);
|
||||
|
||||
// Pasażerski elektr.
|
||||
if (isTrainPassenger(state)) {
|
||||
if (/^(EU|EP)/.test(activeLocomotiveType)) return 650;
|
||||
if (/^ET/.test(activeLocomotiveType)) return 700;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Towarowy / inny elektr.
|
||||
if (/^EU/.test(activeLocomotiveType)) return 2000;
|
||||
if (/^ET/.test(activeLocomotiveType)) return 4000;
|
||||
if (/^EP/.test(activeLocomotiveType)) return 650;
|
||||
|
||||
return 0;
|
||||
return locoMassLimit;
|
||||
}
|
||||
|
||||
export function isTrainPassenger(state: IStore) {
|
||||
if (state.stockList.length == 0) return false;
|
||||
if (state.stockList.every((stock) => stock.isLoco)) return false;
|
||||
export function isTrainPassenger(stockList: IStock[]) {
|
||||
if (stockList.length == 0) return false;
|
||||
if (stockList.every((stock) => isTractionUnit(stock.vehicleRef))) return false;
|
||||
|
||||
return state.stockList.filter((stock) => !stock.isLoco).every((stock) => stock.useType === EVehicleUseType.CAR_PASSENGER);
|
||||
}
|
||||
|
||||
export function chosenRealStock(state: IStore) {
|
||||
const currentStockString = state.stockList
|
||||
.reduce((acc, stock) => {
|
||||
for (let i = 0; i < stock.count; i++) acc.push(stock.type);
|
||||
return acc;
|
||||
}, [] as string[])
|
||||
.join(';');
|
||||
|
||||
const realStockObj = state.readyStockList.find((readyStock) => readyStock.stockString == currentStockString);
|
||||
|
||||
state.chosenRealStockName = realStockObj?.stockId ?? undefined;
|
||||
|
||||
return realStockObj;
|
||||
return stockList
|
||||
.filter((stock) => !isTractionUnit(stock.vehicleRef))
|
||||
.every((stock) => stock.vehicleRef.group === 'wagon-passenger');
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { useStore } from "../store";
|
||||
import { defineComponent } from 'vue';
|
||||
import { useStore } from '../store';
|
||||
|
||||
import MainContainer from "../components/app/MainContainer.vue";
|
||||
import FooterVue from "../components/app/Footer.vue";
|
||||
import MainContainer from '../components/app/MainContainer.vue';
|
||||
import FooterVue from '../components/app/Footer.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -26,10 +26,12 @@ export default defineComponent({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-container {
|
||||
min-height: 100vh;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
min-height: 100vh;
|
||||
padding: 0.5em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module "*.vue" {
|
||||
import type { DefineComponent } from "vue";
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue';
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
||||
|
||||
@@ -2,30 +2,30 @@ import { defineConfig } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
|
||||
import { VitePWA } from 'vite-plugin-pwa';
|
||||
import { resolve } from 'path';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
server: {
|
||||
port: 2137,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
$fonts: resolve('/fonts'),
|
||||
$images: resolve('/images'),
|
||||
},
|
||||
port: 2138,
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
VitePWA({
|
||||
registerType: 'autoUpdate',
|
||||
|
||||
includeAssets: ['/images/*.{png,svg,jpg}', '/fonts/*.{woff,woff2,ttf}'],
|
||||
|
||||
devOptions: {
|
||||
suppressWarnings: true,
|
||||
enabled: true,
|
||||
},
|
||||
|
||||
workbox: {
|
||||
globPatterns: ['**/*.{js,css,html,png,svg,img,woff,woff2}'],
|
||||
globPatterns: ['**/*.{js,css,html,jpg,png,svg,img,woff,woff2}'],
|
||||
|
||||
runtimeCaching: [
|
||||
{
|
||||
urlPattern: /^https:\/\/rj.td2.info.pl\/dist\/img\/thumbnails\/.*/i,
|
||||
urlPattern: new RegExp('^https://rj.td2.info.pl/dist/img/thumbnails/*', 'i'),
|
||||
handler: 'CacheFirst',
|
||||
options: {
|
||||
cacheName: 'swdr-images-cache',
|
||||
@@ -34,21 +34,16 @@ export default defineConfig({
|
||||
maxAgeSeconds: 60 * 60 * 24, // <== 1 day
|
||||
},
|
||||
cacheableResponse: {
|
||||
statuses: [0, 200, 404],
|
||||
statuses: [200],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
urlPattern: /^https:\/\/spythere.github.io\/api\/td2\/.*/i,
|
||||
handler: 'CacheFirst',
|
||||
urlPattern: new RegExp('^https://stacjownik.spythere.eu/vehicles', 'i'),
|
||||
handler: 'NetworkFirst',
|
||||
options: {
|
||||
cacheName: 'spythere-api-cache',
|
||||
expiration: {
|
||||
maxAgeSeconds: 60 * 60 * 24, // <== 1 day
|
||||
},
|
||||
cacheableResponse: {
|
||||
statuses: [0, 200],
|
||||
},
|
||||
cacheName: 'vehicles-cache',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||