mirror of
https://github.com/Spythere/pojazdownik.git
synced 2026-05-03 19:48:11 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 37e4149a34 | |||
| e515203557 | |||
| 0d79c71eba | |||
| 2bbf9a8ac3 | |||
| 45b2bd01a2 | |||
| 665ffb9dce | |||
| 1c2a93fbd5 | |||
| 57ab6cc02d |
@@ -0,0 +1,18 @@
|
|||||||
|
/* eslint-env node */
|
||||||
|
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'
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
'vue/multi-word-component-names': 'off'
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest'
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
env: {
|
|
||||||
node: true,
|
|
||||||
},
|
|
||||||
extends: [
|
|
||||||
"plugin:vue/vue3-essential",
|
|
||||||
"eslint:recommended",
|
|
||||||
"@vue/typescript/recommended",
|
|
||||||
"@vue/prettier",
|
|
||||||
"@vue/prettier/@typescript-eslint",
|
|
||||||
],
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 2020,
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
|
||||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -26,3 +26,6 @@ node_modules
|
|||||||
|
|
||||||
# Dev files
|
# Dev files
|
||||||
stockInfoDev.json
|
stockInfoDev.json
|
||||||
|
|
||||||
|
# Lock files
|
||||||
|
yarn.lock
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
presets: ["@vue/cli-plugin-babel/preset"],
|
|
||||||
};
|
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||||
<link rel="manifest" href="/site.webmanifest" />
|
<link rel="manifest" href="/site.webmanifest" />
|
||||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
|
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
|
||||||
|
|
||||||
<meta name="msapplication-TileColor" content="#da532c" />
|
<meta name="msapplication-TileColor" content="#da532c" />
|
||||||
<meta name="theme-color" content="#e4c428" />
|
<meta name="theme-color" content="#e4c428" />
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
Generated
+7141
-218
File diff suppressed because it is too large
Load Diff
+12
-2
@@ -1,20 +1,30 @@
|
|||||||
{
|
{
|
||||||
"name": "pojazdownik",
|
"name": "pojazdownik",
|
||||||
"version": "1.6.0",
|
"version": "1.7.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vue-tsc --noEmit && vite build",
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
"preview": "yarn build && vite preview --port 4174"
|
"preview": "yarn build && vite preview --port 4174",
|
||||||
|
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
|
||||||
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||||
|
"format": "prettier --write src/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"pinia": "^2.0.17",
|
"pinia": "^2.0.17",
|
||||||
|
"prettier": "^3.0.3",
|
||||||
"vue": "^3.2.37",
|
"vue": "^3.2.37",
|
||||||
"vue-i18n": "9"
|
"vue-i18n": "9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@rushstack/eslint-patch": "^1.3.3",
|
||||||
"@vitejs/plugin-vue": "^4.1.0",
|
"@vitejs/plugin-vue": "^4.1.0",
|
||||||
|
"@vue/eslint-config-prettier": "^8.0.0",
|
||||||
|
"@vue/eslint-config-typescript": "^12.0.0",
|
||||||
|
"@vue/tsconfig": "^0.4.0",
|
||||||
|
"eslint": "^8.49.0",
|
||||||
|
"eslint-plugin-vue": "^9.17.0",
|
||||||
"sass": "^1.59.3",
|
"sass": "^1.59.3",
|
||||||
"typescript": "^5.0.2",
|
"typescript": "^5.0.2",
|
||||||
"vite": "^4.2.1",
|
"vite": "^4.2.1",
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
-12
@@ -18,11 +18,6 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
async created() {
|
async created() {
|
||||||
/* dev info testing */
|
|
||||||
// if (import.meta.env['VITE_STOCK_DEV'] == '1') {
|
|
||||||
// const data = await import('../stockInfoDev.json');
|
|
||||||
// this.store.stockData = data.default as any;
|
|
||||||
// }
|
|
||||||
this.store.fetchStockInfoData();
|
this.store.fetchStockInfoData();
|
||||||
this.store.handleRouting();
|
this.store.handleRouting();
|
||||||
},
|
},
|
||||||
@@ -59,11 +54,4 @@ h2 {
|
|||||||
|
|
||||||
color: #d1d1d1;
|
color: #d1d1d1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $breakpointMd) {
|
|
||||||
#app {
|
|
||||||
font-size: calc(0.7rem + 0.75vw);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from "vue";
|
||||||
import { useStore } from '../../store';
|
import { useStore } from "../../store";
|
||||||
import RealStockCard from '../cards/RealStockCard.vue';
|
import RealStockCard from "../cards/RealStockCard.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { RealStockCard },
|
components: { RealStockCard },
|
||||||
|
|||||||
@@ -3,32 +3,35 @@
|
|||||||
<i18n-t keypath="footer.disclaimer" tag="div" class="text--grayed">
|
<i18n-t keypath="footer.disclaimer" tag="div" class="text--grayed">
|
||||||
<template #tos>
|
<template #tos>
|
||||||
<a style="color: #ccc" :href="$t('footer.tos-href')" target="_blank">
|
<a style="color: #ccc" :href="$t('footer.tos-href')" target="_blank">
|
||||||
{{ $t('footer.tos') }}
|
{{ $t("footer.tos") }}
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
</i18n-t>
|
</i18n-t>
|
||||||
|
|
||||||
<div class="text--grayed" v-if="store.stockData">
|
<div class="text--grayed" v-if="store.stockData">
|
||||||
{{ $t('footer.version-check', { version: store.stockData.version }) }}
|
{{ $t("footer.version-check", { version: store.stockData.version }) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
©
|
©
|
||||||
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
|
<a href="https://td2.info.pl/profile/?u=20777" target="_blank"
|
||||||
{{ new Date().getUTCFullYear() }} | v{{ VERSION }}{{ !isOnProductionHost ? 'dev' : '' }}
|
>Spythere</a
|
||||||
|
>
|
||||||
|
{{ new Date().getUTCFullYear() }} | v{{ VERSION
|
||||||
|
}}{{ !isOnProductionHost ? "dev" : "" }}
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from "vue";
|
||||||
import packageInfo from '../../../package.json';
|
import packageInfo from "../../../package.json";
|
||||||
import { useStore } from '../../store';
|
import { useStore } from "../../store";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isOnProductionHost: location.hostname == 'pojazdownik-td2.web.app',
|
isOnProductionHost: location.hostname == "pojazdownik-td2.web.app",
|
||||||
VERSION: packageInfo.version,
|
VERSION: packageInfo.version,
|
||||||
store: useStore(),
|
store: useStore(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,11 +8,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from "vue";
|
||||||
import LogoSection from '../sections/LogoSection.vue';
|
import LogoSection from "../sections/LogoSection.vue";
|
||||||
import InputsSection from '../sections/InputsSection.vue';
|
import InputsSection from "../sections/InputsSection.vue";
|
||||||
import TrainImageSection from '../sections/TrainImageSection.vue';
|
import TrainImageSection from "../sections/TrainImageSection.vue";
|
||||||
import StockSection from '../sections/StockSection.vue';
|
import StockSection from "../sections/StockSection.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { LogoSection, InputsSection, TrainImageSection, StockSection },
|
components: { LogoSection, InputsSection, TrainImageSection, StockSection },
|
||||||
@@ -20,14 +20,14 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/global.scss';
|
@import "../../styles/global.scss";
|
||||||
|
|
||||||
main {
|
main {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 1500px;
|
max-width: 1300px;
|
||||||
min-height: 75vh;
|
min-height: 75vh;
|
||||||
|
|
||||||
grid-template-columns: 1fr 2fr;
|
grid-template-columns: 1fr 2fr;
|
||||||
|
|||||||
@@ -1,14 +1,25 @@
|
|||||||
<template>
|
<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="g-card_bg" @click="store.isRealStockListCardOpen = false"></div>
|
||||||
|
|
||||||
<div class="card_content">
|
<div class="card_content">
|
||||||
<div class="card_nav">
|
<div class="card_nav">
|
||||||
<div class="top-pane">
|
<div class="top-pane">
|
||||||
<h1>
|
<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>
|
</h1>
|
||||||
<button class="btn exit-btn" @click="store.isRealStockListCardOpen = false">⨯</button>
|
<button
|
||||||
|
class="btn exit-btn"
|
||||||
|
@click="store.isRealStockListCardOpen = false"
|
||||||
|
>
|
||||||
|
⨯
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="filters" ref="focus" tabindex="0">
|
<div class="filters" ref="focus" tabindex="0">
|
||||||
@@ -19,7 +30,11 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<datalist id="readyStockDataList">
|
<datalist id="readyStockDataList">
|
||||||
<option v-for="stock in store.readyStockList" :value="stock.stockId">
|
<option
|
||||||
|
v-for="stock in store.readyStockList"
|
||||||
|
:value="stock.stockId"
|
||||||
|
:key="stock.name"
|
||||||
|
>
|
||||||
{{ stock.stockId }}
|
{{ stock.stockId }}
|
||||||
</option>
|
</option>
|
||||||
</datalist>
|
</datalist>
|
||||||
@@ -31,12 +46,18 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<datalist id="readyStockStringList">
|
<datalist id="readyStockStringList">
|
||||||
<option v-for="stock in computedAvailableStockTypes" :value="stock">
|
<option
|
||||||
{{ stock }}
|
v-for="stockType in computedAvailableStockTypes"
|
||||||
|
:value="stockType"
|
||||||
|
:key="stockType"
|
||||||
|
>
|
||||||
|
{{ stockType }}
|
||||||
</option>
|
</option>
|
||||||
</datalist>
|
</datalist>
|
||||||
|
|
||||||
<button class="btn" @click="resetStockFilters">{{ $t('realstock.action-reset') }}</button>
|
<button class="btn" @click="resetStockFilters">
|
||||||
|
{{ $t("realstock.action-reset") }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -46,14 +67,29 @@
|
|||||||
:key="rStock.stockId"
|
:key="rStock.stockId"
|
||||||
:data-last-selected="store.chosenRealStockName === rStock.stockId"
|
:data-last-selected="store.chosenRealStockName === rStock.stockId"
|
||||||
>
|
>
|
||||||
<div class="stock-title" tabindex="0" @click="chooseStock(rStock)" @keydown.enter="chooseStock(rStock)">
|
<div
|
||||||
<img class="stock-icon" :src="getIconURL(rStock.type)" :alt="rStock.type" />
|
class="stock-title"
|
||||||
<b class="text--accent" style="margin-left: 5px"> {{ rStock.name }}</b>
|
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
|
||||||
|
>
|
||||||
<div>{{ rStock.number }}</div>
|
<div>{{ rStock.number }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stock-thumbnails" ref="thumbnailsRef">
|
<div class="stock-thumbnails" ref="thumbnailsRef">
|
||||||
<div class="thumbnail-item" v-for="stockType in rStock.stockString.split(';')">
|
<div
|
||||||
|
class="thumbnail-item"
|
||||||
|
v-for="stockType in rStock.stockString.split(';')"
|
||||||
|
:key="stockType"
|
||||||
|
>
|
||||||
<div class="thumbnail-container">
|
<div class="thumbnail-container">
|
||||||
<div>{{ stockType }}</div>
|
<div>{{ stockType }}</div>
|
||||||
<img
|
<img
|
||||||
@@ -61,7 +97,7 @@
|
|||||||
:title="stockType"
|
:title="stockType"
|
||||||
style="opacity: 0"
|
style="opacity: 0"
|
||||||
@error="(e) => onStockItemError(e, stockType)"
|
@error="(e) => onStockItemError(e, stockType)"
|
||||||
@load="e => (e.target as HTMLElement).style.opacity = '1'"
|
@load="(e) => ((e.target as HTMLElement).style.opacity = '1')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -75,23 +111,24 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
import { useStore } from '../../store';
|
import { useStore } from "../../store";
|
||||||
import imageMixin from '../../mixins/imageMixin';
|
import imageMixin from "../../mixins/imageMixin";
|
||||||
import stockMixin from '../../mixins/stockMixin';
|
import stockMixin from "../../mixins/stockMixin";
|
||||||
|
|
||||||
import { IReadyStockItem } from '../../types';
|
import { IReadyStockItem } from "../../types";
|
||||||
|
import http from "../../http";
|
||||||
|
|
||||||
interface ResponseJSONData {
|
interface ResponseJSONData {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getVehicleType(stockType: string) {
|
function getVehicleType(stockType: string) {
|
||||||
if (/^E/.test(stockType)) return 'loco-e';
|
if (/^E/.test(stockType)) return "loco-e";
|
||||||
if (/^S/.test(stockType)) return 'loco-s';
|
if (/^S/.test(stockType)) return "loco-s";
|
||||||
|
|
||||||
return 'car-passenger';
|
return "car-passenger";
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@@ -99,11 +136,15 @@ export default defineComponent({
|
|||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
store: useStore(),
|
store: useStore(),
|
||||||
responseStatus: 'loading',
|
responseStatus: "loading",
|
||||||
isMobile: 'ontouchstart' in document.documentElement && navigator.userAgent.match(/Mobi/) ? true : false,
|
isMobile:
|
||||||
|
"ontouchstart" in document.documentElement &&
|
||||||
|
navigator.userAgent.match(/Mobi/)
|
||||||
|
? true
|
||||||
|
: false,
|
||||||
observer: null as IntersectionObserver | null,
|
observer: null as IntersectionObserver | null,
|
||||||
searchedReadyStockName: '',
|
searchedReadyStockName: "",
|
||||||
searchedReadyStockString: '',
|
searchedReadyStockString: "",
|
||||||
visibleIndexesTo: 0,
|
visibleIndexesTo: 0,
|
||||||
lastSelectedStockId: null as string | null,
|
lastSelectedStockId: null as string | null,
|
||||||
scrollTop: 0,
|
scrollTop: 0,
|
||||||
@@ -115,11 +156,11 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
activated() {
|
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,
|
top: this.scrollTop,
|
||||||
behavior: 'auto',
|
behavior: "auto",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -130,8 +171,12 @@ export default defineComponent({
|
|||||||
return this.store.readyStockList
|
return this.store.readyStockList
|
||||||
.filter(
|
.filter(
|
||||||
(rs) =>
|
(rs) =>
|
||||||
rs.stockId.toLocaleLowerCase().includes(this.searchedReadyStockName.toLocaleLowerCase()) &&
|
rs.stockId
|
||||||
rs.stockString.toLocaleLowerCase().includes(this.searchedReadyStockString.toLocaleLowerCase())
|
.toLocaleLowerCase()
|
||||||
|
.includes(this.searchedReadyStockName.toLocaleLowerCase()) &&
|
||||||
|
rs.stockString
|
||||||
|
.toLocaleLowerCase()
|
||||||
|
.includes(this.searchedReadyStockString.toLocaleLowerCase()),
|
||||||
)
|
)
|
||||||
.filter((_, i) => i <= this.visibleIndexesTo);
|
.filter((_, i) => i <= this.visibleIndexesTo);
|
||||||
},
|
},
|
||||||
@@ -139,7 +184,7 @@ export default defineComponent({
|
|||||||
computedAvailableStockTypes() {
|
computedAvailableStockTypes() {
|
||||||
return this.store.readyStockList
|
return this.store.readyStockList
|
||||||
.reduce((acc, rs) => {
|
.reduce((acc, rs) => {
|
||||||
rs.stockString.split(';').forEach((s) => {
|
rs.stockString.split(";").forEach((s) => {
|
||||||
if (!acc.includes(s)) acc.push(s);
|
if (!acc.includes(s)) acc.push(s);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -153,7 +198,7 @@ export default defineComponent({
|
|||||||
computedReadyStockList(curr, prev) {
|
computedReadyStockList(curr, prev) {
|
||||||
if (curr.length < prev.length) {
|
if (curr.length < prev.length) {
|
||||||
this.visibleIndexesTo = 20;
|
this.visibleIndexesTo = 20;
|
||||||
(this.$refs['list'] as HTMLElement).scrollTo({
|
(this.$refs["list"] as HTMLElement).scrollTo({
|
||||||
top: 0,
|
top: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -162,21 +207,21 @@ export default defineComponent({
|
|||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async fetchStockListData() {
|
async fetchStockListData() {
|
||||||
const readyStockJSONData: ResponseJSONData = await (
|
const readyStockJSONData = (
|
||||||
await fetch(`https://spythere.github.io/api/td2/data/readyStock.json?t=${Math.floor(Date.now() / 60000)}`)
|
await http.get<ResponseJSONData>("td2/data/readyStock.json")
|
||||||
).json();
|
).data;
|
||||||
|
|
||||||
if (!readyStockJSONData) {
|
if (!readyStockJSONData) {
|
||||||
this.responseStatus = 'error';
|
this.responseStatus = "error";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let stockKey in readyStockJSONData) {
|
for (let stockKey in readyStockJSONData) {
|
||||||
const [type, number, ...name] = stockKey.split(' ');
|
const [type, number, ...name] = stockKey.split(" ");
|
||||||
|
|
||||||
const obj = {
|
const obj = {
|
||||||
number: number.replace(/_/g, '/'),
|
number: number.replace(/_/g, "/"),
|
||||||
name: name.join(' '),
|
name: name.join(" "),
|
||||||
stockString: readyStockJSONData[stockKey],
|
stockString: readyStockJSONData[stockKey],
|
||||||
type,
|
type,
|
||||||
};
|
};
|
||||||
@@ -187,7 +232,7 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.responseStatus = 'loaded';
|
this.responseStatus = "loaded";
|
||||||
},
|
},
|
||||||
|
|
||||||
mountObserver() {
|
mountObserver() {
|
||||||
@@ -195,7 +240,7 @@ export default defineComponent({
|
|||||||
if (entries[0].intersectionRatio > 0) this.visibleIndexesTo += 20;
|
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) {
|
getImageUrl(name: string) {
|
||||||
@@ -203,8 +248,8 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
resetStockFilters() {
|
resetStockFilters() {
|
||||||
this.searchedReadyStockName = '';
|
this.searchedReadyStockName = "";
|
||||||
this.searchedReadyStockString = '';
|
this.searchedReadyStockString = "";
|
||||||
},
|
},
|
||||||
|
|
||||||
chooseStock(stockItem: IReadyStockItem) {
|
chooseStock(stockItem: IReadyStockItem) {
|
||||||
@@ -216,7 +261,7 @@ export default defineComponent({
|
|||||||
onStockItemError(e: Event, stockType: string) {
|
onStockItemError(e: Event, stockType: string) {
|
||||||
const imageEl = e.target as HTMLImageElement;
|
const imageEl = e.target as HTMLImageElement;
|
||||||
imageEl.src = `images/${getVehicleType(stockType)}-unknown.png`;
|
imageEl.src = `images/${getVehicleType(stockType)}-unknown.png`;
|
||||||
imageEl.style.opacity = '1';
|
imageEl.style.opacity = "1";
|
||||||
},
|
},
|
||||||
|
|
||||||
onListScroll(e: Event) {
|
onListScroll(e: Event) {
|
||||||
@@ -230,7 +275,7 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/global.scss';
|
@import "../../styles/global.scss";
|
||||||
|
|
||||||
.exit-btn {
|
.exit-btn {
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
@@ -316,7 +361,7 @@ ul {
|
|||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
padding: 0.1em;
|
padding: 0.1em;
|
||||||
|
|
||||||
&[data-last-selected='true'] .stock-title {
|
&[data-last-selected="true"] .stock-title {
|
||||||
border: 1px solid $accentColor;
|
border: 1px solid $accentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,4 +423,3 @@ ul {
|
|||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<div class="vehicle-types locos">
|
<div class="vehicle-types locos">
|
||||||
<button
|
<button
|
||||||
v-for="locoType in locomotiveTypeList"
|
v-for="locoType in locomotiveTypeList"
|
||||||
|
:key="locoType.id"
|
||||||
class="btn btn--choice"
|
class="btn btn--choice"
|
||||||
:data-selected="locoType.id == store.chosenLocoPower"
|
:data-selected="locoType.id == store.chosenLocoPower"
|
||||||
@click="selectLocoType(locoType.id)"
|
@click="selectLocoType(locoType.id)"
|
||||||
@@ -23,10 +24,10 @@
|
|||||||
@keydown.enter.prevent="addOrSwitchVehicle"
|
@keydown.enter.prevent="addOrSwitchVehicle"
|
||||||
@keydown.backspace="removeVehicle"
|
@keydown.backspace="removeVehicle"
|
||||||
>
|
>
|
||||||
<option :value="null" disabled>{{ $t('inputs.input-vehicle') }}</option>
|
<option :value="null" disabled>
|
||||||
<option v-for="loco in locoOptions" :value="loco" :key="loco.type">
|
{{ $t('inputs.input-vehicle') }}
|
||||||
{{ loco.type }}<b v-if="loco.supportersOnly">*</b>
|
|
||||||
</option>
|
</option>
|
||||||
|
<option v-for="loco in locoOptions" :value="loco" :key="loco.type">{{ loco.type }}<b v-if="loco.isSponsorsOnly">*</b></option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
<div class="vehicle-types carwagons">
|
<div class="vehicle-types carwagons">
|
||||||
<button
|
<button
|
||||||
v-for="carType in carTypeList"
|
v-for="carType in carTypeList"
|
||||||
|
:key="carType.id"
|
||||||
class="btn btn--choice"
|
class="btn btn--choice"
|
||||||
:data-selected="carType.id == store.chosenCarUseType"
|
:data-selected="carType.id == store.chosenCarUseType"
|
||||||
@click="selectCarWagonType(carType.id)"
|
@click="selectCarWagonType(carType.id)"
|
||||||
@@ -50,11 +52,11 @@
|
|||||||
@keydown.enter.prevent="addOrSwitchVehicle"
|
@keydown.enter.prevent="addOrSwitchVehicle"
|
||||||
@keydown.backspace="removeVehicle"
|
@keydown.backspace="removeVehicle"
|
||||||
>
|
>
|
||||||
<option :value="null" disabled>{{ $t('inputs.input-carwagon') }}</option>
|
<option :value="null" disabled>
|
||||||
|
{{ $t('inputs.input-carwagon') }}
|
||||||
<option v-for="car in carOptions" :value="car" :key="car.type">
|
|
||||||
{{ car.type }}<b v-if="car.supportersOnly">*</b>
|
|
||||||
</option>
|
</option>
|
||||||
|
|
||||||
|
<option v-for="car in carOptions" :value="car" :key="car.type">{{ car.type }}<b v-if="car.isSponsorsOnly">*</b></option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -63,9 +65,7 @@
|
|||||||
<select
|
<select
|
||||||
id="cargo-select"
|
id="cargo-select"
|
||||||
:disabled="
|
:disabled="
|
||||||
(store.chosenCar && !store.chosenCar.loadable) ||
|
(store.chosenCar && !store.chosenCar.loadable) || (store.chosenCar && store.chosenCar.useType == 'car-passenger') || !store.chosenCar
|
||||||
(store.chosenCar && store.chosenCar.useType == 'car-passenger') ||
|
|
||||||
!store.chosenCar
|
|
||||||
"
|
"
|
||||||
data-select="cargo"
|
data-select="cargo"
|
||||||
data-ignore-outside="1"
|
data-ignore-outside="1"
|
||||||
@@ -90,12 +90,7 @@
|
|||||||
<button class="btn" @click="addVehicle(store.chosenVehicle, store.chosenCargo)">
|
<button class="btn" @click="addVehicle(store.chosenVehicle, store.chosenCargo)">
|
||||||
{{ $t('inputs.action-add') }}
|
{{ $t('inputs.action-add') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button class="btn" @click="switchVehicles" :disabled="store.chosenStockListIndex == -1" :data-disabled="store.chosenStockListIndex == -1">
|
||||||
class="btn"
|
|
||||||
@click="switchVehicles"
|
|
||||||
:disabled="store.chosenStockListIndex == -1"
|
|
||||||
:data-disabled="store.chosenStockListIndex == -1"
|
|
||||||
>
|
|
||||||
{{ $t('inputs.action-swap') }}
|
{{ $t('inputs.action-swap') }}
|
||||||
<b class="text--accent">
|
<b class="text--accent">
|
||||||
{{ store.chosenStockListIndex == -1 ? '' : `${store.chosenStockListIndex + 1}.` }}
|
{{ store.chosenStockListIndex == -1 ? '' : `${store.chosenStockListIndex + 1}.` }}
|
||||||
@@ -122,6 +117,7 @@ export default defineComponent({
|
|||||||
mixins: [imageMixin, stockPreviewMixin, stockMixin],
|
mixins: [imageMixin, stockPreviewMixin, stockMixin],
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
|
store: useStore(),
|
||||||
locomotiveTypeList: [
|
locomotiveTypeList: [
|
||||||
{
|
{
|
||||||
id: 'loco-e',
|
id: 'loco-e',
|
||||||
@@ -153,12 +149,20 @@ export default defineComponent({
|
|||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
|
||||||
setup() {
|
computed: {
|
||||||
const store = useStore();
|
locoOptions() {
|
||||||
|
return this.store.locoDataList
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => (a.type > b.type ? 1 : -1))
|
||||||
|
.filter((loco) => loco.power == this.store.chosenLocoPower);
|
||||||
|
},
|
||||||
|
|
||||||
return {
|
carOptions() {
|
||||||
store,
|
return this.store.carDataList
|
||||||
};
|
.slice()
|
||||||
|
.sort((a, b) => (a.type > b.type ? 1 : -1))
|
||||||
|
.filter((car) => car.useType == this.store.chosenCarUseType);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -192,6 +196,29 @@ export default defineComponent({
|
|||||||
const stockObject = this.getStockObject(vehicle, this.store.chosenCargo);
|
const stockObject = this.getStockObject(vehicle, this.store.chosenCargo);
|
||||||
this.store.stockList[this.store.chosenStockListIndex] = stockObject;
|
this.store.stockList[this.store.chosenStockListIndex] = stockObject;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
selectLocoType(locoTypeId: string) {
|
||||||
|
this.store.chosenLocoPower = locoTypeId;
|
||||||
|
this.store.chosenVehicle = this.locoOptions[0];
|
||||||
|
this.store.chosenLoco = this.locoOptions[0];
|
||||||
|
},
|
||||||
|
|
||||||
|
selectCarWagonType(carWagonTypeId: string) {
|
||||||
|
this.store.chosenCarUseType = carWagonTypeId;
|
||||||
|
this.store.chosenVehicle = this.carOptions[0];
|
||||||
|
this.store.chosenCar = this.carOptions[0];
|
||||||
|
this.store.chosenCargo = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
previewVehicleByType(type: 'loco' | 'car' | 'cargo') {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
if (!this.store.chosenLoco && !this.store.chosenCar) return;
|
||||||
|
|
||||||
|
this.store.chosenVehicle = type == 'loco' ? this.store.chosenLoco : this.store.chosenCar;
|
||||||
|
|
||||||
|
this.store.chosenCargo = this.store.chosenCar?.cargoList.find((cargo) => cargo.id == this.store.chosenCargo?.id) || null;
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -207,6 +234,11 @@ export default defineComponent({
|
|||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input_container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 380px;
|
||||||
|
}
|
||||||
|
|
||||||
.input_header {
|
.input_header {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
@@ -226,6 +258,10 @@ button.btn--choice {
|
|||||||
.input_list {
|
.input_list {
|
||||||
margin: 0.5em 0;
|
margin: 0.5em 0;
|
||||||
|
|
||||||
|
select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
@@ -267,4 +303,3 @@ button.btn--choice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="logo-section">
|
<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">
|
<div class="actions">
|
||||||
<button
|
<button
|
||||||
class="btn btn--text"
|
|
||||||
v-for="action in localeActions"
|
v-for="action in localeActions"
|
||||||
|
:key="action.name"
|
||||||
|
class="btn btn--text"
|
||||||
:data-selected="$i18n.locale == action.locale"
|
:data-selected="$i18n.locale == action.locale"
|
||||||
@click="chooseLocale(action.locale)"
|
@click="chooseLocale(action.locale)"
|
||||||
>
|
>
|
||||||
@@ -21,31 +26,31 @@ export default {
|
|||||||
return {
|
return {
|
||||||
localeActions: [
|
localeActions: [
|
||||||
{
|
{
|
||||||
name: 'POLSKI',
|
name: "POLSKI",
|
||||||
locale: 'pl',
|
locale: "pl",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'ENGLISH',
|
name: "ENGLISH",
|
||||||
locale: 'en',
|
locale: "en",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
navigate() {
|
navigate() {
|
||||||
window.location.pathname = '';
|
window.location.pathname = "";
|
||||||
},
|
},
|
||||||
|
|
||||||
chooseLocale(locale: string) {
|
chooseLocale(locale: string) {
|
||||||
this.$i18n.locale = locale;
|
this.$i18n.locale = locale;
|
||||||
window.localStorage.setItem('locale', locale);
|
window.localStorage.setItem("locale", locale);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/global.scss';
|
@import "../../styles/global.scss";
|
||||||
|
|
||||||
.logo-section {
|
.logo-section {
|
||||||
grid-row: 1;
|
grid-row: 1;
|
||||||
@@ -64,7 +69,7 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
|
||||||
button[data-selected='true'] {
|
button[data-selected="true"] {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: $accentColor;
|
color: $accentColor;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
@@ -76,4 +81,3 @@ img {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
<section class="stock-section">
|
<section class="stock-section">
|
||||||
<div class="section_modes">
|
<div class="section_modes">
|
||||||
<button
|
<button
|
||||||
|
v-for="(id, i) in sectionModes"
|
||||||
|
:key="id"
|
||||||
class="btn"
|
class="btn"
|
||||||
ref="sectionButtonRefs"
|
ref="sectionButtonRefs"
|
||||||
v-for="(id, i) in sectionModes"
|
|
||||||
@click="chooseSection(id)"
|
@click="chooseSection(id)"
|
||||||
:data-selected="store.stockSectionMode == id"
|
:data-selected="store.stockSectionMode == id"
|
||||||
>
|
>
|
||||||
@@ -15,29 +16,37 @@
|
|||||||
|
|
||||||
<transition name="tab-change" mode="out-in">
|
<transition name="tab-change" mode="out-in">
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
<component :is="chosenSectionComponent" :key="chosenSectionComponent"></component>
|
<component
|
||||||
|
:is="chosenSectionComponent"
|
||||||
|
:key="chosenSectionComponent"
|
||||||
|
></component>
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
</transition>
|
</transition>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, KeepAlive, onMounted, ref } from 'vue';
|
import { computed, onMounted, ref } from "vue";
|
||||||
import { useStore } from '../../store';
|
import { useStore } from "../../store";
|
||||||
import StockListTab from '../tabs/StockListTab.vue';
|
import StockListTab from "../tabs/StockListTab.vue";
|
||||||
import StockGeneratorTab from '../tabs/StockGeneratorTab.vue';
|
import StockGeneratorTab from "../tabs/StockGeneratorTab.vue";
|
||||||
import NumberGeneratorTab from '../tabs/NumberGeneratorTab.vue';
|
import NumberGeneratorTab from "../tabs/NumberGeneratorTab.vue";
|
||||||
import WikiListTab from '../tabs/WikiListTab.vue';
|
import WikiListTab from "../tabs/WikiListTab.vue";
|
||||||
|
|
||||||
const sectionButtonRefs = ref([]);
|
const sectionButtonRefs = ref([]);
|
||||||
|
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
type SectionMode = typeof store.stockSectionMode;
|
type SectionMode = typeof store.stockSectionMode;
|
||||||
|
|
||||||
const sectionModes: SectionMode[] = ['stock-list', 'wiki-list', 'number-generator', 'stock-generator'];
|
const sectionModes: SectionMode[] = [
|
||||||
|
"stock-list",
|
||||||
|
"wiki-list",
|
||||||
|
"number-generator",
|
||||||
|
"stock-generator",
|
||||||
|
];
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.addEventListener('keydown', (e) => {
|
window.addEventListener("keydown", (e) => {
|
||||||
if (e.target instanceof HTMLInputElement) return;
|
if (e.target instanceof HTMLInputElement) return;
|
||||||
|
|
||||||
if (/[1234]/.test(e.key)) {
|
if (/[1234]/.test(e.key)) {
|
||||||
@@ -50,16 +59,16 @@ onMounted(() => {
|
|||||||
|
|
||||||
const chosenSectionComponent = computed(() => {
|
const chosenSectionComponent = computed(() => {
|
||||||
switch (store.stockSectionMode) {
|
switch (store.stockSectionMode) {
|
||||||
case 'stock-list':
|
case "stock-list":
|
||||||
return StockListTab;
|
return StockListTab;
|
||||||
|
|
||||||
case 'wiki-list':
|
case "wiki-list":
|
||||||
return WikiListTab;
|
return WikiListTab;
|
||||||
|
|
||||||
case 'stock-generator':
|
case "stock-generator":
|
||||||
return StockGeneratorTab;
|
return StockGeneratorTab;
|
||||||
|
|
||||||
case 'number-generator':
|
case "number-generator":
|
||||||
return NumberGeneratorTab;
|
return NumberGeneratorTab;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -73,7 +82,7 @@ function chooseSection(sectionId: SectionMode) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../styles/global.scss';
|
@import "../../styles/global.scss";
|
||||||
|
|
||||||
// Tab change animation
|
// Tab change animation
|
||||||
.tab-change {
|
.tab-change {
|
||||||
@@ -115,14 +124,14 @@ function chooseSection(sectionId: SectionMode) {
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
|
|
||||||
content: '';
|
content: "";
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
transition: all 100ms;
|
transition: all 100ms;
|
||||||
background-color: $accentColor;
|
background-color: $accentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-selected='true']::after {
|
&[data-selected="true"]::after {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,4 +143,3 @@ function chooseSection(sectionId: SectionMode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,58 +1,47 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="train-image-section">
|
<section class="train-image-section">
|
||||||
<div class="train-image__wrapper">
|
<div class="train-image__content" :class="{ sponsor: store.chosenVehicle?.isSponsorsOnly }">
|
||||||
<div class="train-image__content" :class="{ supporter: store.chosenVehicle?.supportersOnly }">
|
<img
|
||||||
<transition name="img-message-anim">
|
v-if="store.chosenVehicle"
|
||||||
<div class="empty-message" v-if="store.imageLoading && store.chosenVehicle?.imageSrc">
|
tabindex="0"
|
||||||
{{ $t('preview.loading') }}
|
:src="getThumbnailURL(store.chosenVehicle.type, 'small')"
|
||||||
</div>
|
@click="onImageClick"
|
||||||
</transition>
|
@keydown.enter="onImageClick"
|
||||||
|
@error="onImageError"
|
||||||
|
type="image/jpeg"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="no-img" v-if="!store.chosenVehicle">{{ $t('preview.title') }}</div>
|
<img v-else src="/images/placeholder.jpg" alt="placeholder" />
|
||||||
|
|
||||||
<img
|
|
||||||
v-if="store.chosenVehicle"
|
|
||||||
:src="`https://spythere.github.io/api/td2/images/${store.chosenVehicle.type}--300px.jpg`"
|
|
||||||
:alt="store.chosenVehicle.type"
|
|
||||||
@load="onImageLoad"
|
|
||||||
@click="onImageClick"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- <div class="empty-message" v-if="store.chosenVehicle && !store.chosenVehicle.imageSrc">Ten pojazd nie ma jeszcze podglądu!</div> -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="train-image__info" v-if="store.chosenVehicle">
|
|
||||||
<b class="text--accent">{{ store.chosenVehicle.type }}</b> •
|
|
||||||
<b style="color: #ccc">
|
|
||||||
{{
|
|
||||||
$t(`preview.${isLocomotive(store.chosenVehicle) ? store.chosenVehicle.power : store.chosenVehicle.useType}`)
|
|
||||||
}}
|
|
||||||
</b>
|
|
||||||
|
|
||||||
<div style="color: #ccc">
|
|
||||||
<div>
|
|
||||||
{{ store.chosenVehicle.length }}m | {{ store.chosenVehicle.mass }}t |
|
|
||||||
{{ store.chosenVehicle.maxSpeed }} km/h
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="isLocomotive(store.chosenVehicle)">
|
|
||||||
{{ $t('preview.cabin') }} {{ store.chosenVehicle.cabinType }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else>
|
|
||||||
{{
|
|
||||||
store.chosenVehicle.useType == 'car-cargo' // ? store.stockData?.usage[store.chosenVehicle.constructionType]
|
|
||||||
? $t(`usage.${store.chosenVehicle.constructionType}`)
|
|
||||||
: `${$t('preview.construction')} ${store.chosenVehicle.constructionType}`
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<b style="color: salmon" v-if="store.chosenVehicle.supportersOnly">{{ $t('preview.sponsor-only') }}</b>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="train-image__info" v-else>{{ $t('preview.desc') }}</div>
|
|
||||||
</div>
|
</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>
|
||||||
|
{{
|
||||||
|
store.chosenVehicle.useType == 'car-cargo'
|
||||||
|
? $t(`usage.${store.chosenVehicle.constructionType}`)
|
||||||
|
: `${$t('preview.construction')} ${store.chosenVehicle.constructionType}`
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<b style="color: salmon" v-if="store.chosenVehicle.isSponsorsOnly">{{
|
||||||
|
$t('preview.sponsor-only', [
|
||||||
|
new Date(store.chosenVehicle.sponsorsOnlyTimestamp).toLocaleDateString($i18n.locale == 'pl' ? 'pl-PL' : 'en-GB'),
|
||||||
|
])
|
||||||
|
}}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="train-image__info" v-else>{{ $t('preview.desc') }}</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -61,8 +50,17 @@ import { computed, defineComponent } from 'vue';
|
|||||||
import { useStore } from '../../store';
|
import { useStore } from '../../store';
|
||||||
import { isLocomotive } from '../../utils/vehicleUtils';
|
import { isLocomotive } from '../../utils/vehicleUtils';
|
||||||
import { ILocomotive, Vehicle } from '../../types';
|
import { ILocomotive, Vehicle } from '../../types';
|
||||||
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
mixins: [imageMixin],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
noImageAvailable: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
|
|
||||||
@@ -85,16 +83,26 @@ export default defineComponent({
|
|||||||
this.store.imageLoading = false;
|
this.store.imageLoading = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onImageError(e: Event) {
|
||||||
|
const el = e.target as HTMLImageElement;
|
||||||
|
if (el.src == '/images/placeholder.jpg') return;
|
||||||
|
|
||||||
|
el.src = '/images/placeholder.jpg';
|
||||||
|
},
|
||||||
|
|
||||||
isLocomotive(vehicle: Vehicle): vehicle is ILocomotive {
|
isLocomotive(vehicle: Vehicle): vehicle is ILocomotive {
|
||||||
return isLocomotive(vehicle);
|
return isLocomotive(vehicle);
|
||||||
},
|
},
|
||||||
|
|
||||||
onImageClick() {
|
onImageClick(e: Event) {
|
||||||
|
const target = e.target as HTMLElement;
|
||||||
|
|
||||||
const chosenVehicle = this.store.chosenVehicle;
|
const chosenVehicle = this.store.chosenVehicle;
|
||||||
|
|
||||||
if (!chosenVehicle) return;
|
if (!chosenVehicle) return;
|
||||||
|
|
||||||
this.store.vehiclePreviewSrc = `https://spythere.github.io/api/td2/images/${chosenVehicle.type}--800px.jpg`;
|
this.store.lastFocusedElement = target;
|
||||||
|
this.store.vehiclePreviewSrc = this.getThumbnailURL(chosenVehicle.type, 'large');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -104,66 +112,45 @@ export default defineComponent({
|
|||||||
@import '../../styles/global.scss';
|
@import '../../styles/global.scss';
|
||||||
|
|
||||||
.train-image-section {
|
.train-image-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
grid-row: 3;
|
grid-row: 3;
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
|
|
||||||
margin-top: 2em;
|
margin-top: 1em;
|
||||||
height: 22em;
|
height: 22em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.train-image {
|
.train-image {
|
||||||
&__wrapper {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
border: 1px solid white;
|
&.sponsor img {
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
max-width: 22em;
|
|
||||||
height: 13em;
|
|
||||||
margin: 0 auto;
|
|
||||||
|
|
||||||
&.supporter {
|
|
||||||
border: 1px solid salmon;
|
border: 1px solid salmon;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
max-width: 380px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
border: 1px solid white;
|
||||||
|
|
||||||
cursor: zoom-in;
|
cursor: zoom-in;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-message,
|
|
||||||
.no-img {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
|
|
||||||
padding: 0.3em 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-message {
|
|
||||||
background: rgba(#000, 0.75);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.train-image__info {
|
.train-image__info {
|
||||||
margin: 1em 0;
|
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
padding: 0 1em;
|
padding: 0.5em;
|
||||||
|
margin: 0.5em auto;
|
||||||
|
line-height: 1.35;
|
||||||
|
|
||||||
b {
|
width: 100%;
|
||||||
font-size: 1.1em;
|
max-width: 380px;
|
||||||
}
|
|
||||||
|
|
||||||
div {
|
background-color: $secondaryColor;
|
||||||
margin: 0.25em 0;
|
font-weight: bold;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transition animations
|
// Transition animations
|
||||||
@@ -185,4 +172,3 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,55 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="number-generator tab">
|
<div class="number-generator tab">
|
||||||
<div class="tab_header">
|
<div class="tab_header">
|
||||||
<h2>{{ $t('numgen.title') }}</h2>
|
<h2>{{ $t("numgen.title") }}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab_content">
|
<div class="tab_content">
|
||||||
<div class="options">
|
<div class="options">
|
||||||
<select v-model="chosenCategory" @change="randomizeTrainNumber()">
|
<select v-model="chosenCategory" @change="randomizeTrainNumber()">
|
||||||
<option :value="null" disabled>{{ $t('numgen.train-category') }}</option>
|
<option :value="null" disabled>
|
||||||
<option v-for="(_, category) in genData.categories" :value="category">
|
{{ $t("numgen.train-category") }}
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
v-for="(_, category) in genData.categories"
|
||||||
|
:key="category"
|
||||||
|
:value="category"
|
||||||
|
>
|
||||||
{{ $t(`numgen.categories.${category}`) }}
|
{{ $t(`numgen.categories.${category}`) }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select v-model="beginRegionName" @change="randomizeTrainNumber()">
|
<select v-model="beginRegionName" @change="randomizeTrainNumber()">
|
||||||
<option :value="null" disabled>{{ $t('numgen.start-region') }}</option>
|
<option :value="null" disabled>
|
||||||
<option v-for="(_, name) in genData.regionNumbers" :value="name">{{ name }}</option>
|
{{ $t("numgen.start-region") }}
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
v-for="(_, name) in genData.regionNumbers"
|
||||||
|
:key="name"
|
||||||
|
:value="name"
|
||||||
|
>
|
||||||
|
{{ name }}
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select v-model="endRegionName" @change="randomizeTrainNumber()">
|
<select v-model="endRegionName" @change="randomizeTrainNumber()">
|
||||||
<option :value="null" disabled>{{ $t('numgen.end-region') }}</option>
|
<option :value="null" disabled>{{ $t("numgen.end-region") }}</option>
|
||||||
<option v-for="(_, name) in genData.regionNumbers" :value="name">{{ name }}</option>
|
<option
|
||||||
|
v-for="(_, name) in genData.regionNumbers"
|
||||||
|
:key="name"
|
||||||
|
:value="name"
|
||||||
|
>
|
||||||
|
{{ name }}
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="generated-number" @click="copyNumber">
|
<div class="generated-number" @click="copyNumber">
|
||||||
<span v-if="trainNumber">
|
<span v-if="trainNumber">
|
||||||
{{ $t('numgen.number-info') }} <b class="text--accent">{{ trainNumber }}</b>
|
{{ $t("numgen.number-info") }}
|
||||||
|
<b class="text--accent">{{ trainNumber }}</b>
|
||||||
</span>
|
</span>
|
||||||
<span v-else>{{ $t('numgen.warning') }}</span>
|
<span v-else>{{ $t("numgen.warning") }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div v-if="chosenCategory">
|
<!-- <div v-if="chosenCategory">
|
||||||
@@ -50,25 +71,29 @@
|
|||||||
|
|
||||||
<div class="tab_links">
|
<div class="tab_links">
|
||||||
<a :href="$t('numgen.td2-wiki-link')" target="_blank">
|
<a :href="$t('numgen.td2-wiki-link')" target="_blank">
|
||||||
{{ $t('numgen.td2-wiki') }}
|
{{ $t("numgen.td2-wiki") }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<div class="tab_actions">
|
<div class="tab_actions">
|
||||||
<button class="btn" @click="randomizeTrainNumber(true)">{{ $t('numgen.action-random-region') }}</button>
|
<button class="btn" @click="randomizeTrainNumber(true)">
|
||||||
<button class="btn" @click="randomizeTrainNumber(false)">{{ $t('numgen.action-random-number') }}</button>
|
{{ $t("numgen.action-random-region") }}
|
||||||
|
</button>
|
||||||
|
<button class="btn" @click="randomizeTrainNumber(false)">
|
||||||
|
{{ $t("numgen.action-random-number") }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Ref, ref } from 'vue';
|
import { Ref, ref } from "vue";
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from "vue-i18n";
|
||||||
|
|
||||||
import genData from '../../constants/numberGeneratorData.json';
|
import genData from "../../constants/numberGeneratorData.json";
|
||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
type RegionName = keyof typeof genData.regionNumbers;
|
type RegionName = keyof typeof genData.regionNumbers;
|
||||||
@@ -83,7 +108,7 @@ const trainNumber = ref(null) as Ref<string | null>;
|
|||||||
const copyNumber = () => {
|
const copyNumber = () => {
|
||||||
if (trainNumber.value) {
|
if (trainNumber.value) {
|
||||||
navigator.clipboard.writeText(trainNumber.value);
|
navigator.clipboard.writeText(trainNumber.value);
|
||||||
alert(i18n.t('numgen.alert'));
|
alert(i18n.t("numgen.alert"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -93,16 +118,21 @@ const randomizeTrainNumber = (randomizeRegions = false) => {
|
|||||||
const regionKeys = Object.keys(genData.regionNumbers);
|
const regionKeys = Object.keys(genData.regionNumbers);
|
||||||
|
|
||||||
if (beginRegionName.value == null || randomizeRegions)
|
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)
|
if (endRegionName.value == null || randomizeRegions)
|
||||||
endRegionName.value = regionKeys[(regionKeys.length * Math.random()) << 0] as RegionName;
|
endRegionName.value = regionKeys[
|
||||||
|
(regionKeys.length * Math.random()) << 0
|
||||||
|
] as RegionName;
|
||||||
|
|
||||||
let number = '';
|
let number = "";
|
||||||
|
|
||||||
if (beginRegionName.value == endRegionName.value) {
|
if (beginRegionName.value == endRegionName.value) {
|
||||||
const sameRegionsNumbers = genData.sameRegions[beginRegionName.value!];
|
const sameRegionsNumbers = genData.sameRegions[beginRegionName.value!];
|
||||||
const randRegionNumber = sameRegionsNumbers[Math.floor(Math.random() * sameRegionsNumbers.length)];
|
const randRegionNumber =
|
||||||
|
sameRegionsNumbers[Math.floor(Math.random() * sameRegionsNumbers.length)];
|
||||||
number += randRegionNumber.toString();
|
number += randRegionNumber.toString();
|
||||||
} else {
|
} else {
|
||||||
const beginRegionNumber = genData.regionNumbers[beginRegionName.value!];
|
const beginRegionNumber = genData.regionNumbers[beginRegionName.value!];
|
||||||
@@ -117,23 +147,30 @@ const randomizeTrainNumber = (randomizeRegions = false) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chosenCategory.value == null) chosenCategory.value = 'EI';
|
if (chosenCategory.value == null) chosenCategory.value = "EI";
|
||||||
|
|
||||||
const rulesArray = genData.categories[chosenCategory.value].split(';').map((r) => ({
|
const rulesArray = genData.categories[chosenCategory.value]
|
||||||
index: r.split(':')[0],
|
.split(";")
|
||||||
rule: r.split(':')[1],
|
.map((r) => ({
|
||||||
nums: Number(r.split(':')[2] || '1'),
|
index: r.split(":")[0],
|
||||||
}));
|
rule: r.split(":")[1],
|
||||||
|
nums: Number(r.split(":")[2] || "1"),
|
||||||
|
}));
|
||||||
|
|
||||||
rulesArray.forEach((r) => {
|
rulesArray.forEach((r) => {
|
||||||
const range = r.rule.split('-');
|
const range = r.rule.split("-");
|
||||||
|
|
||||||
if (range.length == 1) number += r.rule;
|
if (range.length == 1) number += r.rule;
|
||||||
else {
|
else {
|
||||||
const [minRange, maxRange] = range;
|
const [minRange, maxRange] = range;
|
||||||
const randRange = Math.floor(Math.random() * (Number(maxRange) - Number(minRange)) + Number(minRange)).toString();
|
const randRange = Math.floor(
|
||||||
|
Math.random() * (Number(maxRange) - Number(minRange)) +
|
||||||
|
Number(minRange),
|
||||||
|
).toString();
|
||||||
|
|
||||||
number += new Array(Math.abs(randRange.length - r.nums)).fill('0').join('') + randRange;
|
number +=
|
||||||
|
new Array(Math.abs(randRange.length - r.nums)).fill("0").join("") +
|
||||||
|
randRange;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -142,8 +179,8 @@ const randomizeTrainNumber = (randomizeRegions = false) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../../styles/tab.scss';
|
@import "../../styles/tab.scss";
|
||||||
@import '../../styles/global.scss';
|
@import "../../styles/global.scss";
|
||||||
|
|
||||||
.options {
|
.options {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -189,4 +226,3 @@ const randomizeTrainNumber = (randomizeRegions = false) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -37,9 +37,10 @@
|
|||||||
|
|
||||||
<div class="generator_cargo">
|
<div class="generator_cargo">
|
||||||
<button
|
<button
|
||||||
|
v-for="(cargoArray, cargoName) in store.stockData?.generator.cargo"
|
||||||
|
:key="cargoName"
|
||||||
class="btn"
|
class="btn"
|
||||||
:data-chosen="chosenCargoTypes.includes(cargoName.toString())"
|
:data-chosen="chosenCargoTypes.includes(cargoName.toString())"
|
||||||
v-for="(cargoArray, cargoName) in store.stockData?.generator.cargo"
|
|
||||||
@click="toggleCargoChosen(cargoName.toString(), cargoArray)"
|
@click="toggleCargoChosen(cargoName.toString(), cargoArray)"
|
||||||
>
|
>
|
||||||
{{ $t(`cargo.${cargoName}`) }}
|
{{ $t(`cargo.${cargoName}`) }}
|
||||||
@@ -126,7 +127,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
computedChosenCarTypes() {
|
computedChosenCarTypes() {
|
||||||
return new Set<string>(this.chosenCarTypes.sort((c1, c2) => (c1 > c2 ? 1 : -1)));
|
return new Set<string>(this.chosenCarTypes.slice().sort((c1, c2) => (c1 > c2 ? 1 : -1)));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -150,44 +151,52 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
generateStock(empty = false) {
|
generateStock(empty = false) {
|
||||||
const generatedChosenStockList = this.chosenCargoTypes.reduce((acc, type) => {
|
const generatedChosenStockList = this.chosenCargoTypes.reduce(
|
||||||
this.store.stockData?.generator.cargo[type]
|
(acc, type) => {
|
||||||
.filter((c) => !this.excludedCarTypes.includes(c.split(':')[0]))
|
this.store.stockData?.generator.cargo[type]
|
||||||
.forEach((c) => {
|
.filter((c) => !this.excludedCarTypes.includes(c.split(':')[0]))
|
||||||
const [type, cargoType] = c.split(':');
|
.forEach((c) => {
|
||||||
|
const [type, cargoType] = c.split(':');
|
||||||
|
|
||||||
const carWagonObjs = this.store.carDataList.filter((cw) => cw.type.startsWith(type));
|
const carWagonObjs = this.store.carDataList.filter((cw) => cw.type.startsWith(type));
|
||||||
const cargoObjs = [] as (ICargo | undefined)[];
|
const cargoObjs = [] as (ICargo | undefined)[];
|
||||||
|
|
||||||
if (!cargoType || empty) cargoObjs.push(undefined);
|
if (!cargoType || empty) cargoObjs.push(undefined);
|
||||||
else if (cargoType == 'all') cargoObjs.push(...carWagonObjs[0]?.cargoList);
|
else if (cargoType == 'all') cargoObjs.push(...carWagonObjs[0]!.cargoList);
|
||||||
else cargoObjs.push(carWagonObjs[0]?.cargoList.find((cargo) => cargo.id == cargoType));
|
else cargoObjs.push(carWagonObjs[0]?.cargoList.find((cargo) => cargo.id == cargoType));
|
||||||
|
|
||||||
carWagonObjs.forEach((cw) => {
|
carWagonObjs.forEach((cw) => {
|
||||||
cargoObjs.forEach((cargoObj) => {
|
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)
|
if (!chosenStock)
|
||||||
acc.push({
|
acc.push({
|
||||||
constructionType: cw.constructionType,
|
constructionType: cw.constructionType,
|
||||||
carPool: [{ carWagon: cw, cargo: cargoObj }],
|
carPool: [{ carWagon: cw, cargo: cargoObj }],
|
||||||
});
|
});
|
||||||
else chosenStock.carPool.push({ carWagon: cw, cargo: cargoObj });
|
else chosenStock.carPool.push({ carWagon: cw, cargo: cargoObj });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, [] as { constructionType: string; carPool: { carWagon: ICarWagon; cargo?: ICargo }[] }[]);
|
},
|
||||||
|
[] as {
|
||||||
|
constructionType: string;
|
||||||
|
carPool: { carWagon: ICarWagon; cargo?: ICargo }[];
|
||||||
|
}[]
|
||||||
|
);
|
||||||
|
|
||||||
let bestGeneration: { stockList: IStock[]; value: number } = { stockList: [], value: 0 };
|
let bestGeneration: { stockList: IStock[]; value: number } = {
|
||||||
|
stockList: [],
|
||||||
|
value: 0,
|
||||||
|
};
|
||||||
|
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 10; i++) {
|
||||||
const headingLoco = this.store.stockList[0]?.isLoco ? this.store.stockList[0] : undefined;
|
const headingLoco = this.store.stockList[0]?.isLoco ? this.store.stockList[0] : undefined;
|
||||||
this.store.stockList.length = headingLoco ? 1 : 0;
|
this.store.stockList.length = headingLoco ? 1 : 0;
|
||||||
|
|
||||||
const maxMass =
|
const maxMass = this.store.acceptableMass > 0 ? Math.min(this.store.acceptableMass, this.maxMass) : this.maxMass;
|
||||||
this.store.acceptableMass > 0 ? Math.min(this.store.acceptableMass, this.maxMass) : this.maxMass;
|
|
||||||
|
|
||||||
let exceeded = false;
|
let exceeded = false;
|
||||||
|
|
||||||
@@ -277,13 +286,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
background-color: $secondaryColor;
|
background-color: $secondaryColor;
|
||||||
|
|
||||||
&[data-chosen='true'] {
|
|
||||||
background-color: $accentColor;
|
|
||||||
color: black;
|
|
||||||
|
|
||||||
box-shadow: 0 0 5px 1px $accentColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-excluded='true'] {
|
&[data-excluded='true'] {
|
||||||
background-color: gray;
|
background-color: gray;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
@@ -316,4 +318,3 @@ export default defineComponent({
|
|||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -5,14 +5,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stock_actions">
|
<div class="stock_actions">
|
||||||
<label class="file-label">
|
<button class="btn btn--image" @click="clickFileInput">
|
||||||
<div class="btn btn--image">
|
|
||||||
<img src="/images/icon-upload.svg" alt="" />
|
|
||||||
{{ $t('stocklist.action-upload') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input type="file" @change="uploadStock" ref="conFile" accept=".con,.txt" />
|
<input type="file" @change="uploadStock" ref="conFile" accept=".con,.txt" />
|
||||||
</label>
|
<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">
|
<button class="btn btn--image" :data-disabled="stockIsEmpty" :disabled="stockIsEmpty" @click="downloadStock">
|
||||||
<img src="/images/icon-download.svg" alt="download icon" />
|
<img src="/images/icon-download.svg" alt="download icon" />
|
||||||
@@ -37,36 +34,26 @@
|
|||||||
|
|
||||||
<div class="stock_controls" :data-disabled="store.chosenStockListIndex == -1">
|
<div class="stock_controls" :data-disabled="store.chosenStockListIndex == -1">
|
||||||
<b v-if="store.chosenStockListIndex >= 0">
|
<b v-if="store.chosenStockListIndex >= 0">
|
||||||
{{ $t('stocklist.vehicle-no') }} <span class="text--accent">{{ store.chosenStockListIndex + 1 }}</span>
|
{{ $t('stocklist.vehicle-no') }}
|
||||||
|
<span class="text--accent">{{ store.chosenStockListIndex + 1 }}</span>
|
||||||
|
|
||||||
</b>
|
</b>
|
||||||
|
|
||||||
<b v-else>
|
<b v-else>
|
||||||
{{ $t('stocklist.no-vehicle-chosen') }}
|
{{ $t('stocklist.no-vehicle-chosen') }}
|
||||||
</b>
|
</b>
|
||||||
|
|
||||||
<button
|
<button class="btn" :tabindex="store.chosenStockListIndex == -1 ? -1 : 0" @click="moveUpStock(store.chosenStockListIndex)">
|
||||||
class="btn"
|
|
||||||
:tabindex="store.chosenStockListIndex == -1 ? -1 : 0"
|
|
||||||
@click="moveUpStock(store.chosenStockListIndex)"
|
|
||||||
>
|
|
||||||
<img :src="getIconURL('higher')" alt="move up vehicle" />
|
<img :src="getIconURL('higher')" alt="move up vehicle" />
|
||||||
{{ $t('stocklist.action-move-up') }}
|
{{ $t('stocklist.action-move-up') }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button class="btn" :tabindex="store.chosenStockListIndex == -1 ? -1 : 0" @click="moveDownStock(store.chosenStockListIndex)">
|
||||||
class="btn"
|
|
||||||
:tabindex="store.chosenStockListIndex == -1 ? -1 : 0"
|
|
||||||
@click="moveDownStock(store.chosenStockListIndex)"
|
|
||||||
>
|
|
||||||
<img :src="getIconURL('lower')" alt="move down vehicle" />
|
<img :src="getIconURL('lower')" alt="move down vehicle" />
|
||||||
{{ $t('stocklist.action-move-down') }}
|
{{ $t('stocklist.action-move-down') }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button class="btn" :tabindex="store.chosenStockListIndex == -1 ? -1 : 0" @click="removeStock(store.chosenStockListIndex)">
|
||||||
class="btn"
|
|
||||||
:tabindex="store.chosenStockListIndex == -1 ? -1 : 0"
|
|
||||||
@click="removeStock(store.chosenStockListIndex)"
|
|
||||||
>
|
|
||||||
<img :src="getIconURL('remove')" alt="remove vehicle" />
|
<img :src="getIconURL('remove')" alt="remove vehicle" />
|
||||||
{{ $t('stocklist.action-remove') }}
|
{{ $t('stocklist.action-remove') }}
|
||||||
</button>
|
</button>
|
||||||
@@ -82,22 +69,19 @@
|
|||||||
</b>
|
</b>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
{{ $t('stocklist.mass') }} <span class="text--accent">{{ store.totalMass }}t</span> ({{
|
{{ $t('stocklist.mass') }}
|
||||||
$t('stocklist.mass-accepted')
|
<span class="text--accent">{{ store.totalMass }}t</span> ({{ $t('stocklist.mass-accepted') }}:
|
||||||
}}: <span class="text--accent">{{ store.acceptableMass ? store.acceptableMass + 't' : '-' }}</span
|
<span class="text--accent">{{ store.acceptableMass ? store.acceptableMass + 't' : '-' }}</span
|
||||||
>) - {{ $t('stocklist.length') }}:
|
>) - {{ $t('stocklist.length') }}:
|
||||||
<span class="text--accent">{{ store.totalLength }}m</span>
|
<span class="text--accent">{{ store.totalLength }}m</span>
|
||||||
- {{ $t('stocklist.vmax') }}: <span class="text--accent">{{ store.maxStockSpeed }} km/h</span>
|
- {{ $t('stocklist.vmax') }}:
|
||||||
|
<span class="text--accent">{{ store.maxStockSpeed }} km/h</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stock_cold-start">
|
<div class="stock_cold-start">
|
||||||
<label>
|
<label>
|
||||||
<input
|
<input type="checkbox" v-model="store.isColdStart" :disabled="!locoSupportsColdStart(store.stockList[0]?.constructionType || '')" />
|
||||||
type="checkbox"
|
|
||||||
v-model="store.isColdStart"
|
|
||||||
:disabled="!locoSupportsColdStart(store.stockList[0]?.constructionType || '')"
|
|
||||||
/>
|
|
||||||
{{ $t('stocklist.coldstart-info') }}
|
{{ $t('stocklist.coldstart-info') }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -105,22 +89,15 @@
|
|||||||
<div class="stock_warnings" v-if="stockHasWarnings">
|
<div class="stock_warnings" v-if="stockHasWarnings">
|
||||||
<div class="warning" v-if="locoNotSuitable">(!) {{ $t('stocklist.warning-not-suitable') }}</div>
|
<div class="warning" v-if="locoNotSuitable">(!) {{ $t('stocklist.warning-not-suitable') }}</div>
|
||||||
|
|
||||||
<div class="warning" v-if="trainTooLong && store.isTrainPassenger">
|
<div class="warning" v-if="trainTooLong && store.isTrainPassenger">(!) {{ $t('stocklist.warning-passenger-too-long') }}</div>
|
||||||
(!) {{ $t('stocklist.warning-passenger-too-long') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="warning" v-if="trainTooLong && !store.isTrainPassenger">
|
<div class="warning" v-if="trainTooLong && !store.isTrainPassenger">(!) {{ $t('stocklist.warning-freight-too-long') }}</div>
|
||||||
(!) {{ $t('stocklist.warning-freight-too-long') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="warning" v-if="trainTooHeavy">
|
<div class="warning" v-if="trainTooHeavy">
|
||||||
(!)
|
(!)
|
||||||
<i18n-t keypath="stocklist.warning-too-heavy">
|
<i18n-t keypath="stocklist.warning-too-heavy">
|
||||||
<template #href>
|
<template #href>
|
||||||
<a
|
<a target="_blank" href="https://docs.google.com/spreadsheets/d/1bFXUsHsAu4youmNz-46Q1HslZaaoklvfoBDS553TnNk/edit">
|
||||||
target="_blank"
|
|
||||||
href="https://docs.google.com/spreadsheets/d/1bFXUsHsAu4youmNz-46Q1HslZaaoklvfoBDS553TnNk/edit"
|
|
||||||
>
|
|
||||||
{{ $t('stocklist.acceptable-mass-docs') }}
|
{{ $t('stocklist.acceptable-mass-docs') }}
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
@@ -153,23 +130,19 @@
|
|||||||
@keydown.backspace="removeStock(i)"
|
@keydown.backspace="removeStock(i)"
|
||||||
ref="itemRefs"
|
ref="itemRefs"
|
||||||
>
|
>
|
||||||
<div
|
<div class="stock-info" @dragstart="onDragStart(i)" @drop="onDrop($event, i)" @dragover="allowDrop" draggable="true">
|
||||||
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 class="stock-info__no" :data-selected="i == store.chosenStockListIndex">
|
||||||
<span v-if="i == store.chosenStockListIndex">• </span>
|
<span v-if="i == store.chosenStockListIndex">• </span>
|
||||||
{{ i + 1 }}.
|
{{ i + 1 }}.
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="stock-info__type" :class="{ supporter: stock.supportersOnly }">
|
<span class="stock-info__type" :class="{ sponsor: stock.isSponsorsOnly }">
|
||||||
{{ stock.isLoco ? stock.type : getCarSpecFromType(stock.type) }}
|
{{ stock.isLoco ? stock.type : getCarSpecFromType(stock.type) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="stock-info__cargo" v-if="stock.cargo"> {{ stock.cargo.id }} </span>
|
<span class="stock-info__cargo" v-if="stock.cargo">
|
||||||
|
{{ stock.cargo.id }}
|
||||||
|
</span>
|
||||||
<span class="stock-info__length"> {{ stock.length }}m </span>
|
<span class="stock-info__length"> {{ stock.length }}m </span>
|
||||||
<span class="stock-info__mass">{{ stock.cargo ? stock.cargo.totalMass : stock.mass }}t </span>
|
<span class="stock-info__mass">{{ stock.cargo ? stock.cargo.totalMass : stock.mass }}t </span>
|
||||||
<span class="stock-info__speed"> {{ stock.maxSpeed }}km/h </span>
|
<span class="stock-info__speed"> {{ stock.maxSpeed }}km/h </span>
|
||||||
@@ -182,7 +155,6 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import TrainImage from '../sections/TrainImageSection.vue';
|
|
||||||
|
|
||||||
import { useStore } from '../../store';
|
import { useStore } from '../../store';
|
||||||
|
|
||||||
@@ -195,7 +167,7 @@ import stockMixin from '../../mixins/stockMixin';
|
|||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'stock-list',
|
name: 'stock-list',
|
||||||
components: { TrainImage, StockThumbnails },
|
components: { StockThumbnails },
|
||||||
|
|
||||||
mixins: [warningsMixin, imageMixin, stockMixin, stockPreviewMixin],
|
mixins: [warningsMixin, imageMixin, stockMixin, stockPreviewMixin],
|
||||||
|
|
||||||
@@ -219,8 +191,7 @@ export default defineComponent({
|
|||||||
return this.store.stockList
|
return this.store.stockList
|
||||||
.map((stock, i) => {
|
.map((stock, i) => {
|
||||||
let stockTypeStr = stock.isLoco || !stock.cargo ? stock.type : `${stock.type}:${stock.cargo.id}`;
|
let stockTypeStr = stock.isLoco || !stock.cargo ? stock.type : `${stock.type}:${stock.cargo.id}`;
|
||||||
let coldStart =
|
let coldStart = i == 0 && this.store.isColdStart && locoSupportsColdStart(stock.constructionType || '') ? ',c' : '';
|
||||||
i == 0 && this.store.isColdStart && locoSupportsColdStart(stock.constructionType || '') ? ',c' : '';
|
|
||||||
|
|
||||||
return stockTypeStr + coldStart;
|
return stockTypeStr + coldStart;
|
||||||
})
|
})
|
||||||
@@ -251,11 +222,14 @@ export default defineComponent({
|
|||||||
}, 20);
|
}, 20);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
clickFileInput() {
|
||||||
|
(this.$refs['conFile'] as HTMLInputElement).click();
|
||||||
|
},
|
||||||
|
|
||||||
onListItemClick(stockID: number) {
|
onListItemClick(stockID: number) {
|
||||||
const stock = this.store.stockList[stockID];
|
const stock = this.store.stockList[stockID];
|
||||||
|
|
||||||
this.store.chosenStockListIndex =
|
this.store.chosenStockListIndex = this.store.chosenStockListIndex == stockID && this.store.chosenVehicle?.type == stock.type ? -1 : stockID;
|
||||||
this.store.chosenStockListIndex == stockID && this.store.chosenVehicle?.type == stock.type ? -1 : stockID;
|
|
||||||
|
|
||||||
if (this.store.chosenStockListIndex == -1) {
|
if (this.store.chosenStockListIndex == -1) {
|
||||||
this.store.chosenVehicle = null;
|
this.store.chosenVehicle = null;
|
||||||
@@ -352,9 +326,9 @@ export default defineComponent({
|
|||||||
downloadStock() {
|
downloadStock() {
|
||||||
if (this.store.stockList.length == 0) return alert(this.$t('stocklist.alert-empty'));
|
if (this.store.stockList.length == 0) return alert(this.$t('stocklist.alert-empty'));
|
||||||
|
|
||||||
const defaultName = `${this.store.chosenRealStockName || this.store.stockList[0].type} ${
|
const defaultName = `${this.store.chosenRealStockName || this.store.stockList[0].type} ${this.store.totalMass}t; ${
|
||||||
this.store.totalMass
|
this.store.totalLength
|
||||||
}t; ${this.store.totalLength}m; vmax ${this.store.maxStockSpeed}`;
|
}m; vmax ${this.store.maxStockSpeed}`;
|
||||||
|
|
||||||
const fileName = prompt(this.$t('stocklist.prompt-file'), defaultName);
|
const fileName = prompt(this.$t('stocklist.prompt-file'), defaultName);
|
||||||
|
|
||||||
@@ -487,12 +461,13 @@ export default defineComponent({
|
|||||||
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
|
||||||
label.file-label {
|
button {
|
||||||
text-align: center;
|
width: 100%;
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
input {
|
input {
|
||||||
display: none;
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -549,7 +524,7 @@ li > .stock-info {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.supporter {
|
.sponsor {
|
||||||
color: salmon;
|
color: salmon;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -610,4 +585,3 @@ li > .stock-info {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
+112
-140
@@ -7,10 +7,10 @@
|
|||||||
<div class="tab_content">
|
<div class="tab_content">
|
||||||
<div class="actions-panel">
|
<div class="actions-panel">
|
||||||
<div class="actions-panel_vehicles">
|
<div class="actions-panel_vehicles">
|
||||||
<button class="btn btn--choice" @click="changeWikiMode('locomotives')">
|
<button class="btn" :data-chosen="currentFilterMode == 'tractions'" @click="toggleFilter('tractions')">
|
||||||
{{ $t('wiki.action-vehicles') }}
|
{{ $t('wiki.action-vehicles') }}
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn--choice" @click="changeWikiMode('carWagons')">
|
<button class="btn" :data-chosen="currentFilterMode == 'carriages'" @click="toggleFilter('carriages')">
|
||||||
{{ $t('wiki.action-carriages') }}
|
{{ $t('wiki.action-carriages') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -20,70 +20,58 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-wrapper" @scroll="scrollEvent" ref="table-wrapper">
|
<div class="table-wrapper" ref="table-wrapper">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th v-for="header in wikiMode == 'locomotives' ? locoHeaders : carHeaders" @click="toggleSorter(header)">
|
<th v-for="header in visibleHeaders" @click="toggleSorter(header)" :key="header.id">
|
||||||
{{ $t(`wiki.header.${header.id}`) }}
|
{{ $t(`wiki.header.${header.id}`) }}
|
||||||
|
|
||||||
<span v-if="currentModeSorter.id == header.id">
|
<span v-if="currentSorter.id == header.id">
|
||||||
{{ currentModeSorter.direction == 1 ? `⇑` : `⇓` }}
|
{{ currentSorter.direction == 1 ? `⇑` : `⇓` }}
|
||||||
</span>
|
</span>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody v-if="wikiMode == 'locomotives'">
|
<tbody>
|
||||||
<tr
|
<tr
|
||||||
v-for="loco in computedLocoList"
|
v-for="{ vehicle, show } in computedTableData"
|
||||||
@click="previewLocomotive(loco)"
|
|
||||||
@keydown.enter="previewLocomotive(loco)"
|
|
||||||
@dblclick="addLocomotive(loco)"
|
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
v-show="show"
|
||||||
|
:key="vehicle.type"
|
||||||
|
@click="previewVehicle(vehicle)"
|
||||||
|
@keydown.enter="previewVehicle(vehicle)"
|
||||||
|
@dblclick="addVehicle(vehicle)"
|
||||||
>
|
>
|
||||||
<td>
|
<td style="width: 120px">
|
||||||
<img
|
<img
|
||||||
:src="`https://spythere.github.io/api/td2/images/${loco.type}--300px.jpg`"
|
width="120"
|
||||||
|
:src="getThumbnailURL(vehicle.type, 'small')"
|
||||||
|
:alt="`${vehicle.type}`"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
:alt="`Lokomotywa ${loco.type}`"
|
@error="(e) => ((e.target as HTMLElement).style.display = 'none')"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>{{ loco.type }}</td>
|
<td :data-sponsoronly="vehicle.isSponsorsOnly">{{ vehicle.type }}</td>
|
||||||
<td>{{ $t(`wiki.${loco.power}`) }}</td>
|
|
||||||
<td>{{ loco.constructionType }}</td>
|
|
||||||
<td>{{ locoSupportsColdStart(loco.constructionType) ? `✓` : '✗' }}</td>
|
|
||||||
<td>{{ loco.length }}m</td>
|
|
||||||
<td>{{ loco.mass }}t</td>
|
|
||||||
<td>{{ loco.maxSpeed }}km/h</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
|
|
||||||
<tbody v-else>
|
<td v-if="isLocomotive(vehicle)">{{ $t(`wiki.${vehicle.power}`) }}</td>
|
||||||
<tr
|
<td v-else>{{ $t(`wiki.${vehicle.useType}`) }}</td>
|
||||||
v-for="car in computedCarList"
|
|
||||||
@keydow.enter="previewCarWagon(car)"
|
<td>{{ vehicle.constructionType }}</td>
|
||||||
@click="previewCarWagon(car)"
|
<td>{{ vehicle.length }}m</td>
|
||||||
@dblclick="addCarWagon(car)"
|
<td>{{ vehicle.mass }}t</td>
|
||||||
tabindex="0"
|
<td>{{ vehicle.maxSpeed }}km/h</td>
|
||||||
>
|
|
||||||
<td>
|
<td v-if="currentFilterMode == 'carriages'">{{ !isLocomotive(vehicle) ? vehicle.cargoList.length : '---' }}</td>
|
||||||
<img
|
<td v-if="currentFilterMode == 'tractions'">
|
||||||
:src="`https://spythere.github.io/api/td2/images/${car.type}--300px.jpg`"
|
{{ isLocomotive(vehicle) ? (locoSupportsColdStart(vehicle.constructionType) ? `✓` : '✗') : '---' }}
|
||||||
loading="lazy"
|
|
||||||
:alt="`Lokomotywa ${car.type}`"
|
|
||||||
/>
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>{{ car.type }}</td>
|
|
||||||
<td>{{ car.constructionType }}</td>
|
|
||||||
<td>{{ car.length }}m</td>
|
|
||||||
<td>{{ car.mass }}t</td>
|
|
||||||
<td>{{ car.maxSpeed }}km/h</td>
|
|
||||||
<td>{{ car.cargoList.length == 0 ? '-' : car.cargoList.length }}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
||||||
|
<span ref="table-bottom"></span>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -97,152 +85,139 @@ import stockPreviewMixin from '../../mixins/stockPreviewMixin';
|
|||||||
import { Vehicle } from '../../types';
|
import { Vehicle } from '../../types';
|
||||||
import { isLocomotive } from '../../utils/vehicleUtils';
|
import { isLocomotive } from '../../utils/vehicleUtils';
|
||||||
import stockMixin from '../../mixins/stockMixin';
|
import stockMixin from '../../mixins/stockMixin';
|
||||||
|
import imageMixin from '../../mixins/imageMixin';
|
||||||
import { locoSupportsColdStart } from '../../utils/locoUtils';
|
import { locoSupportsColdStart } from '../../utils/locoUtils';
|
||||||
|
|
||||||
type WikiMode = 'locomotives' | 'carWagons';
|
type SorterID = 'type' | 'constructionType' | 'image' | 'length' | 'mass' | 'maxSpeed' | 'cargoCount' | 'group' | 'coldStart';
|
||||||
type SorterID =
|
|
||||||
| 'type'
|
|
||||||
| 'constructionType'
|
|
||||||
| 'image'
|
|
||||||
| 'length'
|
|
||||||
| 'mass'
|
|
||||||
| 'maxSpeed'
|
|
||||||
| 'cargoCount'
|
|
||||||
| 'power'
|
|
||||||
| 'coldStart';
|
|
||||||
|
|
||||||
interface WikiHeader {
|
interface IWikiHeader {
|
||||||
id: SorterID;
|
id: SorterID;
|
||||||
sortable: boolean;
|
sortable: boolean;
|
||||||
|
for: 'all' | 'carriages' | 'tractions';
|
||||||
}
|
}
|
||||||
|
|
||||||
const locoHeaders: WikiHeader[] = [
|
interface IWikiRow {
|
||||||
{ id: 'image', sortable: false },
|
vehicle: Vehicle;
|
||||||
{ id: 'type', sortable: true },
|
show: boolean;
|
||||||
{ id: 'power', sortable: true },
|
}
|
||||||
{ id: 'constructionType', sortable: true },
|
|
||||||
{ id: 'coldStart', sortable: true },
|
|
||||||
{ id: 'length', sortable: true },
|
|
||||||
{ id: 'mass', sortable: true },
|
|
||||||
{ id: 'maxSpeed', sortable: true },
|
|
||||||
];
|
|
||||||
|
|
||||||
const carHeaders: WikiHeader[] = [
|
const headers: IWikiHeader[] = [
|
||||||
{ id: 'image', sortable: false },
|
{ id: 'image', sortable: false, for: 'all' },
|
||||||
{ id: 'type', sortable: true },
|
{ id: 'type', sortable: true, for: 'all' },
|
||||||
{ id: 'constructionType', sortable: true },
|
{ id: 'group', sortable: true, for: 'all' },
|
||||||
{ id: 'length', sortable: true },
|
{ id: 'constructionType', sortable: true, for: 'all' },
|
||||||
{ id: 'mass', sortable: true },
|
{ id: 'length', sortable: true, for: 'all' },
|
||||||
{ id: 'maxSpeed', sortable: true },
|
{ id: 'mass', sortable: true, for: 'all' },
|
||||||
{ id: 'cargoCount', sortable: true },
|
{ id: 'maxSpeed', sortable: true, for: 'all' },
|
||||||
|
{ id: 'coldStart', sortable: true, for: 'tractions' },
|
||||||
|
{ id: 'cargoCount', sortable: true, for: 'carriages' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
mixins: [stockPreviewMixin, stockMixin],
|
mixins: [stockPreviewMixin, stockMixin, imageMixin],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
store: useStore(),
|
store: useStore(),
|
||||||
locoHeaders,
|
headers,
|
||||||
carHeaders,
|
|
||||||
|
|
||||||
locosScrollTop: 0,
|
scrollTop: 0,
|
||||||
carsScrollTop: 0,
|
|
||||||
|
|
||||||
wikiMode: 'locomotives' as WikiMode,
|
|
||||||
searchedVehicleTypeName: '',
|
searchedVehicleTypeName: '',
|
||||||
|
|
||||||
currentLocoSorter: {
|
currentSorter: {
|
||||||
id: 'type' as SorterID,
|
id: 'type' as SorterID,
|
||||||
direction: 1,
|
direction: 1,
|
||||||
},
|
},
|
||||||
|
|
||||||
currentCarSorter: {
|
currentFilterMode: 'all' as 'all' | 'tractions' | 'carriages',
|
||||||
id: 'type' as SorterID,
|
|
||||||
direction: 1,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
activated() {
|
activated() {
|
||||||
const tableWrapperRef = this.$refs['table-wrapper'] as HTMLElement;
|
const tableWrapperRef = this.$refs['table-wrapper'] as HTMLElement;
|
||||||
tableWrapperRef.scrollTo({ top: this.wikiMode == 'locomotives' ? this.locosScrollTop : this.carsScrollTop });
|
|
||||||
|
tableWrapperRef.scrollTo({
|
||||||
|
top: this.scrollTop,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
locoSupportsColdStart,
|
locoSupportsColdStart,
|
||||||
|
isLocomotive,
|
||||||
|
|
||||||
scrollEvent(e: Event) {
|
toggleFilter(name: typeof this.currentFilterMode) {
|
||||||
const tableScrollTop = (e.target as HTMLElement).scrollTop;
|
this.currentFilterMode = this.currentFilterMode == name ? 'all' : name;
|
||||||
|
|
||||||
if (this.wikiMode == 'locomotives') this.locosScrollTop = tableScrollTop;
|
|
||||||
else this.carsScrollTop = tableScrollTop;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
changeWikiMode(wikiMode: WikiMode) {
|
toggleSorter(header: IWikiHeader) {
|
||||||
this.searchedVehicleTypeName = '';
|
|
||||||
this.wikiMode = wikiMode;
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleSorter(header: WikiHeader) {
|
|
||||||
if (!header.sortable) return;
|
if (!header.sortable) return;
|
||||||
|
|
||||||
if (header.id == this.currentModeSorter.id) this.currentModeSorter.direction *= -1;
|
if (header.id == this.currentSorter.id) this.currentSorter.direction *= -1;
|
||||||
this.currentModeSorter.id = header.id;
|
this.currentSorter.id = header.id;
|
||||||
},
|
},
|
||||||
|
|
||||||
sortVehicles(vA: Vehicle, vB: Vehicle) {
|
sortTableRows(row1: IWikiRow, row2: IWikiRow) {
|
||||||
const { id, direction } = this.currentModeSorter;
|
if (!row1.show) return 0;
|
||||||
const vehiclesAreLocos = isLocomotive(vA) && isLocomotive(vB);
|
|
||||||
const vehiclesAreCars = !isLocomotive(vA) && !isLocomotive(vB);
|
const { id, direction } = this.currentSorter;
|
||||||
|
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case 'type':
|
case 'type':
|
||||||
case 'constructionType':
|
case 'constructionType':
|
||||||
return direction == 1 ? vA[id].localeCompare(vB[id]) : vB[id].localeCompare(vA[id]);
|
case 'group':
|
||||||
|
return direction == 1 ? row1.vehicle[id].localeCompare(row2.vehicle[id]) : row2.vehicle[id].localeCompare(row1.vehicle[id]);
|
||||||
|
|
||||||
case 'mass':
|
case 'mass':
|
||||||
case 'length':
|
case 'length':
|
||||||
case 'maxSpeed':
|
case 'maxSpeed':
|
||||||
return Math.sign(vA[id] - vB[id]) * direction;
|
return Math.sign(row1.vehicle[id] - row2.vehicle[id]) * direction;
|
||||||
|
|
||||||
case 'cargoCount':
|
case 'cargoCount':
|
||||||
if (vehiclesAreCars) return Math.sign((vA.cargoList.length || -1) - (vB.cargoList.length || -1)) * direction;
|
return (
|
||||||
|
(!isLocomotive(row1.vehicle) ? Math.sign(row1.vehicle.cargoList.length || -1) : -1) -
|
||||||
|
(!isLocomotive(row2.vehicle) ? (row2.vehicle.cargoList.length || -1) * direction : -1)
|
||||||
|
);
|
||||||
|
|
||||||
case 'coldStart':
|
case 'coldStart':
|
||||||
if (vehiclesAreLocos)
|
return (locoSupportsColdStart(row1.vehicle.constructionType) > locoSupportsColdStart(row2.vehicle.constructionType) ? 1 : -1) * direction;
|
||||||
return (
|
|
||||||
(locoSupportsColdStart(vA.constructionType) > locoSupportsColdStart(vB.constructionType) ? 1 : -1) *
|
|
||||||
direction
|
|
||||||
);
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return direction == 1 ? vA.type.localeCompare(vB.type) : vB.type.localeCompare(vA.type);
|
return direction == 1 ? row1.vehicle.type.localeCompare(row2.vehicle.type) : row2.vehicle.type.localeCompare(row1.vehicle.type);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
currentModeSorter() {
|
computedTableData(): IWikiRow[] {
|
||||||
return this.wikiMode == 'carWagons' ? this.currentCarSorter : this.currentLocoSorter;
|
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));
|
||||||
},
|
},
|
||||||
|
|
||||||
computedLocoList() {
|
visibleHeaders() {
|
||||||
const trimmedSearchValue = this.searchedVehicleTypeName.trim();
|
const filtersActive = this.currentFilterMode;
|
||||||
|
|
||||||
return this.store.locoDataList
|
return this.headers.filter((header) => header.for == 'all' || header.for == filtersActive);
|
||||||
.filter((loco) => new RegExp(`${trimmedSearchValue}`, 'i').test(loco.type))
|
|
||||||
.sort(this.sortVehicles);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
computedCarList() {
|
areTractionVehiclesShown() {
|
||||||
const trimmedSearchValue = this.searchedVehicleTypeName.trim();
|
return this.currentFilterMode == 'all' || this.currentFilterMode == 'tractions';
|
||||||
|
},
|
||||||
|
|
||||||
return this.store.carDataList
|
areCarriagesShown() {
|
||||||
.filter((car) => new RegExp(`${trimmedSearchValue}`, 'i').test(car.type))
|
return this.currentFilterMode == 'all' || this.currentFilterMode == 'carriages';
|
||||||
.sort(this.sortVehicles);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -300,6 +275,10 @@ export default defineComponent({
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: #333;
|
background-color: #333;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
&:nth-child(odd) {
|
&:nth-child(odd) {
|
||||||
background-color: #444;
|
background-color: #444;
|
||||||
}
|
}
|
||||||
@@ -311,29 +290,22 @@ export default defineComponent({
|
|||||||
|
|
||||||
td {
|
td {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 0.25em;
|
height: 70px;
|
||||||
height: 85px;
|
|
||||||
}
|
|
||||||
|
|
||||||
td:first-child {
|
&[data-sponsoronly='true'] {
|
||||||
width: 120px;
|
color: salmon;
|
||||||
}
|
}
|
||||||
|
|
||||||
td img {
|
|
||||||
display: block;
|
|
||||||
width: 120px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $breakpointMd) {
|
@media screen and (max-width: $breakpointMd) {
|
||||||
.wiki-list table {
|
.wiki-list table {
|
||||||
td {
|
th {
|
||||||
width: 100px;
|
min-width: 100px;
|
||||||
height: auto;
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 6em;
|
max-width: 100px;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from "vue";
|
||||||
import { useStore } from '../../store';
|
import { useStore } from "../../store";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<div class="stock_thumbnails" ref="thumbnailsRef">
|
<div class="stock_thumbnails" ref="thumbnailsRef">
|
||||||
<div
|
<div
|
||||||
v-for="(stock, stockIndex) in store.stockList"
|
v-for="(stock, stockIndex) in store.stockList"
|
||||||
|
:key="stockIndex"
|
||||||
:data-selected="store.chosenStockListIndex == stockIndex"
|
:data-selected="store.chosenStockListIndex == stockIndex"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
@dragstart="onDragStart(stockIndex)"
|
@dragstart="onDragStart(stockIndex)"
|
||||||
@@ -9,7 +10,7 @@
|
|||||||
@dragover="allowDrop"
|
@dragover="allowDrop"
|
||||||
>
|
>
|
||||||
<span @click="onListItemClick(stockIndex)" :key="stock.id">
|
<span @click="onListItemClick(stockIndex)" :key="stock.id">
|
||||||
<b :class="{ supporter: stock.supportersOnly }">
|
<b :class="{ sponsor: stock.isSponsorsOnly }">
|
||||||
{{ stock.type }}
|
{{ stock.type }}
|
||||||
</b>
|
</b>
|
||||||
|
|
||||||
@@ -52,9 +53,11 @@ watch(
|
|||||||
if (index < 0) return;
|
if (index < 0) return;
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
(thumbnailsRef.value as HTMLElement)
|
(thumbnailsRef.value as HTMLElement).querySelector(`div:nth-child(${index + 1})`)?.scrollIntoView({
|
||||||
.querySelector(`div:nth-child(${index + 1})`)
|
block: 'nearest',
|
||||||
?.scrollIntoView({ block: 'nearest', inline: 'start', behavior: 'smooth' });
|
inline: 'start',
|
||||||
|
behavior: 'smooth',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -118,8 +121,7 @@ const allowDrop = (e: DragEvent) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.supporter {
|
.sponsor {
|
||||||
color: salmon;
|
color: salmon;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
},
|
},
|
||||||
"sameRegions": {
|
"sameRegions": {
|
||||||
"Losowy": [
|
"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,
|
10, 11, 19, 91, 93, 97, 99, 20, 22, 29, 30, 33, 39, 40, 44, 49, 94, 50,
|
||||||
79, 88, 80, 89, 92, 98
|
55, 59, 90, 95, 96, 66, 60, 69, 77, 70, 79, 88, 80, 89, 92, 98
|
||||||
],
|
],
|
||||||
"Warszawa": [10, 11, 19, 91, 93, 97, 99],
|
"Warszawa": [10, 11, 19, 91, 93, 97, 99],
|
||||||
"Lublin": [20, 22, 29],
|
"Lublin": [20, 22, 29],
|
||||||
@@ -35,4 +35,3 @@
|
|||||||
"LT": "2:5;3:0-899:3"
|
"LT": "2:5;3:0-899:3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,12 @@
|
|||||||
},
|
},
|
||||||
"cargo": null
|
"cargo": null
|
||||||
},
|
},
|
||||||
|
"EP09": {
|
||||||
|
"passenger": {
|
||||||
|
"650": 160
|
||||||
|
},
|
||||||
|
"cargo": null
|
||||||
|
},
|
||||||
"ET41": {
|
"ET41": {
|
||||||
"passenger": {
|
"passenger": {
|
||||||
"700": 125
|
"700": 125
|
||||||
@@ -58,5 +64,3 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
export const enum EVehicleUseType {
|
export const enum EVehicleUseType {
|
||||||
LOCO_ELECTRICAL = 'loco-e',
|
LOCO_ELECTRICAL = "loco-e",
|
||||||
LOCO_DIESEL = "loco-s",
|
LOCO_DIESEL = "loco-s",
|
||||||
EMU = "loco-ezt",
|
EMU = "loco-ezt",
|
||||||
DMU = "loco-szt",
|
DMU = "loco-szt",
|
||||||
|
|
||||||
CAR_PASSENGER = "car-passenger",
|
CAR_PASSENGER = "car-passenger",
|
||||||
CAR_CARGO = "car-cargo"
|
CAR_CARGO = "car-cargo",
|
||||||
}
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
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",
|
||||||
|
});
|
||||||
|
|
||||||
|
export default http;
|
||||||
+13
-11
@@ -1,31 +1,33 @@
|
|||||||
import localePL from './locales/pl.json';
|
import localePL from "./locales/pl.json";
|
||||||
import localeEN from './locales/en.json';
|
import localeEN from "./locales/en.json";
|
||||||
import { createI18n } from 'vue-i18n';
|
import { createI18n } from "vue-i18n";
|
||||||
import axios from 'axios';
|
import http from "./http";
|
||||||
|
|
||||||
type LocaleMessageSchema = typeof localePL;
|
type LocaleMessageSchema = typeof localePL;
|
||||||
type LocaleKey = 'en' | 'pl';
|
type LocaleKey = "en" | "pl";
|
||||||
|
|
||||||
const locales: { [key in LocaleKey]: LocaleMessageSchema } = {
|
const locales: { [key in LocaleKey]: LocaleMessageSchema } = {
|
||||||
en: localeEN,
|
en: localeEN,
|
||||||
pl: localePL,
|
pl: localePL,
|
||||||
};
|
};
|
||||||
|
|
||||||
const locale = window.localStorage.getItem('locale') || (/^pl\b/.test(navigator.language) ? 'pl' : 'en');
|
const locale =
|
||||||
|
window.localStorage.getItem("locale") ||
|
||||||
|
(/^pl\b/.test(navigator.language) ? "pl" : "en");
|
||||||
|
|
||||||
const i18n = createI18n<[LocaleMessageSchema], 'en' | 'pl'>({
|
const i18n = createI18n<[LocaleMessageSchema], "en" | "pl">({
|
||||||
locale,
|
locale,
|
||||||
fallbackLocale: 'pl',
|
fallbackLocale: "pl",
|
||||||
legacy: false,
|
legacy: false,
|
||||||
globalInjection: true,
|
globalInjection: true,
|
||||||
messages: locales,
|
messages: locales,
|
||||||
});
|
});
|
||||||
|
|
||||||
async function fetchBackendTranslations() {
|
async function fetchBackendTranslations() {
|
||||||
const localeData = (await axios.get(`https://spythere.github.io/api/td2/data/locales.json`)).data;
|
const localeData = (await http.get(`td2/data/locales.json`)).data;
|
||||||
|
|
||||||
i18n.global.mergeLocaleMessage('pl', localeData.pl);
|
i18n.global.mergeLocaleMessage("pl", localeData.pl);
|
||||||
i18n.global.mergeLocaleMessage('en', localeData.en);
|
i18n.global.mergeLocaleMessage("en", localeData.en);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchBackendTranslations();
|
fetchBackendTranslations();
|
||||||
|
|||||||
+6
-4
@@ -29,13 +29,13 @@
|
|||||||
"title": "RAILWAY VEHICLE PREVIEW",
|
"title": "RAILWAY VEHICLE PREVIEW",
|
||||||
"loading": "IMAGE LOADING...",
|
"loading": "IMAGE LOADING...",
|
||||||
"desc": "Choose a railway vehicle above to see its preview",
|
"desc": "Choose a railway vehicle above to see its preview",
|
||||||
"sponsor-only": "* SPONSORS ONLY",
|
"sponsor-only": "* SPONSORS ONLY UNTIL {0}",
|
||||||
"loco-e": "ELECTRIC LOCO",
|
"loco-e": "ELECTRIC LOCO",
|
||||||
"loco-s": "DIESEL LOCO",
|
"loco-s": "DIESEL LOCO",
|
||||||
"loco-ezt": "ELECTRIC M.U.",
|
"loco-ezt": "ELECTRIC M.U.",
|
||||||
"loco-szt": "DIESEL M.U.",
|
"loco-szt": "DIESEL M.U.",
|
||||||
"car-passenger": "PASSENGER CARRIAGE",
|
"car-passenger": "PASSENGER CARRIAGE",
|
||||||
"car-cargo": "FREIGHT CAR",
|
"car-cargo": "FREIGHT CARRIAGE",
|
||||||
"cabin": "Cabin type:",
|
"cabin": "Cabin type:",
|
||||||
"construction": "Construction type:"
|
"construction": "Construction type:"
|
||||||
},
|
},
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
"header": {
|
"header": {
|
||||||
"image": "Image",
|
"image": "Image",
|
||||||
"type": "Name",
|
"type": "Name",
|
||||||
"power": "Type",
|
"group": "Type group",
|
||||||
"constructionType": "Construction",
|
"constructionType": "Construction",
|
||||||
"coldStart": "Cold start",
|
"coldStart": "Cold start",
|
||||||
"length": "Length",
|
"length": "Length",
|
||||||
@@ -139,7 +139,9 @@
|
|||||||
"loco-ezt": "EMU",
|
"loco-ezt": "EMU",
|
||||||
"loco-szt": "DMU",
|
"loco-szt": "DMU",
|
||||||
"loco-s": "Diesel locomotive",
|
"loco-s": "Diesel locomotive",
|
||||||
"loco-e": "Electric locomotive"
|
"loco-e": "Electric locomotive",
|
||||||
|
"car-passenger": "Passenger carriage",
|
||||||
|
"car-cargo": "Frieght carriage"
|
||||||
},
|
},
|
||||||
"realstock": {
|
"realstock": {
|
||||||
"title": "POLISH TRAIN COMPOSITIONS by",
|
"title": "POLISH TRAIN COMPOSITIONS by",
|
||||||
|
|||||||
+5
-3
@@ -29,7 +29,7 @@
|
|||||||
"title": "PODGLĄD WYBRANEGO POJAZDU",
|
"title": "PODGLĄD WYBRANEGO POJAZDU",
|
||||||
"loading": "ŁADOWANIE OBRAZU...",
|
"loading": "ŁADOWANIE OBRAZU...",
|
||||||
"desc": "Wybierz pojazd lub wagon, aby zobaczyć jego podgląd powyżej",
|
"desc": "Wybierz pojazd lub wagon, aby zobaczyć jego podgląd powyżej",
|
||||||
"sponsor-only": "* TYLKO DLA SPONSORÓW",
|
"sponsor-only": "* TYLKO DLA SPONSORÓW DO {0}",
|
||||||
"loco-e": "ELEKTROWÓZ",
|
"loco-e": "ELEKTROWÓZ",
|
||||||
"loco-s": "SPALINOWÓZ",
|
"loco-s": "SPALINOWÓZ",
|
||||||
"loco-ezt": "EZT",
|
"loco-ezt": "EZT",
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
"header": {
|
"header": {
|
||||||
"image": "Zdjęcie",
|
"image": "Zdjęcie",
|
||||||
"type": "Nazwa",
|
"type": "Nazwa",
|
||||||
"power": "Rodzaj",
|
"group": "Rodzaj",
|
||||||
"constructionType": "Konstrukcja",
|
"constructionType": "Konstrukcja",
|
||||||
"coldStart": "Zimny start",
|
"coldStart": "Zimny start",
|
||||||
"length": "Długość",
|
"length": "Długość",
|
||||||
@@ -139,7 +139,9 @@
|
|||||||
"loco-ezt": "EZT",
|
"loco-ezt": "EZT",
|
||||||
"loco-szt": "SZT",
|
"loco-szt": "SZT",
|
||||||
"loco-s": "Spalinowóz",
|
"loco-s": "Spalinowóz",
|
||||||
"loco-e": "Elektrowóz"
|
"loco-e": "Elektrowóz",
|
||||||
|
"car-passenger": "Wagon pasażerski",
|
||||||
|
"car-cargo": "Wagon towarowy"
|
||||||
},
|
},
|
||||||
"realstock": {
|
"realstock": {
|
||||||
"title": "ZESTAWIENIA REALNE by",
|
"title": "ZESTAWIENIA REALNE by",
|
||||||
|
|||||||
+6
-7
@@ -1,14 +1,13 @@
|
|||||||
import { createApp } from 'vue';
|
import { createApp } from "vue";
|
||||||
import { createPinia } from 'pinia';
|
import { createPinia } from "pinia";
|
||||||
import { registerSW } from 'virtual:pwa-register';
|
import { registerSW } from "virtual:pwa-register";
|
||||||
|
|
||||||
import App from './App.vue';
|
import App from "./App.vue";
|
||||||
import i18n from './i18n-setup';
|
import i18n from "./i18n-setup";
|
||||||
const pinia = createPinia();
|
const pinia = createPinia();
|
||||||
|
|
||||||
registerSW({
|
registerSW({
|
||||||
immediate: true,
|
immediate: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
createApp(App).use(pinia).use(i18n).mount('#app');
|
createApp(App).use(pinia).use(i18n).mount("#app");
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
methods: {
|
methods: {
|
||||||
getIconURL(name: string, ext = 'svg'): string {
|
getIconURL(name: string, ext = "svg"): string {
|
||||||
return `/images/icon-${name}.${ext}`;
|
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`;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -29,8 +29,9 @@ export default defineComponent({
|
|||||||
count,
|
count,
|
||||||
imgSrc: vehicle.imageSrc,
|
imgSrc: vehicle.imageSrc,
|
||||||
useType: isLoco ? vehicle.power : vehicle.useType,
|
useType: isLoco ? vehicle.power : vehicle.useType,
|
||||||
supportersOnly: vehicle.supportersOnly,
|
isSponsorsOnly: vehicle.isSponsorsOnly,
|
||||||
constructionType: vehicle.constructionType,
|
constructionType: vehicle.constructionType,
|
||||||
|
sponsorsOnlyTimestamp: vehicle.sponsorsOnlyTimestamp,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -91,4 +92,3 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { useStore } from '../store';
|
import { useStore } from '../store';
|
||||||
import { ICarWagon, ILocomotive, IStock, Vehicle } from '../types';
|
import { ICarWagon, ILocomotive, IStock, Vehicle } from '../types';
|
||||||
|
import { isLocomotive } from '../utils/vehicleUtils';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
@@ -9,45 +10,9 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {},
|
||||||
locoOptions() {
|
|
||||||
return this.store.locoDataList
|
|
||||||
.sort((a, b) => (a.type > b.type ? 1 : -1))
|
|
||||||
.filter((loco) => loco.power == this.store.chosenLocoPower);
|
|
||||||
},
|
|
||||||
|
|
||||||
carOptions() {
|
|
||||||
return this.store.carDataList
|
|
||||||
.sort((a, b) => (a.type > b.type ? 1 : -1))
|
|
||||||
.filter((car) => car.useType == this.store.chosenCarUseType);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
selectLocoType(locoTypeId: string) {
|
|
||||||
this.store.chosenLocoPower = locoTypeId;
|
|
||||||
this.store.chosenVehicle = this.locoOptions[0];
|
|
||||||
this.store.chosenLoco = this.locoOptions[0];
|
|
||||||
},
|
|
||||||
|
|
||||||
selectCarWagonType(carWagonTypeId: string) {
|
|
||||||
this.store.chosenCarUseType = carWagonTypeId;
|
|
||||||
this.store.chosenVehicle = this.carOptions[0];
|
|
||||||
this.store.chosenCar = this.carOptions[0];
|
|
||||||
this.store.chosenCargo = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
previewVehicleByType(type: 'loco' | 'car' | 'cargo') {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
if (!this.store.chosenLoco && !this.store.chosenCar) return;
|
|
||||||
|
|
||||||
this.store.chosenVehicle = type == 'loco' ? this.store.chosenLoco : this.store.chosenCar;
|
|
||||||
|
|
||||||
this.store.chosenCargo =
|
|
||||||
this.store.chosenCar?.cargoList.find((cargo) => cargo.id == this.store.chosenCargo?.id) || null;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
previewStock(stock: IStock) {
|
previewStock(stock: IStock) {
|
||||||
if (this.store.chosenVehicle?.imageSrc != stock.imgSrc) this.store.imageLoading = true;
|
if (this.store.chosenVehicle?.imageSrc != stock.imgSrc) this.store.imageLoading = true;
|
||||||
|
|
||||||
@@ -81,6 +46,11 @@ export default defineComponent({
|
|||||||
this.store.chosenCargo = null;
|
this.store.chosenCargo = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
previewVehicle(vehicle: Vehicle) {
|
||||||
|
if (isLocomotive(vehicle)) this.previewLocomotive(vehicle);
|
||||||
|
else this.previewCarWagon(vehicle);
|
||||||
|
},
|
||||||
|
|
||||||
resetPreview() {
|
resetPreview() {
|
||||||
this.store.chosenVehicle = null;
|
this.store.chosenVehicle = null;
|
||||||
this.store.chosenCar = null;
|
this.store.chosenCar = null;
|
||||||
@@ -89,4 +59,3 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from "vue";
|
||||||
import { useStore } from '../store';
|
import { useStore } from "../store";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
@@ -18,7 +18,10 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
|
|
||||||
trainTooHeavy() {
|
trainTooHeavy() {
|
||||||
return this.store.acceptableMass && this.store.totalMass > this.store.acceptableMass;
|
return (
|
||||||
|
this.store.acceptableMass &&
|
||||||
|
this.store.totalMass > this.store.acceptableMass
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
locoNotSuitable() {
|
locoNotSuitable() {
|
||||||
@@ -26,15 +29,19 @@ export default defineComponent({
|
|||||||
!this.store.isTrainPassenger &&
|
!this.store.isTrainPassenger &&
|
||||||
this.store.stockList.length > 1 &&
|
this.store.stockList.length > 1 &&
|
||||||
!this.store.stockList.every((stock) => stock.isLoco) &&
|
!this.store.stockList.every((stock) => stock.isLoco) &&
|
||||||
this.store.stockList.some((stock) => stock.isLoco && stock.type.startsWith('EP'))
|
this.store.stockList.some(
|
||||||
|
(stock) => stock.isLoco && stock.type.startsWith("EP"),
|
||||||
|
)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
tooManyLocomotives() {
|
tooManyLocomotives() {
|
||||||
return this.store.stockList.reduce((acc, stock) => {
|
return (
|
||||||
if (stock.isLoco) acc += stock.count;
|
this.store.stockList.reduce((acc, stock) => {
|
||||||
return acc;
|
if (stock.isLoco) acc += stock.count;
|
||||||
}, 0) > 2;
|
return acc;
|
||||||
|
}, 0) > 2
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
+7
-5
@@ -1,4 +1,4 @@
|
|||||||
import { IStore } from './types';
|
import { IStockData, IStore } from './types';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import {
|
import {
|
||||||
acceptableMass,
|
acceptableMass,
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
totalLength,
|
totalLength,
|
||||||
totalMass,
|
totalMass,
|
||||||
} from './utils/vehicleUtils';
|
} from './utils/vehicleUtils';
|
||||||
|
import http from './http';
|
||||||
|
|
||||||
export const useStore = defineStore({
|
export const useStore = defineStore({
|
||||||
id: 'store',
|
id: 'store',
|
||||||
@@ -46,11 +47,14 @@ export const useStore = defineStore({
|
|||||||
isRealStockListCardOpen: false,
|
isRealStockListCardOpen: false,
|
||||||
|
|
||||||
stockData: undefined,
|
stockData: undefined,
|
||||||
} as IStore),
|
|
||||||
|
lastFocusedElement: null,
|
||||||
|
}) as IStore,
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
locoDataList: (state) => locoDataList(state),
|
locoDataList: (state) => locoDataList(state),
|
||||||
carDataList: (state) => carDataList(state),
|
carDataList: (state) => carDataList(state),
|
||||||
|
vehicleDataList: (state) => [...locoDataList(state), ...carDataList(state)],
|
||||||
totalMass: (state) => totalMass(state),
|
totalMass: (state) => totalMass(state),
|
||||||
totalLength: (state) => totalLength(state),
|
totalLength: (state) => totalLength(state),
|
||||||
maxStockSpeed: (state) => maxStockSpeed(state),
|
maxStockSpeed: (state) => maxStockSpeed(state),
|
||||||
@@ -61,7 +65,7 @@ export const useStore = defineStore({
|
|||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
async fetchStockInfoData() {
|
async fetchStockInfoData() {
|
||||||
const stockData = await (await fetch(`https://spythere.github.io/api/td2/data/stockInfo.json`)).json();
|
const stockData = (await http.get<IStockData>('td2/data/stockInfo.json')).data;
|
||||||
this.stockData = stockData;
|
this.stockData = stockData;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -82,5 +86,3 @@ export const useStore = defineStore({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+43
-6
@@ -1,5 +1,3 @@
|
|||||||
@import url('https://fonts.googleapis.com/css2?family=Lato:wght@400;700;900&display=swap');
|
|
||||||
|
|
||||||
$breakpointMd: 960px;
|
$breakpointMd: 960px;
|
||||||
$breakpointSm: 550px;
|
$breakpointSm: 550px;
|
||||||
|
|
||||||
@@ -8,6 +6,36 @@ $textColor: #fff;
|
|||||||
$secondaryColor: #222;
|
$secondaryColor: #222;
|
||||||
$accentColor: #e4c428;
|
$accentColor: #e4c428;
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Lato';
|
||||||
|
src:
|
||||||
|
url('$fonts/Lato-Light.woff2') format('woff2'),
|
||||||
|
url('$fonts/Lato-Light.woff') format('woff');
|
||||||
|
font-weight: 300;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Lato';
|
||||||
|
src:
|
||||||
|
url('$fonts/Lato-Bold.woff2') format('woff2'),
|
||||||
|
url('$fonts/Lato-Bold.woff') format('woff');
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Lato';
|
||||||
|
src:
|
||||||
|
url('$fonts/Lato-Regular.woff2') format('woff2'),
|
||||||
|
url('$fonts/Lato-Regular.woff') format('woff');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 7px;
|
width: 7px;
|
||||||
height: 7px;
|
height: 7px;
|
||||||
@@ -32,7 +60,7 @@ html {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
font-family: 'Lato', sans-serif;
|
font-family: Lato, sans-serif;
|
||||||
|
|
||||||
background-color: $bgColor;
|
background-color: $bgColor;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
@@ -64,7 +92,7 @@ select,
|
|||||||
option,
|
option,
|
||||||
input,
|
input,
|
||||||
button {
|
button {
|
||||||
font-family: 'Lato', sans-serif;
|
font-family: Lato, sans-serif;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +122,9 @@ button {
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
||||||
transition: all 250ms;
|
transition:
|
||||||
|
color 150ms,
|
||||||
|
background-color 150ms;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $accentColor;
|
color: $accentColor;
|
||||||
@@ -111,6 +141,13 @@ button {
|
|||||||
outline: 1px solid white;
|
outline: 1px solid white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&[data-chosen='true'] {
|
||||||
|
background-color: $accentColor;
|
||||||
|
color: black;
|
||||||
|
|
||||||
|
box-shadow: 0 0 5px 1px $accentColor;
|
||||||
|
}
|
||||||
|
|
||||||
&[data-disabled='true'] {
|
&[data-disabled='true'] {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
@@ -148,7 +185,7 @@ button {
|
|||||||
select,
|
select,
|
||||||
input[type='text'],
|
input[type='text'],
|
||||||
input[type='number'] {
|
input[type='number'] {
|
||||||
background: none;
|
background: $bgColor;
|
||||||
border: 2px solid #aaa;
|
border: 2px solid #aaa;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -1,4 +1,4 @@
|
|||||||
@import './global.scss';
|
@import "./global.scss";
|
||||||
|
|
||||||
.tab {
|
.tab {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -77,4 +77,3 @@ hr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+20
-14
@@ -30,9 +30,12 @@ export interface IStore {
|
|||||||
|
|
||||||
stockSectionMode: 'stock-list' | 'stock-generator' | 'number-generator' | 'wiki-list';
|
stockSectionMode: 'stock-list' | 'stock-generator' | 'number-generator' | 'wiki-list';
|
||||||
stockData?: IStockData;
|
stockData?: IStockData;
|
||||||
|
|
||||||
|
lastFocusedElement: HTMLElement | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TStockInfoKey = 'loco-e' | 'loco-s' | 'loco-ezt' | 'loco-szt' | 'car-passenger' | 'car-cargo';
|
export type TLocoGroup = 'loco-e' | 'loco-s' | 'loco-ezt' | 'loco-szt';
|
||||||
|
export type TCarWagonGroup = 'car-passenger' | 'car-cargo';
|
||||||
|
|
||||||
export interface IStockProps {
|
export interface IStockProps {
|
||||||
type: string;
|
type: string;
|
||||||
@@ -52,12 +55,12 @@ export interface IStockData {
|
|||||||
};
|
};
|
||||||
|
|
||||||
info: {
|
info: {
|
||||||
'car-cargo': [string, string, boolean, boolean, string][];
|
'car-cargo': [string, string, boolean, number | null, string][];
|
||||||
'car-passenger': [string, string, boolean, boolean, string][];
|
'car-passenger': [string, string, boolean, number | null, string][];
|
||||||
'loco-e': [string, string, string, string, boolean][];
|
'loco-e': [string, string, string, string, number | null][];
|
||||||
'loco-s': [string, string, string, string, boolean][];
|
'loco-s': [string, string, string, string, number | null][];
|
||||||
'loco-szt': [string, string, string, string, boolean][];
|
'loco-szt': [string, string, string, string, number | null][];
|
||||||
'loco-ezt': [string, string, string, string, boolean][];
|
'loco-ezt': [string, string, string, string, number | null][];
|
||||||
};
|
};
|
||||||
|
|
||||||
props: IStockProps[];
|
props: IStockProps[];
|
||||||
@@ -67,11 +70,13 @@ export interface IStockData {
|
|||||||
|
|
||||||
export interface ILocomotive {
|
export interface ILocomotive {
|
||||||
type: string;
|
type: string;
|
||||||
power: string;
|
power: TLocoGroup;
|
||||||
|
group: TLocoGroup;
|
||||||
constructionType: string;
|
constructionType: string;
|
||||||
cabinType: string;
|
cabinType: string;
|
||||||
maxSpeed: number;
|
maxSpeed: number;
|
||||||
supportersOnly: boolean;
|
isSponsorsOnly: boolean;
|
||||||
|
sponsorsOnlyTimestamp: number;
|
||||||
imageSrc: string;
|
imageSrc: string;
|
||||||
|
|
||||||
mass: number;
|
mass: number;
|
||||||
@@ -79,12 +84,13 @@ export interface ILocomotive {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ICarWagon {
|
export interface ICarWagon {
|
||||||
//"203V_PKPC_Fll_01","203V",true,false,"100",img
|
|
||||||
type: string;
|
type: string;
|
||||||
useType: 'car-passenger' | 'car-cargo';
|
useType: TCarWagonGroup;
|
||||||
|
group: TCarWagonGroup;
|
||||||
constructionType: string;
|
constructionType: string;
|
||||||
loadable: boolean;
|
loadable: boolean;
|
||||||
supportersOnly: boolean;
|
isSponsorsOnly: boolean;
|
||||||
|
sponsorsOnlyTimestamp: number;
|
||||||
maxSpeed: number;
|
maxSpeed: number;
|
||||||
imageSrc: string;
|
imageSrc: string;
|
||||||
|
|
||||||
@@ -108,7 +114,8 @@ export interface IStock {
|
|||||||
maxSpeed: number;
|
maxSpeed: number;
|
||||||
cargo?: { id: string; totalMass: number };
|
cargo?: { id: string; totalMass: number };
|
||||||
isLoco: boolean;
|
isLoco: boolean;
|
||||||
supportersOnly: boolean;
|
isSponsorsOnly: boolean;
|
||||||
|
sponsorsOnlyTimestamp: number;
|
||||||
count: number;
|
count: number;
|
||||||
imgSrc?: string;
|
imgSrc?: string;
|
||||||
}
|
}
|
||||||
@@ -120,4 +127,3 @@ export interface IReadyStockItem {
|
|||||||
number: string;
|
number: string;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
const supportedConstructions = ['303e', '203e'];
|
const supportedConstructions = ["303e", "203e"];
|
||||||
|
|
||||||
export function locoSupportsColdStart(constructionType: string) {
|
export function locoSupportsColdStart(constructionType: string) {
|
||||||
return new RegExp(`(${supportedConstructions.join('|')})`).test(constructionType);
|
return new RegExp(`(${supportedConstructions.join("|")})`).test(
|
||||||
|
constructionType,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
import speedLimitTable from '../constants/speedLimits.json';
|
import speedLimitTable from "../constants/speedLimits.json";
|
||||||
export type LocoType = keyof typeof speedLimitTable;
|
export type LocoType = keyof typeof speedLimitTable;
|
||||||
|
|
||||||
export const calculateSpeedLimit = (locoType: LocoType, stockMass: number, isTrainPassenger: boolean) => {
|
export const calculateSpeedLimit = (
|
||||||
const speedTable = speedLimitTable[locoType][isTrainPassenger ? 'passenger' : 'cargo'];
|
locoType: LocoType,
|
||||||
|
stockMass: number,
|
||||||
|
isTrainPassenger: boolean,
|
||||||
|
) => {
|
||||||
|
const speedTable =
|
||||||
|
speedLimitTable[locoType][isTrainPassenger ? "passenger" : "cargo"];
|
||||||
|
|
||||||
if (!speedTable) return undefined;
|
if (!speedTable) return undefined;
|
||||||
|
|
||||||
let speedLimit = 0;
|
let speedLimit = 0;
|
||||||
for (let mass in speedTable) if (stockMass > Number(mass)) speedLimit = (speedTable as any)[mass];
|
for (const mass in speedTable)
|
||||||
|
if (stockMass > Number(mass)) speedLimit = (speedTable as any)[mass];
|
||||||
|
|
||||||
return speedLimit;
|
return speedLimit;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+23
-26
@@ -1,5 +1,5 @@
|
|||||||
import { EVehicleUseType } from '../enums/EVehicleUseType';
|
import { EVehicleUseType } from '../enums/EVehicleUseType';
|
||||||
import { ICarWagon, ILocomotive, IStore, TStockInfoKey } from '../types';
|
import { ICarWagon, ILocomotive, IStore, TCarWagonGroup, TLocoGroup } from '../types';
|
||||||
import { LocoType, calculateSpeedLimit } from './speedLimitUtils';
|
import { LocoType, calculateSpeedLimit } from './speedLimitUtils';
|
||||||
|
|
||||||
export function isLocomotive(vehicle: ILocomotive | ICarWagon): vehicle is ILocomotive {
|
export function isLocomotive(vehicle: ILocomotive | ICarWagon): vehicle is ILocomotive {
|
||||||
@@ -14,21 +14,23 @@ export function locoDataList(state: IStore) {
|
|||||||
return Object.keys(stockData.info).reduce((acc, vehiclePower) => {
|
return Object.keys(stockData.info).reduce((acc, vehiclePower) => {
|
||||||
if (!vehiclePower.startsWith('loco')) return acc;
|
if (!vehiclePower.startsWith('loco')) return acc;
|
||||||
|
|
||||||
const locoVehiclesData = stockData.info[vehiclePower as 'loco-e' | 'loco-s' | 'loco-ezt' | 'loco-szt'];
|
const locoVehiclesData = stockData.info[vehiclePower as TLocoGroup];
|
||||||
|
|
||||||
locoVehiclesData.forEach((loco) => {
|
locoVehiclesData.forEach((loco) => {
|
||||||
if (state.showSupporter && !loco[4]) return;
|
if (state.showSupporter && !loco[4]) return;
|
||||||
|
|
||||||
const [type, constructionType, cabinType, maxSpeed, supportersOnly] = loco;
|
const [type, constructionType, cabinType, maxSpeed, sponsorsTimestamp] = loco;
|
||||||
const locoProps = stockData.props.find((prop) => constructionType == prop.type);
|
const locoProps = stockData.props.find((prop) => constructionType == prop.type);
|
||||||
|
|
||||||
acc.push({
|
acc.push({
|
||||||
power: vehiclePower,
|
power: vehiclePower as TLocoGroup,
|
||||||
|
group: vehiclePower as TLocoGroup,
|
||||||
type,
|
type,
|
||||||
constructionType,
|
constructionType,
|
||||||
cabinType,
|
cabinType,
|
||||||
maxSpeed: Number(maxSpeed),
|
maxSpeed: Number(maxSpeed),
|
||||||
supportersOnly,
|
isSponsorsOnly: Number(sponsorsTimestamp) > Date.now(),
|
||||||
|
sponsorsOnlyTimestamp: Number(sponsorsTimestamp),
|
||||||
imageSrc: '',
|
imageSrc: '',
|
||||||
|
|
||||||
length: locoProps?.length && type.startsWith('2EN') ? locoProps.length * 2 : locoProps?.length || 0,
|
length: locoProps?.length && type.startsWith('2EN') ? locoProps.length * 2 : locoProps?.length || 0,
|
||||||
@@ -48,20 +50,24 @@ export function carDataList(state: IStore) {
|
|||||||
return Object.keys(stockData.info).reduce((acc, vehicleUseType) => {
|
return Object.keys(stockData.info).reduce((acc, vehicleUseType) => {
|
||||||
if (!vehicleUseType.startsWith('car')) return acc;
|
if (!vehicleUseType.startsWith('car')) return acc;
|
||||||
|
|
||||||
const carVehiclesData = stockData.info[vehicleUseType as 'car-passenger' | 'car-cargo'];
|
const carVehiclesData = stockData.info[vehicleUseType as TCarWagonGroup];
|
||||||
|
|
||||||
carVehiclesData.forEach((car) => {
|
carVehiclesData.forEach((car) => {
|
||||||
if (state.showSupporter && !car[3]) return;
|
const [type, constructionType, loadable, sponsorsOnlyTimestamp, maxSpeed] = car;
|
||||||
|
|
||||||
const carPropsData = stockData.props.find((v) => car[0].toString().startsWith(v.type));
|
if (state.showSupporter && Number(sponsorsOnlyTimestamp) <= Date.now()) return;
|
||||||
|
|
||||||
|
const carPropsData = stockData.props.find((v) => type.toString().startsWith(v.type));
|
||||||
|
|
||||||
acc.push({
|
acc.push({
|
||||||
useType: vehicleUseType as 'car-passenger' | 'car-cargo',
|
useType: vehicleUseType as TCarWagonGroup,
|
||||||
type: car[0],
|
group: vehicleUseType as TCarWagonGroup,
|
||||||
constructionType: car[1],
|
type,
|
||||||
loadable: car[2],
|
constructionType,
|
||||||
supportersOnly: car[3],
|
loadable,
|
||||||
maxSpeed: Number(car[4]),
|
isSponsorsOnly: Number(sponsorsOnlyTimestamp) > Date.now(),
|
||||||
|
sponsorsOnlyTimestamp: Number(sponsorsOnlyTimestamp),
|
||||||
|
maxSpeed: Number(maxSpeed),
|
||||||
imageSrc: '',
|
imageSrc: '',
|
||||||
cargoList:
|
cargoList:
|
||||||
!carPropsData || carPropsData.cargo === null
|
!carPropsData || carPropsData.cargo === null
|
||||||
@@ -81,10 +87,7 @@ export function carDataList(state: IStore) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function totalMass(state: IStore) {
|
export function totalMass(state: IStore) {
|
||||||
return ~~state.stockList.reduce(
|
return ~~state.stockList.reduce((acc, stock) => acc + (stock.cargo ? stock.cargo.totalMass : stock.mass) * stock.count, 0);
|
||||||
(acc, stock) => acc + (stock.cargo ? stock.cargo.totalMass : stock.mass) * stock.count,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function totalLength(state: IStore) {
|
export function totalLength(state: IStore) {
|
||||||
@@ -92,10 +95,7 @@ export function totalLength(state: IStore) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function maxStockSpeed(state: IStore) {
|
export function maxStockSpeed(state: IStore) {
|
||||||
const stockSpeedLimit = state.stockList.reduce(
|
const stockSpeedLimit = state.stockList.reduce((acc, stock) => (stock.maxSpeed < acc || acc == 0 ? stock.maxSpeed : acc), 0);
|
||||||
(acc, stock) => (stock.maxSpeed < acc || acc == 0 ? stock.maxSpeed : acc),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
const headingLoco = state.stockList[0]?.isLoco ? state.stockList[0] : undefined;
|
const headingLoco = state.stockList[0]?.isLoco ? state.stockList[0] : undefined;
|
||||||
|
|
||||||
if (!headingLoco) return stockSpeedLimit;
|
if (!headingLoco) return stockSpeedLimit;
|
||||||
@@ -139,9 +139,7 @@ export function isTrainPassenger(state: IStore) {
|
|||||||
if (state.stockList.length == 0) return false;
|
if (state.stockList.length == 0) return false;
|
||||||
if (state.stockList.every((stock) => stock.isLoco)) return false;
|
if (state.stockList.every((stock) => stock.isLoco)) return false;
|
||||||
|
|
||||||
return state.stockList
|
return state.stockList.filter((stock) => !stock.isLoco).every((stock) => stock.useType === EVehicleUseType.CAR_PASSENGER);
|
||||||
.filter((stock) => !stock.isLoco)
|
|
||||||
.every((stock) => stock.useType === EVehicleUseType.CAR_PASSENGER);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function chosenRealStock(state: IStore) {
|
export function chosenRealStock(state: IStore) {
|
||||||
@@ -158,4 +156,3 @@ export function chosenRealStock(state: IStore) {
|
|||||||
|
|
||||||
return realStockObj;
|
return realStockObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
<MainContainer />
|
<MainContainer />
|
||||||
<Footer />
|
<FooterVue />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from "vue";
|
||||||
import { useStore } from '../store';
|
import { useStore } from "../store";
|
||||||
|
|
||||||
import MainContainer from '../components/app/MainContainer.vue';
|
import MainContainer from "../components/app/MainContainer.vue";
|
||||||
import Footer from '../components/app/Footer.vue';
|
import FooterVue from "../components/app/Footer.vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
MainContainer,
|
MainContainer,
|
||||||
Footer,
|
FooterVue,
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
|
|||||||
Vendored
+4
-4
@@ -1,7 +1,7 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
declare module '*.vue' {
|
declare module "*.vue" {
|
||||||
import type { DefineComponent } from 'vue'
|
import type { DefineComponent } from "vue";
|
||||||
const component: DefineComponent<{}, {}, any>
|
const component: DefineComponent<{}, {}, any>;
|
||||||
export default component
|
export default component;
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-5
@@ -2,19 +2,26 @@ import { defineConfig } from 'vite';
|
|||||||
import vue from '@vitejs/plugin-vue';
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
|
||||||
import { VitePWA } from 'vite-plugin-pwa';
|
import { VitePWA } from 'vite-plugin-pwa';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
server: {
|
server: {
|
||||||
port: 2137,
|
port: 2137,
|
||||||
},
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
$fonts: resolve('/fonts'),
|
||||||
|
$images: resolve('/images'),
|
||||||
|
},
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
VitePWA({
|
VitePWA({
|
||||||
registerType: 'autoUpdate',
|
registerType: 'autoUpdate',
|
||||||
|
|
||||||
workbox: {
|
workbox: {
|
||||||
// globPatterns: ['**/*.{js,css,html,png,svg,img}'],
|
globPatterns: ['**/*.{js,css,html,png,svg,img,woff,woff2}'],
|
||||||
|
|
||||||
runtimeCaching: [
|
runtimeCaching: [
|
||||||
{
|
{
|
||||||
@@ -24,10 +31,23 @@ export default defineConfig({
|
|||||||
cacheName: 'swdr-images-cache',
|
cacheName: 'swdr-images-cache',
|
||||||
expiration: {
|
expiration: {
|
||||||
maxEntries: 50,
|
maxEntries: 50,
|
||||||
maxAgeSeconds: 60 * 60 * 24 * 7, // <== 7 days
|
maxAgeSeconds: 60 * 60 * 24, // <== 1 day
|
||||||
},
|
},
|
||||||
cacheableResponse: {
|
cacheableResponse: {
|
||||||
statuses: [404],
|
statuses: [0, 200, 404],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urlPattern: /^https:\/\/spythere.github.io\/api\/td2\/.*/i,
|
||||||
|
handler: 'CacheFirst',
|
||||||
|
options: {
|
||||||
|
cacheName: 'spythere-api-cache',
|
||||||
|
expiration: {
|
||||||
|
maxAgeSeconds: 60 * 60 * 24, // <== 1 day
|
||||||
|
},
|
||||||
|
cacheableResponse: {
|
||||||
|
statuses: [0, 200],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -36,5 +56,3 @@ export default defineConfig({
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user