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",
|
||||
},
|
||||
};
|
||||
+4
-1
@@ -25,4 +25,7 @@ pnpm-debug.log*
|
||||
node_modules
|
||||
|
||||
# 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="manifest" href="/site.webmanifest" />
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
|
||||
|
||||
<meta name="msapplication-TileColor" content="#da532c" />
|
||||
<meta name="theme-color" content="#e4c428" />
|
||||
</head>
|
||||
|
||||
Generated
+7141
-218
File diff suppressed because it is too large
Load Diff
+12
-2
@@ -1,20 +1,30 @@
|
||||
{
|
||||
"name": "pojazdownik",
|
||||
"version": "1.6.0",
|
||||
"version": "1.7.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"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": {
|
||||
"axios": "^1.4.0",
|
||||
"pinia": "^2.0.17",
|
||||
"prettier": "^3.0.3",
|
||||
"vue": "^3.2.37",
|
||||
"vue-i18n": "9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rushstack/eslint-patch": "^1.3.3",
|
||||
"@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",
|
||||
"typescript": "^5.0.2",
|
||||
"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() {
|
||||
/* 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.handleRouting();
|
||||
},
|
||||
@@ -59,11 +54,4 @@ h2 {
|
||||
|
||||
color: #d1d1d1;
|
||||
}
|
||||
|
||||
@media screen and (max-width: $breakpointMd) {
|
||||
#app {
|
||||
font-size: calc(0.7rem + 0.75vw);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
<template>
|
||||
<div>
|
||||
<keep-alive>
|
||||
<RealStockCard v-if="store.isRealStockListCardOpen" />
|
||||
</keep-alive>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useStore } from '../../store';
|
||||
import RealStockCard from '../cards/RealStockCard.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { RealStockCard },
|
||||
data() {
|
||||
return {
|
||||
store: useStore(),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
<template>
|
||||
<div>
|
||||
<keep-alive>
|
||||
<RealStockCard v-if="store.isRealStockListCardOpen" />
|
||||
</keep-alive>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { useStore } from "../../store";
|
||||
import RealStockCard from "../cards/RealStockCard.vue";
|
||||
|
||||
export default defineComponent({
|
||||
components: { RealStockCard },
|
||||
data() {
|
||||
return {
|
||||
store: useStore(),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -1,49 +1,52 @@
|
||||
<template>
|
||||
<footer>
|
||||
<i18n-t keypath="footer.disclaimer" tag="div" class="text--grayed">
|
||||
<template #tos>
|
||||
<a style="color: #ccc" :href="$t('footer.tos-href')" target="_blank">
|
||||
{{ $t('footer.tos') }}
|
||||
</a>
|
||||
</template>
|
||||
</i18n-t>
|
||||
|
||||
<div class="text--grayed" v-if="store.stockData">
|
||||
{{ $t('footer.version-check', { version: store.stockData.version }) }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
©
|
||||
<a href="https://td2.info.pl/profile/?u=20777" target="_blank">Spythere</a>
|
||||
{{ new Date().getUTCFullYear() }} | v{{ VERSION }}{{ !isOnProductionHost ? 'dev' : '' }}
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import packageInfo from '../../../package.json';
|
||||
import { useStore } from '../../store';
|
||||
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
isOnProductionHost: location.hostname == 'pojazdownik-td2.web.app',
|
||||
VERSION: packageInfo.version,
|
||||
store: useStore(),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25em;
|
||||
|
||||
text-align: center;
|
||||
padding: 1em 1em 0 1em;
|
||||
margin-top: auto;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<footer>
|
||||
<i18n-t keypath="footer.disclaimer" tag="div" class="text--grayed">
|
||||
<template #tos>
|
||||
<a style="color: #ccc" :href="$t('footer.tos-href')" target="_blank">
|
||||
{{ $t("footer.tos") }}
|
||||
</a>
|
||||
</template>
|
||||
</i18n-t>
|
||||
|
||||
<div class="text--grayed" v-if="store.stockData">
|
||||
{{ $t("footer.version-check", { version: store.stockData.version }) }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
©
|
||||
<a href="https://td2.info.pl/profile/?u=20777" target="_blank"
|
||||
>Spythere</a
|
||||
>
|
||||
{{ new Date().getUTCFullYear() }} | v{{ VERSION
|
||||
}}{{ !isOnProductionHost ? "dev" : "" }}
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import packageInfo from "../../../package.json";
|
||||
import { useStore } from "../../store";
|
||||
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
isOnProductionHost: location.hostname == "pojazdownik-td2.web.app",
|
||||
VERSION: packageInfo.version,
|
||||
store: useStore(),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25em;
|
||||
|
||||
text-align: center;
|
||||
padding: 1em 1em 0 1em;
|
||||
margin-top: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
<template>
|
||||
<main>
|
||||
<LogoSection />
|
||||
<InputsSection />
|
||||
<TrainImageSection />
|
||||
<StockSection />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import LogoSection from '../sections/LogoSection.vue';
|
||||
import InputsSection from '../sections/InputsSection.vue';
|
||||
import TrainImageSection from '../sections/TrainImageSection.vue';
|
||||
import StockSection from '../sections/StockSection.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { LogoSection, InputsSection, TrainImageSection, StockSection },
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/global.scss';
|
||||
|
||||
main {
|
||||
display: grid;
|
||||
gap: 1em;
|
||||
|
||||
width: 100%;
|
||||
max-width: 1500px;
|
||||
min-height: 75vh;
|
||||
|
||||
grid-template-columns: 1fr 2fr;
|
||||
grid-template-rows: auto 360px minmax(400px, 1fr);
|
||||
}
|
||||
|
||||
@media screen and (max-width: $breakpointMd) {
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<main>
|
||||
<LogoSection />
|
||||
<InputsSection />
|
||||
<TrainImageSection />
|
||||
<StockSection />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import LogoSection from "../sections/LogoSection.vue";
|
||||
import InputsSection from "../sections/InputsSection.vue";
|
||||
import TrainImageSection from "../sections/TrainImageSection.vue";
|
||||
import StockSection from "../sections/StockSection.vue";
|
||||
|
||||
export default defineComponent({
|
||||
components: { LogoSection, InputsSection, TrainImageSection, StockSection },
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../styles/global.scss";
|
||||
|
||||
main {
|
||||
display: grid;
|
||||
gap: 1em;
|
||||
|
||||
width: 100%;
|
||||
max-width: 1300px;
|
||||
min-height: 75vh;
|
||||
|
||||
grid-template-columns: 1fr 2fr;
|
||||
grid-template-rows: auto 360px minmax(400px, 1fr);
|
||||
}
|
||||
|
||||
@media screen and (max-width: $breakpointMd) {
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
<template>
|
||||
<div class="real-stock-card g-card" @keydown.esc="store.isRealStockListCardOpen = false">
|
||||
<div
|
||||
class="real-stock-card g-card"
|
||||
@keydown.esc="store.isRealStockListCardOpen = false"
|
||||
>
|
||||
<div class="g-card_bg" @click="store.isRealStockListCardOpen = false"></div>
|
||||
|
||||
<div class="card_content">
|
||||
<div class="card_nav">
|
||||
<div class="top-pane">
|
||||
<h1>
|
||||
{{ $t('realstock.title') }} <a href="https://td2.info.pl/profile/?u=17708" target="_blank">Railtrains997</a>
|
||||
{{ $t("realstock.title") }}
|
||||
<a href="https://td2.info.pl/profile/?u=17708" target="_blank"
|
||||
>Railtrains997</a
|
||||
>
|
||||
</h1>
|
||||
<button class="btn exit-btn" @click="store.isRealStockListCardOpen = false">⨯</button>
|
||||
<button
|
||||
class="btn exit-btn"
|
||||
@click="store.isRealStockListCardOpen = false"
|
||||
>
|
||||
⨯
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="filters" ref="focus" tabindex="0">
|
||||
@@ -19,7 +30,11 @@
|
||||
/>
|
||||
|
||||
<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 }}
|
||||
</option>
|
||||
</datalist>
|
||||
@@ -31,12 +46,18 @@
|
||||
/>
|
||||
|
||||
<datalist id="readyStockStringList">
|
||||
<option v-for="stock in computedAvailableStockTypes" :value="stock">
|
||||
{{ stock }}
|
||||
<option
|
||||
v-for="stockType in computedAvailableStockTypes"
|
||||
:value="stockType"
|
||||
:key="stockType"
|
||||
>
|
||||
{{ stockType }}
|
||||
</option>
|
||||
</datalist>
|
||||
|
||||
<button class="btn" @click="resetStockFilters">{{ $t('realstock.action-reset') }}</button>
|
||||
<button class="btn" @click="resetStockFilters">
|
||||
{{ $t("realstock.action-reset") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -46,14 +67,29 @@
|
||||
:key="rStock.stockId"
|
||||
:data-last-selected="store.chosenRealStockName === rStock.stockId"
|
||||
>
|
||||
<div class="stock-title" tabindex="0" @click="chooseStock(rStock)" @keydown.enter="chooseStock(rStock)">
|
||||
<img class="stock-icon" :src="getIconURL(rStock.type)" :alt="rStock.type" />
|
||||
<b class="text--accent" style="margin-left: 5px"> {{ rStock.name }}</b>
|
||||
<div
|
||||
class="stock-title"
|
||||
tabindex="0"
|
||||
@click="chooseStock(rStock)"
|
||||
@keydown.enter="chooseStock(rStock)"
|
||||
>
|
||||
<img
|
||||
class="stock-icon"
|
||||
:src="getIconURL(rStock.type)"
|
||||
:alt="rStock.type"
|
||||
/>
|
||||
<b class="text--accent" style="margin-left: 5px">
|
||||
{{ rStock.name }}</b
|
||||
>
|
||||
<div>{{ rStock.number }}</div>
|
||||
</div>
|
||||
|
||||
<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>{{ stockType }}</div>
|
||||
<img
|
||||
@@ -61,7 +97,7 @@
|
||||
:title="stockType"
|
||||
style="opacity: 0"
|
||||
@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>
|
||||
@@ -75,23 +111,24 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import { useStore } from '../../store';
|
||||
import imageMixin from '../../mixins/imageMixin';
|
||||
import stockMixin from '../../mixins/stockMixin';
|
||||
import { useStore } from "../../store";
|
||||
import imageMixin from "../../mixins/imageMixin";
|
||||
import stockMixin from "../../mixins/stockMixin";
|
||||
|
||||
import { IReadyStockItem } from '../../types';
|
||||
import { IReadyStockItem } from "../../types";
|
||||
import http from "../../http";
|
||||
|
||||
interface ResponseJSONData {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
function getVehicleType(stockType: string) {
|
||||
if (/^E/.test(stockType)) return 'loco-e';
|
||||
if (/^S/.test(stockType)) return 'loco-s';
|
||||
if (/^E/.test(stockType)) return "loco-e";
|
||||
if (/^S/.test(stockType)) return "loco-s";
|
||||
|
||||
return 'car-passenger';
|
||||
return "car-passenger";
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
@@ -99,11 +136,15 @@ export default defineComponent({
|
||||
|
||||
data: () => ({
|
||||
store: useStore(),
|
||||
responseStatus: 'loading',
|
||||
isMobile: 'ontouchstart' in document.documentElement && navigator.userAgent.match(/Mobi/) ? true : false,
|
||||
responseStatus: "loading",
|
||||
isMobile:
|
||||
"ontouchstart" in document.documentElement &&
|
||||
navigator.userAgent.match(/Mobi/)
|
||||
? true
|
||||
: false,
|
||||
observer: null as IntersectionObserver | null,
|
||||
searchedReadyStockName: '',
|
||||
searchedReadyStockString: '',
|
||||
searchedReadyStockName: "",
|
||||
searchedReadyStockString: "",
|
||||
visibleIndexesTo: 0,
|
||||
lastSelectedStockId: null as string | null,
|
||||
scrollTop: 0,
|
||||
@@ -115,11 +156,11 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
activated() {
|
||||
(this.$refs['focus'] as HTMLElement).focus();
|
||||
(this.$refs["focus"] as HTMLElement).focus();
|
||||
|
||||
(this.$refs['list'] as HTMLElement).scrollTo({
|
||||
(this.$refs["list"] as HTMLElement).scrollTo({
|
||||
top: this.scrollTop,
|
||||
behavior: 'auto',
|
||||
behavior: "auto",
|
||||
});
|
||||
},
|
||||
|
||||
@@ -130,8 +171,12 @@ export default defineComponent({
|
||||
return this.store.readyStockList
|
||||
.filter(
|
||||
(rs) =>
|
||||
rs.stockId.toLocaleLowerCase().includes(this.searchedReadyStockName.toLocaleLowerCase()) &&
|
||||
rs.stockString.toLocaleLowerCase().includes(this.searchedReadyStockString.toLocaleLowerCase())
|
||||
rs.stockId
|
||||
.toLocaleLowerCase()
|
||||
.includes(this.searchedReadyStockName.toLocaleLowerCase()) &&
|
||||
rs.stockString
|
||||
.toLocaleLowerCase()
|
||||
.includes(this.searchedReadyStockString.toLocaleLowerCase()),
|
||||
)
|
||||
.filter((_, i) => i <= this.visibleIndexesTo);
|
||||
},
|
||||
@@ -139,7 +184,7 @@ export default defineComponent({
|
||||
computedAvailableStockTypes() {
|
||||
return this.store.readyStockList
|
||||
.reduce((acc, rs) => {
|
||||
rs.stockString.split(';').forEach((s) => {
|
||||
rs.stockString.split(";").forEach((s) => {
|
||||
if (!acc.includes(s)) acc.push(s);
|
||||
});
|
||||
|
||||
@@ -153,7 +198,7 @@ export default defineComponent({
|
||||
computedReadyStockList(curr, prev) {
|
||||
if (curr.length < prev.length) {
|
||||
this.visibleIndexesTo = 20;
|
||||
(this.$refs['list'] as HTMLElement).scrollTo({
|
||||
(this.$refs["list"] as HTMLElement).scrollTo({
|
||||
top: 0,
|
||||
});
|
||||
}
|
||||
@@ -162,21 +207,21 @@ export default defineComponent({
|
||||
|
||||
methods: {
|
||||
async fetchStockListData() {
|
||||
const readyStockJSONData: ResponseJSONData = await (
|
||||
await fetch(`https://spythere.github.io/api/td2/data/readyStock.json?t=${Math.floor(Date.now() / 60000)}`)
|
||||
).json();
|
||||
const readyStockJSONData = (
|
||||
await http.get<ResponseJSONData>("td2/data/readyStock.json")
|
||||
).data;
|
||||
|
||||
if (!readyStockJSONData) {
|
||||
this.responseStatus = 'error';
|
||||
this.responseStatus = "error";
|
||||
return;
|
||||
}
|
||||
|
||||
for (let stockKey in readyStockJSONData) {
|
||||
const [type, number, ...name] = stockKey.split(' ');
|
||||
const [type, number, ...name] = stockKey.split(" ");
|
||||
|
||||
const obj = {
|
||||
number: number.replace(/_/g, '/'),
|
||||
name: name.join(' '),
|
||||
number: number.replace(/_/g, "/"),
|
||||
name: name.join(" "),
|
||||
stockString: readyStockJSONData[stockKey],
|
||||
type,
|
||||
};
|
||||
@@ -187,7 +232,7 @@ export default defineComponent({
|
||||
});
|
||||
}
|
||||
|
||||
this.responseStatus = 'loaded';
|
||||
this.responseStatus = "loaded";
|
||||
},
|
||||
|
||||
mountObserver() {
|
||||
@@ -195,7 +240,7 @@ export default defineComponent({
|
||||
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) {
|
||||
@@ -203,8 +248,8 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
resetStockFilters() {
|
||||
this.searchedReadyStockName = '';
|
||||
this.searchedReadyStockString = '';
|
||||
this.searchedReadyStockName = "";
|
||||
this.searchedReadyStockString = "";
|
||||
},
|
||||
|
||||
chooseStock(stockItem: IReadyStockItem) {
|
||||
@@ -216,7 +261,7 @@ export default defineComponent({
|
||||
onStockItemError(e: Event, stockType: string) {
|
||||
const imageEl = e.target as HTMLImageElement;
|
||||
imageEl.src = `images/${getVehicleType(stockType)}-unknown.png`;
|
||||
imageEl.style.opacity = '1';
|
||||
imageEl.style.opacity = "1";
|
||||
},
|
||||
|
||||
onListScroll(e: Event) {
|
||||
@@ -230,7 +275,7 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/global.scss';
|
||||
@import "../../styles/global.scss";
|
||||
|
||||
.exit-btn {
|
||||
font-size: 1.2em;
|
||||
@@ -316,7 +361,7 @@ ul {
|
||||
gap: 1rem;
|
||||
padding: 0.1em;
|
||||
|
||||
&[data-last-selected='true'] .stock-title {
|
||||
&[data-last-selected="true"] .stock-title {
|
||||
border: 1px solid $accentColor;
|
||||
}
|
||||
|
||||
@@ -378,4 +423,3 @@ ul {
|
||||
padding: 1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<div class="vehicle-types locos">
|
||||
<button
|
||||
v-for="locoType in locomotiveTypeList"
|
||||
:key="locoType.id"
|
||||
class="btn btn--choice"
|
||||
:data-selected="locoType.id == store.chosenLocoPower"
|
||||
@click="selectLocoType(locoType.id)"
|
||||
@@ -23,10 +24,10 @@
|
||||
@keydown.enter.prevent="addOrSwitchVehicle"
|
||||
@keydown.backspace="removeVehicle"
|
||||
>
|
||||
<option :value="null" disabled>{{ $t('inputs.input-vehicle') }}</option>
|
||||
<option v-for="loco in locoOptions" :value="loco" :key="loco.type">
|
||||
{{ loco.type }}<b v-if="loco.supportersOnly">*</b>
|
||||
<option :value="null" disabled>
|
||||
{{ $t('inputs.input-vehicle') }}
|
||||
</option>
|
||||
<option v-for="loco in locoOptions" :value="loco" :key="loco.type">{{ loco.type }}<b v-if="loco.isSponsorsOnly">*</b></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -34,6 +35,7 @@
|
||||
<div class="vehicle-types carwagons">
|
||||
<button
|
||||
v-for="carType in carTypeList"
|
||||
:key="carType.id"
|
||||
class="btn btn--choice"
|
||||
:data-selected="carType.id == store.chosenCarUseType"
|
||||
@click="selectCarWagonType(carType.id)"
|
||||
@@ -50,11 +52,11 @@
|
||||
@keydown.enter.prevent="addOrSwitchVehicle"
|
||||
@keydown.backspace="removeVehicle"
|
||||
>
|
||||
<option :value="null" disabled>{{ $t('inputs.input-carwagon') }}</option>
|
||||
|
||||
<option v-for="car in carOptions" :value="car" :key="car.type">
|
||||
{{ car.type }}<b v-if="car.supportersOnly">*</b>
|
||||
<option :value="null" disabled>
|
||||
{{ $t('inputs.input-carwagon') }}
|
||||
</option>
|
||||
|
||||
<option v-for="car in carOptions" :value="car" :key="car.type">{{ car.type }}<b v-if="car.isSponsorsOnly">*</b></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -63,9 +65,7 @@
|
||||
<select
|
||||
id="cargo-select"
|
||||
:disabled="
|
||||
(store.chosenCar && !store.chosenCar.loadable) ||
|
||||
(store.chosenCar && store.chosenCar.useType == 'car-passenger') ||
|
||||
!store.chosenCar
|
||||
(store.chosenCar && !store.chosenCar.loadable) || (store.chosenCar && store.chosenCar.useType == 'car-passenger') || !store.chosenCar
|
||||
"
|
||||
data-select="cargo"
|
||||
data-ignore-outside="1"
|
||||
@@ -90,12 +90,7 @@
|
||||
<button class="btn" @click="addVehicle(store.chosenVehicle, store.chosenCargo)">
|
||||
{{ $t('inputs.action-add') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn"
|
||||
@click="switchVehicles"
|
||||
:disabled="store.chosenStockListIndex == -1"
|
||||
:data-disabled="store.chosenStockListIndex == -1"
|
||||
>
|
||||
<button class="btn" @click="switchVehicles" :disabled="store.chosenStockListIndex == -1" :data-disabled="store.chosenStockListIndex == -1">
|
||||
{{ $t('inputs.action-swap') }}
|
||||
<b class="text--accent">
|
||||
{{ store.chosenStockListIndex == -1 ? '' : `${store.chosenStockListIndex + 1}.` }}
|
||||
@@ -122,6 +117,7 @@ export default defineComponent({
|
||||
mixins: [imageMixin, stockPreviewMixin, stockMixin],
|
||||
|
||||
data: () => ({
|
||||
store: useStore(),
|
||||
locomotiveTypeList: [
|
||||
{
|
||||
id: 'loco-e',
|
||||
@@ -153,12 +149,20 @@ export default defineComponent({
|
||||
],
|
||||
}),
|
||||
|
||||
setup() {
|
||||
const store = useStore();
|
||||
computed: {
|
||||
locoOptions() {
|
||||
return this.store.locoDataList
|
||||
.slice()
|
||||
.sort((a, b) => (a.type > b.type ? 1 : -1))
|
||||
.filter((loco) => loco.power == this.store.chosenLocoPower);
|
||||
},
|
||||
|
||||
return {
|
||||
store,
|
||||
};
|
||||
carOptions() {
|
||||
return this.store.carDataList
|
||||
.slice()
|
||||
.sort((a, b) => (a.type > b.type ? 1 : -1))
|
||||
.filter((car) => car.useType == this.store.chosenCarUseType);
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
@@ -192,6 +196,29 @@ export default defineComponent({
|
||||
const stockObject = this.getStockObject(vehicle, this.store.chosenCargo);
|
||||
this.store.stockList[this.store.chosenStockListIndex] = stockObject;
|
||||
},
|
||||
|
||||
selectLocoType(locoTypeId: string) {
|
||||
this.store.chosenLocoPower = locoTypeId;
|
||||
this.store.chosenVehicle = this.locoOptions[0];
|
||||
this.store.chosenLoco = this.locoOptions[0];
|
||||
},
|
||||
|
||||
selectCarWagonType(carWagonTypeId: string) {
|
||||
this.store.chosenCarUseType = carWagonTypeId;
|
||||
this.store.chosenVehicle = this.carOptions[0];
|
||||
this.store.chosenCar = this.carOptions[0];
|
||||
this.store.chosenCargo = null;
|
||||
},
|
||||
|
||||
previewVehicleByType(type: 'loco' | 'car' | 'cargo') {
|
||||
this.$nextTick(() => {
|
||||
if (!this.store.chosenLoco && !this.store.chosenCar) return;
|
||||
|
||||
this.store.chosenVehicle = type == 'loco' ? this.store.chosenLoco : this.store.chosenCar;
|
||||
|
||||
this.store.chosenCargo = this.store.chosenCar?.cargoList.find((cargo) => cargo.id == this.store.chosenCargo?.id) || null;
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -207,6 +234,11 @@ export default defineComponent({
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
.input_container {
|
||||
width: 100%;
|
||||
max-width: 380px;
|
||||
}
|
||||
|
||||
.input_header {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
@@ -226,6 +258,10 @@ button.btn--choice {
|
||||
.input_list {
|
||||
margin: 0.5em 0;
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
|
||||
@@ -267,4 +303,3 @@ button.btn--choice {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
<template>
|
||||
<section class="logo-section">
|
||||
<img :src="`/logo-${$i18n.locale}.svg`" alt="logo pojazdownik" @click="navigate" />
|
||||
<img
|
||||
:src="`/logo-${$i18n.locale}.svg`"
|
||||
alt="logo pojazdownik"
|
||||
@click="navigate"
|
||||
/>
|
||||
|
||||
<div class="actions">
|
||||
<button
|
||||
class="btn btn--text"
|
||||
v-for="action in localeActions"
|
||||
:key="action.name"
|
||||
class="btn btn--text"
|
||||
:data-selected="$i18n.locale == action.locale"
|
||||
@click="chooseLocale(action.locale)"
|
||||
>
|
||||
@@ -21,31 +26,31 @@ export default {
|
||||
return {
|
||||
localeActions: [
|
||||
{
|
||||
name: 'POLSKI',
|
||||
locale: 'pl',
|
||||
name: "POLSKI",
|
||||
locale: "pl",
|
||||
},
|
||||
{
|
||||
name: 'ENGLISH',
|
||||
locale: 'en',
|
||||
name: "ENGLISH",
|
||||
locale: "en",
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
navigate() {
|
||||
window.location.pathname = '';
|
||||
window.location.pathname = "";
|
||||
},
|
||||
|
||||
chooseLocale(locale: string) {
|
||||
this.$i18n.locale = locale;
|
||||
window.localStorage.setItem('locale', locale);
|
||||
window.localStorage.setItem("locale", locale);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/global.scss';
|
||||
@import "../../styles/global.scss";
|
||||
|
||||
.logo-section {
|
||||
grid-row: 1;
|
||||
@@ -64,7 +69,7 @@ export default {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
|
||||
button[data-selected='true'] {
|
||||
button[data-selected="true"] {
|
||||
font-weight: bold;
|
||||
color: $accentColor;
|
||||
text-decoration: underline;
|
||||
@@ -76,4 +81,3 @@ img {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
<section class="stock-section">
|
||||
<div class="section_modes">
|
||||
<button
|
||||
v-for="(id, i) in sectionModes"
|
||||
:key="id"
|
||||
class="btn"
|
||||
ref="sectionButtonRefs"
|
||||
v-for="(id, i) in sectionModes"
|
||||
@click="chooseSection(id)"
|
||||
:data-selected="store.stockSectionMode == id"
|
||||
>
|
||||
@@ -15,29 +16,37 @@
|
||||
|
||||
<transition name="tab-change" mode="out-in">
|
||||
<keep-alive>
|
||||
<component :is="chosenSectionComponent" :key="chosenSectionComponent"></component>
|
||||
<component
|
||||
:is="chosenSectionComponent"
|
||||
:key="chosenSectionComponent"
|
||||
></component>
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, KeepAlive, onMounted, ref } from 'vue';
|
||||
import { useStore } from '../../store';
|
||||
import StockListTab from '../tabs/StockListTab.vue';
|
||||
import StockGeneratorTab from '../tabs/StockGeneratorTab.vue';
|
||||
import NumberGeneratorTab from '../tabs/NumberGeneratorTab.vue';
|
||||
import WikiListTab from '../tabs/WikiListTab.vue';
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { useStore } from "../../store";
|
||||
import StockListTab from "../tabs/StockListTab.vue";
|
||||
import StockGeneratorTab from "../tabs/StockGeneratorTab.vue";
|
||||
import NumberGeneratorTab from "../tabs/NumberGeneratorTab.vue";
|
||||
import WikiListTab from "../tabs/WikiListTab.vue";
|
||||
|
||||
const sectionButtonRefs = ref([]);
|
||||
|
||||
const store = useStore();
|
||||
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(() => {
|
||||
window.addEventListener('keydown', (e) => {
|
||||
window.addEventListener("keydown", (e) => {
|
||||
if (e.target instanceof HTMLInputElement) return;
|
||||
|
||||
if (/[1234]/.test(e.key)) {
|
||||
@@ -50,16 +59,16 @@ onMounted(() => {
|
||||
|
||||
const chosenSectionComponent = computed(() => {
|
||||
switch (store.stockSectionMode) {
|
||||
case 'stock-list':
|
||||
case "stock-list":
|
||||
return StockListTab;
|
||||
|
||||
case 'wiki-list':
|
||||
case "wiki-list":
|
||||
return WikiListTab;
|
||||
|
||||
case 'stock-generator':
|
||||
case "stock-generator":
|
||||
return StockGeneratorTab;
|
||||
|
||||
case 'number-generator':
|
||||
case "number-generator":
|
||||
return NumberGeneratorTab;
|
||||
|
||||
default:
|
||||
@@ -73,7 +82,7 @@ function chooseSection(sectionId: SectionMode) {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../styles/global.scss';
|
||||
@import "../../styles/global.scss";
|
||||
|
||||
// Tab change animation
|
||||
.tab-change {
|
||||
@@ -115,14 +124,14 @@ function chooseSection(sectionId: SectionMode) {
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
|
||||
content: '';
|
||||
content: "";
|
||||
width: 0;
|
||||
height: 2px;
|
||||
transition: all 100ms;
|
||||
background-color: $accentColor;
|
||||
}
|
||||
|
||||
&[data-selected='true']::after {
|
||||
&[data-selected="true"]::after {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -134,4 +143,3 @@ function chooseSection(sectionId: SectionMode) {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,58 +1,47 @@
|
||||
<template>
|
||||
<section class="train-image-section">
|
||||
<div class="train-image__wrapper">
|
||||
<div class="train-image__content" :class="{ supporter: store.chosenVehicle?.supportersOnly }">
|
||||
<transition name="img-message-anim">
|
||||
<div class="empty-message" v-if="store.imageLoading && store.chosenVehicle?.imageSrc">
|
||||
{{ $t('preview.loading') }}
|
||||
</div>
|
||||
</transition>
|
||||
<div class="train-image__content" :class="{ sponsor: store.chosenVehicle?.isSponsorsOnly }">
|
||||
<img
|
||||
v-if="store.chosenVehicle"
|
||||
tabindex="0"
|
||||
:src="getThumbnailURL(store.chosenVehicle.type, 'small')"
|
||||
@click="onImageClick"
|
||||
@keydown.enter="onImageClick"
|
||||
@error="onImageError"
|
||||
type="image/jpeg"
|
||||
/>
|
||||
|
||||
<div class="no-img" v-if="!store.chosenVehicle">{{ $t('preview.title') }}</div>
|
||||
|
||||
<img
|
||||
v-if="store.chosenVehicle"
|
||||
:src="`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>
|
||||
<img v-else src="/images/placeholder.jpg" alt="placeholder" />
|
||||
</div>
|
||||
|
||||
<div class="train-image__info" v-if="store.chosenVehicle">
|
||||
<b class="text--accent">{{ store.chosenVehicle.type }}</b> •
|
||||
<b style="color: #ccc">
|
||||
{{ $t(`preview.${isLocomotive(store.chosenVehicle) ? store.chosenVehicle.power : store.chosenVehicle.useType}`) }}
|
||||
</b>
|
||||
|
||||
<div style="color: #ccc">
|
||||
<div>{{ store.chosenVehicle.length }}m | {{ store.chosenVehicle.mass }}t | {{ store.chosenVehicle.maxSpeed }} km/h</div>
|
||||
|
||||
<div v-if="isLocomotive(store.chosenVehicle)">{{ $t('preview.cabin') }} {{ store.chosenVehicle.cabinType }}</div>
|
||||
|
||||
<div v-else>
|
||||
{{
|
||||
store.chosenVehicle.useType == 'car-cargo'
|
||||
? $t(`usage.${store.chosenVehicle.constructionType}`)
|
||||
: `${$t('preview.construction')} ${store.chosenVehicle.constructionType}`
|
||||
}}
|
||||
</div>
|
||||
|
||||
<b style="color: salmon" v-if="store.chosenVehicle.isSponsorsOnly">{{
|
||||
$t('preview.sponsor-only', [
|
||||
new Date(store.chosenVehicle.sponsorsOnlyTimestamp).toLocaleDateString($i18n.locale == 'pl' ? 'pl-PL' : 'en-GB'),
|
||||
])
|
||||
}}</b>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="train-image__info" v-else>{{ $t('preview.desc') }}</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -61,8 +50,17 @@ import { computed, defineComponent } from 'vue';
|
||||
import { useStore } from '../../store';
|
||||
import { isLocomotive } from '../../utils/vehicleUtils';
|
||||
import { ILocomotive, Vehicle } from '../../types';
|
||||
import imageMixin from '../../mixins/imageMixin';
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [imageMixin],
|
||||
|
||||
data() {
|
||||
return {
|
||||
noImageAvailable: false,
|
||||
};
|
||||
},
|
||||
|
||||
setup() {
|
||||
const store = useStore();
|
||||
|
||||
@@ -85,16 +83,26 @@ export default defineComponent({
|
||||
this.store.imageLoading = false;
|
||||
},
|
||||
|
||||
onImageError(e: Event) {
|
||||
const el = e.target as HTMLImageElement;
|
||||
if (el.src == '/images/placeholder.jpg') return;
|
||||
|
||||
el.src = '/images/placeholder.jpg';
|
||||
},
|
||||
|
||||
isLocomotive(vehicle: Vehicle): vehicle is ILocomotive {
|
||||
return isLocomotive(vehicle);
|
||||
},
|
||||
|
||||
onImageClick() {
|
||||
onImageClick(e: Event) {
|
||||
const target = e.target as HTMLElement;
|
||||
|
||||
const chosenVehicle = this.store.chosenVehicle;
|
||||
|
||||
if (!chosenVehicle) return;
|
||||
|
||||
this.store.vehiclePreviewSrc = `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';
|
||||
|
||||
.train-image-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
|
||||
grid-row: 3;
|
||||
grid-column: 1;
|
||||
|
||||
margin-top: 2em;
|
||||
margin-top: 1em;
|
||||
height: 22em;
|
||||
}
|
||||
|
||||
.train-image {
|
||||
&__wrapper {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__content {
|
||||
border: 1px solid white;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
max-width: 22em;
|
||||
height: 13em;
|
||||
margin: 0 auto;
|
||||
|
||||
&.supporter {
|
||||
&.sponsor img {
|
||||
border: 1px solid salmon;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 380px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid white;
|
||||
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
.empty-message,
|
||||
.no-img {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
|
||||
padding: 0.3em 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.empty-message {
|
||||
background: rgba(#000, 0.75);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.train-image__info {
|
||||
margin: 1em 0;
|
||||
font-size: 1.1em;
|
||||
padding: 0 1em;
|
||||
padding: 0.5em;
|
||||
margin: 0.5em auto;
|
||||
line-height: 1.35;
|
||||
|
||||
b {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
width: 100%;
|
||||
max-width: 380px;
|
||||
|
||||
div {
|
||||
margin: 0.25em 0;
|
||||
}
|
||||
background-color: $secondaryColor;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
// Transition animations
|
||||
@@ -185,4 +172,3 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,34 +1,55 @@
|
||||
<template>
|
||||
<div class="number-generator tab">
|
||||
<div class="tab_header">
|
||||
<h2>{{ $t('numgen.title') }}</h2>
|
||||
<h2>{{ $t("numgen.title") }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="tab_content">
|
||||
<div class="options">
|
||||
<select v-model="chosenCategory" @change="randomizeTrainNumber()">
|
||||
<option :value="null" disabled>{{ $t('numgen.train-category') }}</option>
|
||||
<option v-for="(_, category) in genData.categories" :value="category">
|
||||
<option :value="null" disabled>
|
||||
{{ $t("numgen.train-category") }}
|
||||
</option>
|
||||
<option
|
||||
v-for="(_, category) in genData.categories"
|
||||
:key="category"
|
||||
:value="category"
|
||||
>
|
||||
{{ $t(`numgen.categories.${category}`) }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<select v-model="beginRegionName" @change="randomizeTrainNumber()">
|
||||
<option :value="null" disabled>{{ $t('numgen.start-region') }}</option>
|
||||
<option v-for="(_, name) in genData.regionNumbers" :value="name">{{ name }}</option>
|
||||
<option :value="null" disabled>
|
||||
{{ $t("numgen.start-region") }}
|
||||
</option>
|
||||
<option
|
||||
v-for="(_, name) in genData.regionNumbers"
|
||||
:key="name"
|
||||
:value="name"
|
||||
>
|
||||
{{ name }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<select v-model="endRegionName" @change="randomizeTrainNumber()">
|
||||
<option :value="null" disabled>{{ $t('numgen.end-region') }}</option>
|
||||
<option v-for="(_, name) in genData.regionNumbers" :value="name">{{ name }}</option>
|
||||
<option :value="null" disabled>{{ $t("numgen.end-region") }}</option>
|
||||
<option
|
||||
v-for="(_, name) in genData.regionNumbers"
|
||||
:key="name"
|
||||
:value="name"
|
||||
>
|
||||
{{ name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="generated-number" @click="copyNumber">
|
||||
<span v-if="trainNumber">
|
||||
{{ $t('numgen.number-info') }} <b class="text--accent">{{ trainNumber }}</b>
|
||||
{{ $t("numgen.number-info") }}
|
||||
<b class="text--accent">{{ trainNumber }}</b>
|
||||
</span>
|
||||
<span v-else>{{ $t('numgen.warning') }}</span>
|
||||
<span v-else>{{ $t("numgen.warning") }}</span>
|
||||
</div>
|
||||
|
||||
<!-- <div v-if="chosenCategory">
|
||||
@@ -50,25 +71,29 @@
|
||||
|
||||
<div class="tab_links">
|
||||
<a :href="$t('numgen.td2-wiki-link')" target="_blank">
|
||||
{{ $t('numgen.td2-wiki') }}
|
||||
{{ $t("numgen.td2-wiki") }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="tab_actions">
|
||||
<button class="btn" @click="randomizeTrainNumber(true)">{{ $t('numgen.action-random-region') }}</button>
|
||||
<button class="btn" @click="randomizeTrainNumber(false)">{{ $t('numgen.action-random-number') }}</button>
|
||||
<button class="btn" @click="randomizeTrainNumber(true)">
|
||||
{{ $t("numgen.action-random-region") }}
|
||||
</button>
|
||||
<button class="btn" @click="randomizeTrainNumber(false)">
|
||||
{{ $t("numgen.action-random-number") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Ref, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { Ref, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
import genData from '../../constants/numberGeneratorData.json';
|
||||
import genData from "../../constants/numberGeneratorData.json";
|
||||
|
||||
const i18n = useI18n();
|
||||
type RegionName = keyof typeof genData.regionNumbers;
|
||||
@@ -83,7 +108,7 @@ const trainNumber = ref(null) as Ref<string | null>;
|
||||
const copyNumber = () => {
|
||||
if (trainNumber.value) {
|
||||
navigator.clipboard.writeText(trainNumber.value);
|
||||
alert(i18n.t('numgen.alert'));
|
||||
alert(i18n.t("numgen.alert"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -93,16 +118,21 @@ const randomizeTrainNumber = (randomizeRegions = false) => {
|
||||
const regionKeys = Object.keys(genData.regionNumbers);
|
||||
|
||||
if (beginRegionName.value == null || randomizeRegions)
|
||||
beginRegionName.value = regionKeys[(regionKeys.length * Math.random()) << 0] as RegionName;
|
||||
beginRegionName.value = regionKeys[
|
||||
(regionKeys.length * Math.random()) << 0
|
||||
] as RegionName;
|
||||
|
||||
if (endRegionName.value == null || randomizeRegions)
|
||||
endRegionName.value = regionKeys[(regionKeys.length * Math.random()) << 0] as RegionName;
|
||||
endRegionName.value = regionKeys[
|
||||
(regionKeys.length * Math.random()) << 0
|
||||
] as RegionName;
|
||||
|
||||
let number = '';
|
||||
let number = "";
|
||||
|
||||
if (beginRegionName.value == endRegionName.value) {
|
||||
const sameRegionsNumbers = genData.sameRegions[beginRegionName.value!];
|
||||
const randRegionNumber = sameRegionsNumbers[Math.floor(Math.random() * sameRegionsNumbers.length)];
|
||||
const randRegionNumber =
|
||||
sameRegionsNumbers[Math.floor(Math.random() * sameRegionsNumbers.length)];
|
||||
number += randRegionNumber.toString();
|
||||
} else {
|
||||
const beginRegionNumber = genData.regionNumbers[beginRegionName.value!];
|
||||
@@ -117,23 +147,30 @@ const randomizeTrainNumber = (randomizeRegions = false) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (chosenCategory.value == null) chosenCategory.value = 'EI';
|
||||
if (chosenCategory.value == null) chosenCategory.value = "EI";
|
||||
|
||||
const rulesArray = genData.categories[chosenCategory.value].split(';').map((r) => ({
|
||||
index: r.split(':')[0],
|
||||
rule: r.split(':')[1],
|
||||
nums: Number(r.split(':')[2] || '1'),
|
||||
}));
|
||||
const rulesArray = genData.categories[chosenCategory.value]
|
||||
.split(";")
|
||||
.map((r) => ({
|
||||
index: r.split(":")[0],
|
||||
rule: r.split(":")[1],
|
||||
nums: Number(r.split(":")[2] || "1"),
|
||||
}));
|
||||
|
||||
rulesArray.forEach((r) => {
|
||||
const range = r.rule.split('-');
|
||||
const range = r.rule.split("-");
|
||||
|
||||
if (range.length == 1) number += r.rule;
|
||||
else {
|
||||
const [minRange, maxRange] = range;
|
||||
const randRange = Math.floor(Math.random() * (Number(maxRange) - Number(minRange)) + Number(minRange)).toString();
|
||||
const randRange = Math.floor(
|
||||
Math.random() * (Number(maxRange) - Number(minRange)) +
|
||||
Number(minRange),
|
||||
).toString();
|
||||
|
||||
number += new Array(Math.abs(randRange.length - r.nums)).fill('0').join('') + randRange;
|
||||
number +=
|
||||
new Array(Math.abs(randRange.length - r.nums)).fill("0").join("") +
|
||||
randRange;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -142,8 +179,8 @@ const randomizeTrainNumber = (randomizeRegions = false) => {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/tab.scss';
|
||||
@import '../../styles/global.scss';
|
||||
@import "../../styles/tab.scss";
|
||||
@import "../../styles/global.scss";
|
||||
|
||||
.options {
|
||||
display: grid;
|
||||
@@ -189,4 +226,3 @@ const randomizeTrainNumber = (randomizeRegions = false) => {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -37,9 +37,10 @@
|
||||
|
||||
<div class="generator_cargo">
|
||||
<button
|
||||
v-for="(cargoArray, cargoName) in store.stockData?.generator.cargo"
|
||||
:key="cargoName"
|
||||
class="btn"
|
||||
:data-chosen="chosenCargoTypes.includes(cargoName.toString())"
|
||||
v-for="(cargoArray, cargoName) in store.stockData?.generator.cargo"
|
||||
@click="toggleCargoChosen(cargoName.toString(), cargoArray)"
|
||||
>
|
||||
{{ $t(`cargo.${cargoName}`) }}
|
||||
@@ -126,7 +127,7 @@ export default defineComponent({
|
||||
|
||||
computed: {
|
||||
computedChosenCarTypes() {
|
||||
return new Set<string>(this.chosenCarTypes.sort((c1, c2) => (c1 > c2 ? 1 : -1)));
|
||||
return new Set<string>(this.chosenCarTypes.slice().sort((c1, c2) => (c1 > c2 ? 1 : -1)));
|
||||
},
|
||||
},
|
||||
|
||||
@@ -150,44 +151,52 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
generateStock(empty = false) {
|
||||
const generatedChosenStockList = this.chosenCargoTypes.reduce((acc, type) => {
|
||||
this.store.stockData?.generator.cargo[type]
|
||||
.filter((c) => !this.excludedCarTypes.includes(c.split(':')[0]))
|
||||
.forEach((c) => {
|
||||
const [type, cargoType] = c.split(':');
|
||||
const generatedChosenStockList = this.chosenCargoTypes.reduce(
|
||||
(acc, type) => {
|
||||
this.store.stockData?.generator.cargo[type]
|
||||
.filter((c) => !this.excludedCarTypes.includes(c.split(':')[0]))
|
||||
.forEach((c) => {
|
||||
const [type, cargoType] = c.split(':');
|
||||
|
||||
const carWagonObjs = this.store.carDataList.filter((cw) => cw.type.startsWith(type));
|
||||
const cargoObjs = [] as (ICargo | undefined)[];
|
||||
const carWagonObjs = this.store.carDataList.filter((cw) => cw.type.startsWith(type));
|
||||
const cargoObjs = [] as (ICargo | undefined)[];
|
||||
|
||||
if (!cargoType || empty) cargoObjs.push(undefined);
|
||||
else if (cargoType == 'all') cargoObjs.push(...carWagonObjs[0]?.cargoList);
|
||||
else cargoObjs.push(carWagonObjs[0]?.cargoList.find((cargo) => cargo.id == cargoType));
|
||||
if (!cargoType || empty) cargoObjs.push(undefined);
|
||||
else if (cargoType == 'all') cargoObjs.push(...carWagonObjs[0]!.cargoList);
|
||||
else cargoObjs.push(carWagonObjs[0]?.cargoList.find((cargo) => cargo.id == cargoType));
|
||||
|
||||
carWagonObjs.forEach((cw) => {
|
||||
cargoObjs.forEach((cargoObj) => {
|
||||
const chosenStock = acc.find((a) => a.constructionType.includes(cw.constructionType));
|
||||
carWagonObjs.forEach((cw) => {
|
||||
cargoObjs.forEach((cargoObj) => {
|
||||
const chosenStock = acc.find((a) => a.constructionType.includes(cw.constructionType));
|
||||
|
||||
if (!chosenStock)
|
||||
acc.push({
|
||||
constructionType: cw.constructionType,
|
||||
carPool: [{ carWagon: cw, cargo: cargoObj }],
|
||||
});
|
||||
else chosenStock.carPool.push({ carWagon: cw, cargo: cargoObj });
|
||||
if (!chosenStock)
|
||||
acc.push({
|
||||
constructionType: cw.constructionType,
|
||||
carPool: [{ carWagon: cw, cargo: cargoObj }],
|
||||
});
|
||||
else chosenStock.carPool.push({ carWagon: cw, cargo: cargoObj });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, [] as { constructionType: string; carPool: { carWagon: ICarWagon; cargo?: ICargo }[] }[]);
|
||||
return acc;
|
||||
},
|
||||
[] as {
|
||||
constructionType: string;
|
||||
carPool: { carWagon: ICarWagon; cargo?: ICargo }[];
|
||||
}[]
|
||||
);
|
||||
|
||||
let bestGeneration: { stockList: IStock[]; value: number } = { stockList: [], value: 0 };
|
||||
let bestGeneration: { stockList: IStock[]; value: number } = {
|
||||
stockList: [],
|
||||
value: 0,
|
||||
};
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const headingLoco = this.store.stockList[0]?.isLoco ? this.store.stockList[0] : undefined;
|
||||
this.store.stockList.length = headingLoco ? 1 : 0;
|
||||
|
||||
const maxMass =
|
||||
this.store.acceptableMass > 0 ? Math.min(this.store.acceptableMass, this.maxMass) : this.maxMass;
|
||||
const maxMass = this.store.acceptableMass > 0 ? Math.min(this.store.acceptableMass, this.maxMass) : this.maxMass;
|
||||
|
||||
let exceeded = false;
|
||||
|
||||
@@ -277,13 +286,6 @@ export default defineComponent({
|
||||
|
||||
background-color: $secondaryColor;
|
||||
|
||||
&[data-chosen='true'] {
|
||||
background-color: $accentColor;
|
||||
color: black;
|
||||
|
||||
box-shadow: 0 0 5px 1px $accentColor;
|
||||
}
|
||||
|
||||
&[data-excluded='true'] {
|
||||
background-color: gray;
|
||||
box-shadow: none;
|
||||
@@ -316,4 +318,3 @@ export default defineComponent({
|
||||
color: black;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -5,14 +5,11 @@
|
||||
</div>
|
||||
|
||||
<div class="stock_actions">
|
||||
<label class="file-label">
|
||||
<div class="btn btn--image">
|
||||
<img src="/images/icon-upload.svg" alt="" />
|
||||
{{ $t('stocklist.action-upload') }}
|
||||
</div>
|
||||
|
||||
<button class="btn btn--image" @click="clickFileInput">
|
||||
<input type="file" @change="uploadStock" ref="conFile" accept=".con,.txt" />
|
||||
</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">
|
||||
<img src="/images/icon-download.svg" alt="download icon" />
|
||||
@@ -37,36 +34,26 @@
|
||||
|
||||
<div class="stock_controls" :data-disabled="store.chosenStockListIndex == -1">
|
||||
<b v-if="store.chosenStockListIndex >= 0">
|
||||
{{ $t('stocklist.vehicle-no') }} <span class="text--accent">{{ store.chosenStockListIndex + 1 }}</span>
|
||||
{{ $t('stocklist.vehicle-no') }}
|
||||
<span class="text--accent">{{ store.chosenStockListIndex + 1 }}</span>
|
||||
|
||||
</b>
|
||||
|
||||
<b v-else>
|
||||
{{ $t('stocklist.no-vehicle-chosen') }}
|
||||
</b>
|
||||
|
||||
<button
|
||||
class="btn"
|
||||
:tabindex="store.chosenStockListIndex == -1 ? -1 : 0"
|
||||
@click="moveUpStock(store.chosenStockListIndex)"
|
||||
>
|
||||
<button class="btn" :tabindex="store.chosenStockListIndex == -1 ? -1 : 0" @click="moveUpStock(store.chosenStockListIndex)">
|
||||
<img :src="getIconURL('higher')" alt="move up vehicle" />
|
||||
{{ $t('stocklist.action-move-up') }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn"
|
||||
:tabindex="store.chosenStockListIndex == -1 ? -1 : 0"
|
||||
@click="moveDownStock(store.chosenStockListIndex)"
|
||||
>
|
||||
<button class="btn" :tabindex="store.chosenStockListIndex == -1 ? -1 : 0" @click="moveDownStock(store.chosenStockListIndex)">
|
||||
<img :src="getIconURL('lower')" alt="move down vehicle" />
|
||||
{{ $t('stocklist.action-move-down') }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn"
|
||||
:tabindex="store.chosenStockListIndex == -1 ? -1 : 0"
|
||||
@click="removeStock(store.chosenStockListIndex)"
|
||||
>
|
||||
<button class="btn" :tabindex="store.chosenStockListIndex == -1 ? -1 : 0" @click="removeStock(store.chosenStockListIndex)">
|
||||
<img :src="getIconURL('remove')" alt="remove vehicle" />
|
||||
{{ $t('stocklist.action-remove') }}
|
||||
</button>
|
||||
@@ -82,22 +69,19 @@
|
||||
</b>
|
||||
|
||||
<span>
|
||||
{{ $t('stocklist.mass') }} <span class="text--accent">{{ store.totalMass }}t</span> ({{
|
||||
$t('stocklist.mass-accepted')
|
||||
}}: <span class="text--accent">{{ store.acceptableMass ? store.acceptableMass + 't' : '-' }}</span
|
||||
{{ $t('stocklist.mass') }}
|
||||
<span class="text--accent">{{ store.totalMass }}t</span> ({{ $t('stocklist.mass-accepted') }}:
|
||||
<span class="text--accent">{{ store.acceptableMass ? store.acceptableMass + 't' : '-' }}</span
|
||||
>) - {{ $t('stocklist.length') }}:
|
||||
<span class="text--accent">{{ store.totalLength }}m</span>
|
||||
- {{ $t('stocklist.vmax') }}: <span class="text--accent">{{ store.maxStockSpeed }} km/h</span>
|
||||
- {{ $t('stocklist.vmax') }}:
|
||||
<span class="text--accent">{{ store.maxStockSpeed }} km/h</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="stock_cold-start">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="store.isColdStart"
|
||||
:disabled="!locoSupportsColdStart(store.stockList[0]?.constructionType || '')"
|
||||
/>
|
||||
<input type="checkbox" v-model="store.isColdStart" :disabled="!locoSupportsColdStart(store.stockList[0]?.constructionType || '')" />
|
||||
{{ $t('stocklist.coldstart-info') }}
|
||||
</label>
|
||||
</div>
|
||||
@@ -105,22 +89,15 @@
|
||||
<div class="stock_warnings" v-if="stockHasWarnings">
|
||||
<div class="warning" v-if="locoNotSuitable">(!) {{ $t('stocklist.warning-not-suitable') }}</div>
|
||||
|
||||
<div class="warning" v-if="trainTooLong && store.isTrainPassenger">
|
||||
(!) {{ $t('stocklist.warning-passenger-too-long') }}
|
||||
</div>
|
||||
<div class="warning" v-if="trainTooLong && store.isTrainPassenger">(!) {{ $t('stocklist.warning-passenger-too-long') }}</div>
|
||||
|
||||
<div class="warning" v-if="trainTooLong && !store.isTrainPassenger">
|
||||
(!) {{ $t('stocklist.warning-freight-too-long') }}
|
||||
</div>
|
||||
<div class="warning" v-if="trainTooLong && !store.isTrainPassenger">(!) {{ $t('stocklist.warning-freight-too-long') }}</div>
|
||||
|
||||
<div class="warning" v-if="trainTooHeavy">
|
||||
(!)
|
||||
<i18n-t keypath="stocklist.warning-too-heavy">
|
||||
<template #href>
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://docs.google.com/spreadsheets/d/1bFXUsHsAu4youmNz-46Q1HslZaaoklvfoBDS553TnNk/edit"
|
||||
>
|
||||
<a target="_blank" href="https://docs.google.com/spreadsheets/d/1bFXUsHsAu4youmNz-46Q1HslZaaoklvfoBDS553TnNk/edit">
|
||||
{{ $t('stocklist.acceptable-mass-docs') }}
|
||||
</a>
|
||||
</template>
|
||||
@@ -153,23 +130,19 @@
|
||||
@keydown.backspace="removeStock(i)"
|
||||
ref="itemRefs"
|
||||
>
|
||||
<div
|
||||
class="stock-info"
|
||||
@dragstart="onDragStart(i)"
|
||||
@drop="onDrop($event, i)"
|
||||
@dragover="allowDrop"
|
||||
draggable="true"
|
||||
>
|
||||
<div class="stock-info" @dragstart="onDragStart(i)" @drop="onDrop($event, i)" @dragover="allowDrop" draggable="true">
|
||||
<span class="stock-info__no" :data-selected="i == store.chosenStockListIndex">
|
||||
<span v-if="i == store.chosenStockListIndex">• </span>
|
||||
{{ i + 1 }}.
|
||||
</span>
|
||||
|
||||
<span class="stock-info__type" :class="{ supporter: stock.supportersOnly }">
|
||||
<span class="stock-info__type" :class="{ sponsor: stock.isSponsorsOnly }">
|
||||
{{ stock.isLoco ? stock.type : getCarSpecFromType(stock.type) }}
|
||||
</span>
|
||||
|
||||
<span class="stock-info__cargo" v-if="stock.cargo"> {{ stock.cargo.id }} </span>
|
||||
<span class="stock-info__cargo" v-if="stock.cargo">
|
||||
{{ stock.cargo.id }}
|
||||
</span>
|
||||
<span class="stock-info__length"> {{ stock.length }}m </span>
|
||||
<span class="stock-info__mass">{{ stock.cargo ? stock.cargo.totalMass : stock.mass }}t </span>
|
||||
<span class="stock-info__speed"> {{ stock.maxSpeed }}km/h </span>
|
||||
@@ -182,7 +155,6 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import TrainImage from '../sections/TrainImageSection.vue';
|
||||
|
||||
import { useStore } from '../../store';
|
||||
|
||||
@@ -195,7 +167,7 @@ import stockMixin from '../../mixins/stockMixin';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'stock-list',
|
||||
components: { TrainImage, StockThumbnails },
|
||||
components: { StockThumbnails },
|
||||
|
||||
mixins: [warningsMixin, imageMixin, stockMixin, stockPreviewMixin],
|
||||
|
||||
@@ -219,8 +191,7 @@ export default defineComponent({
|
||||
return this.store.stockList
|
||||
.map((stock, i) => {
|
||||
let stockTypeStr = stock.isLoco || !stock.cargo ? stock.type : `${stock.type}:${stock.cargo.id}`;
|
||||
let coldStart =
|
||||
i == 0 && this.store.isColdStart && locoSupportsColdStart(stock.constructionType || '') ? ',c' : '';
|
||||
let coldStart = i == 0 && this.store.isColdStart && locoSupportsColdStart(stock.constructionType || '') ? ',c' : '';
|
||||
|
||||
return stockTypeStr + coldStart;
|
||||
})
|
||||
@@ -251,11 +222,14 @@ export default defineComponent({
|
||||
}, 20);
|
||||
},
|
||||
|
||||
clickFileInput() {
|
||||
(this.$refs['conFile'] as HTMLInputElement).click();
|
||||
},
|
||||
|
||||
onListItemClick(stockID: number) {
|
||||
const stock = this.store.stockList[stockID];
|
||||
|
||||
this.store.chosenStockListIndex =
|
||||
this.store.chosenStockListIndex == stockID && this.store.chosenVehicle?.type == stock.type ? -1 : stockID;
|
||||
this.store.chosenStockListIndex = this.store.chosenStockListIndex == stockID && this.store.chosenVehicle?.type == stock.type ? -1 : stockID;
|
||||
|
||||
if (this.store.chosenStockListIndex == -1) {
|
||||
this.store.chosenVehicle = null;
|
||||
@@ -352,9 +326,9 @@ export default defineComponent({
|
||||
downloadStock() {
|
||||
if (this.store.stockList.length == 0) return alert(this.$t('stocklist.alert-empty'));
|
||||
|
||||
const defaultName = `${this.store.chosenRealStockName || this.store.stockList[0].type} ${
|
||||
this.store.totalMass
|
||||
}t; ${this.store.totalLength}m; vmax ${this.store.maxStockSpeed}`;
|
||||
const defaultName = `${this.store.chosenRealStockName || this.store.stockList[0].type} ${this.store.totalMass}t; ${
|
||||
this.store.totalLength
|
||||
}m; vmax ${this.store.maxStockSpeed}`;
|
||||
|
||||
const fileName = prompt(this.$t('stocklist.prompt-file'), defaultName);
|
||||
|
||||
@@ -487,12 +461,13 @@ export default defineComponent({
|
||||
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
|
||||
label.file-label {
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
button {
|
||||
width: 100%;
|
||||
|
||||
input {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -549,7 +524,7 @@ li > .stock-info {
|
||||
}
|
||||
}
|
||||
|
||||
.supporter {
|
||||
.sponsor {
|
||||
color: salmon;
|
||||
}
|
||||
|
||||
@@ -610,4 +585,3 @@ li > .stock-info {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
+328
-356
@@ -1,356 +1,328 @@
|
||||
<template>
|
||||
<section class="wiki-list tab">
|
||||
<div class="tab_header">
|
||||
<h2>{{ $t('wiki.title') }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="tab_content">
|
||||
<div class="actions-panel">
|
||||
<div class="actions-panel_vehicles">
|
||||
<button class="btn btn--choice" @click="changeWikiMode('locomotives')">
|
||||
{{ $t('wiki.action-vehicles') }}
|
||||
</button>
|
||||
<button class="btn btn--choice" @click="changeWikiMode('carWagons')">
|
||||
{{ $t('wiki.action-carriages') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="actions-panel_search">
|
||||
<input type="text" :placeholder="$t('wiki.search')" v-model="searchedVehicleTypeName" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-wrapper" @scroll="scrollEvent" ref="table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="header in wikiMode == 'locomotives' ? locoHeaders : carHeaders" @click="toggleSorter(header)">
|
||||
{{ $t(`wiki.header.${header.id}`) }}
|
||||
|
||||
<span v-if="currentModeSorter.id == header.id">
|
||||
{{ currentModeSorter.direction == 1 ? `⇑` : `⇓` }}
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody v-if="wikiMode == 'locomotives'">
|
||||
<tr
|
||||
v-for="loco in computedLocoList"
|
||||
@click="previewLocomotive(loco)"
|
||||
@keydown.enter="previewLocomotive(loco)"
|
||||
@dblclick="addLocomotive(loco)"
|
||||
tabindex="0"
|
||||
>
|
||||
<td>
|
||||
<img
|
||||
:src="`https://spythere.github.io/api/td2/images/${loco.type}--300px.jpg`"
|
||||
loading="lazy"
|
||||
:alt="`Lokomotywa ${loco.type}`"
|
||||
/>
|
||||
</td>
|
||||
|
||||
<td>{{ loco.type }}</td>
|
||||
<td>{{ $t(`wiki.${loco.power}`) }}</td>
|
||||
<td>{{ loco.constructionType }}</td>
|
||||
<td>{{ locoSupportsColdStart(loco.constructionType) ? `✓` : '✗' }}</td>
|
||||
<td>{{ loco.length }}m</td>
|
||||
<td>{{ loco.mass }}t</td>
|
||||
<td>{{ loco.maxSpeed }}km/h</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tbody v-else>
|
||||
<tr
|
||||
v-for="car in computedCarList"
|
||||
@keydow.enter="previewCarWagon(car)"
|
||||
@click="previewCarWagon(car)"
|
||||
@dblclick="addCarWagon(car)"
|
||||
tabindex="0"
|
||||
>
|
||||
<td>
|
||||
<img
|
||||
:src="`https://spythere.github.io/api/td2/images/${car.type}--300px.jpg`"
|
||||
loading="lazy"
|
||||
:alt="`Lokomotywa ${car.type}`"
|
||||
/>
|
||||
</td>
|
||||
|
||||
<td>{{ car.type }}</td>
|
||||
<td>{{ car.constructionType }}</td>
|
||||
<td>{{ car.length }}m</td>
|
||||
<td>{{ car.mass }}t</td>
|
||||
<td>{{ car.maxSpeed }}km/h</td>
|
||||
<td>{{ car.cargoList.length == 0 ? '-' : car.cargoList.length }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useStore } from '../../store';
|
||||
import stockPreviewMixin from '../../mixins/stockPreviewMixin';
|
||||
import { Vehicle } from '../../types';
|
||||
import { isLocomotive } from '../../utils/vehicleUtils';
|
||||
import stockMixin from '../../mixins/stockMixin';
|
||||
import { locoSupportsColdStart } from '../../utils/locoUtils';
|
||||
|
||||
type WikiMode = 'locomotives' | 'carWagons';
|
||||
type SorterID =
|
||||
| 'type'
|
||||
| 'constructionType'
|
||||
| 'image'
|
||||
| 'length'
|
||||
| 'mass'
|
||||
| 'maxSpeed'
|
||||
| 'cargoCount'
|
||||
| 'power'
|
||||
| 'coldStart';
|
||||
|
||||
interface WikiHeader {
|
||||
id: SorterID;
|
||||
sortable: boolean;
|
||||
}
|
||||
|
||||
const locoHeaders: WikiHeader[] = [
|
||||
{ id: 'image', sortable: false },
|
||||
{ id: 'type', sortable: true },
|
||||
{ id: 'power', sortable: true },
|
||||
{ id: 'constructionType', sortable: true },
|
||||
{ id: 'coldStart', sortable: true },
|
||||
{ id: 'length', sortable: true },
|
||||
{ id: 'mass', sortable: true },
|
||||
{ id: 'maxSpeed', sortable: true },
|
||||
];
|
||||
|
||||
const carHeaders: WikiHeader[] = [
|
||||
{ id: 'image', sortable: false },
|
||||
{ id: 'type', sortable: true },
|
||||
{ id: 'constructionType', sortable: true },
|
||||
{ id: 'length', sortable: true },
|
||||
{ id: 'mass', sortable: true },
|
||||
{ id: 'maxSpeed', sortable: true },
|
||||
{ id: 'cargoCount', sortable: true },
|
||||
];
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [stockPreviewMixin, stockMixin],
|
||||
|
||||
data() {
|
||||
return {
|
||||
store: useStore(),
|
||||
locoHeaders,
|
||||
carHeaders,
|
||||
|
||||
locosScrollTop: 0,
|
||||
carsScrollTop: 0,
|
||||
|
||||
wikiMode: 'locomotives' as WikiMode,
|
||||
searchedVehicleTypeName: '',
|
||||
|
||||
currentLocoSorter: {
|
||||
id: 'type' as SorterID,
|
||||
direction: 1,
|
||||
},
|
||||
|
||||
currentCarSorter: {
|
||||
id: 'type' as SorterID,
|
||||
direction: 1,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
activated() {
|
||||
const tableWrapperRef = this.$refs['table-wrapper'] as HTMLElement;
|
||||
tableWrapperRef.scrollTo({ top: this.wikiMode == 'locomotives' ? this.locosScrollTop : this.carsScrollTop });
|
||||
},
|
||||
|
||||
methods: {
|
||||
locoSupportsColdStart,
|
||||
|
||||
scrollEvent(e: Event) {
|
||||
const tableScrollTop = (e.target as HTMLElement).scrollTop;
|
||||
|
||||
if (this.wikiMode == 'locomotives') this.locosScrollTop = tableScrollTop;
|
||||
else this.carsScrollTop = tableScrollTop;
|
||||
},
|
||||
|
||||
changeWikiMode(wikiMode: WikiMode) {
|
||||
this.searchedVehicleTypeName = '';
|
||||
this.wikiMode = wikiMode;
|
||||
},
|
||||
|
||||
toggleSorter(header: WikiHeader) {
|
||||
if (!header.sortable) return;
|
||||
|
||||
if (header.id == this.currentModeSorter.id) this.currentModeSorter.direction *= -1;
|
||||
this.currentModeSorter.id = header.id;
|
||||
},
|
||||
|
||||
sortVehicles(vA: Vehicle, vB: Vehicle) {
|
||||
const { id, direction } = this.currentModeSorter;
|
||||
const vehiclesAreLocos = isLocomotive(vA) && isLocomotive(vB);
|
||||
const vehiclesAreCars = !isLocomotive(vA) && !isLocomotive(vB);
|
||||
|
||||
switch (id) {
|
||||
case 'type':
|
||||
case 'constructionType':
|
||||
return direction == 1 ? vA[id].localeCompare(vB[id]) : vB[id].localeCompare(vA[id]);
|
||||
|
||||
case 'mass':
|
||||
case 'length':
|
||||
case 'maxSpeed':
|
||||
return Math.sign(vA[id] - vB[id]) * direction;
|
||||
|
||||
case 'cargoCount':
|
||||
if (vehiclesAreCars) return Math.sign((vA.cargoList.length || -1) - (vB.cargoList.length || -1)) * direction;
|
||||
|
||||
case 'coldStart':
|
||||
if (vehiclesAreLocos)
|
||||
return (
|
||||
(locoSupportsColdStart(vA.constructionType) > locoSupportsColdStart(vB.constructionType) ? 1 : -1) *
|
||||
direction
|
||||
);
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return direction == 1 ? vA.type.localeCompare(vB.type) : vB.type.localeCompare(vA.type);
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
currentModeSorter() {
|
||||
return this.wikiMode == 'carWagons' ? this.currentCarSorter : this.currentLocoSorter;
|
||||
},
|
||||
|
||||
computedLocoList() {
|
||||
const trimmedSearchValue = this.searchedVehicleTypeName.trim();
|
||||
|
||||
return this.store.locoDataList
|
||||
.filter((loco) => new RegExp(`${trimmedSearchValue}`, 'i').test(loco.type))
|
||||
.sort(this.sortVehicles);
|
||||
},
|
||||
|
||||
computedCarList() {
|
||||
const trimmedSearchValue = this.searchedVehicleTypeName.trim();
|
||||
|
||||
return this.store.carDataList
|
||||
.filter((car) => new RegExp(`${trimmedSearchValue}`, 'i').test(car.type))
|
||||
.sort(this.sortVehicles);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/tab.scss';
|
||||
|
||||
.actions-panel {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5em;
|
||||
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.actions-panel_vehicles {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.actions-panel_search {
|
||||
input {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
overflow: auto;
|
||||
height: 750px;
|
||||
max-height: 95vh;
|
||||
}
|
||||
|
||||
.wiki-list table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
|
||||
thead {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #111;
|
||||
padding: 0.5em;
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
tr {
|
||||
cursor: pointer;
|
||||
background-color: #333;
|
||||
|
||||
&:nth-child(odd) {
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
text-align: center;
|
||||
padding: 0.25em;
|
||||
height: 85px;
|
||||
}
|
||||
|
||||
td:first-child {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
td img {
|
||||
display: block;
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $breakpointMd) {
|
||||
.wiki-list table {
|
||||
td {
|
||||
width: 100px;
|
||||
height: auto;
|
||||
|
||||
img {
|
||||
width: 6em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $breakpointSm) {
|
||||
.actions-panel {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.actions-panel_vehicles {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.actions-panel_search {
|
||||
display: grid;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<section class="wiki-list tab">
|
||||
<div class="tab_header">
|
||||
<h2>{{ $t('wiki.title') }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="tab_content">
|
||||
<div class="actions-panel">
|
||||
<div class="actions-panel_vehicles">
|
||||
<button class="btn" :data-chosen="currentFilterMode == 'tractions'" @click="toggleFilter('tractions')">
|
||||
{{ $t('wiki.action-vehicles') }}
|
||||
</button>
|
||||
<button class="btn" :data-chosen="currentFilterMode == 'carriages'" @click="toggleFilter('carriages')">
|
||||
{{ $t('wiki.action-carriages') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="actions-panel_search">
|
||||
<input type="text" :placeholder="$t('wiki.search')" v-model="searchedVehicleTypeName" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-wrapper" ref="table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="header in visibleHeaders" @click="toggleSorter(header)" :key="header.id">
|
||||
{{ $t(`wiki.header.${header.id}`) }}
|
||||
|
||||
<span v-if="currentSorter.id == header.id">
|
||||
{{ currentSorter.direction == 1 ? `⇑` : `⇓` }}
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="{ vehicle, show } in computedTableData"
|
||||
tabindex="0"
|
||||
v-show="show"
|
||||
:key="vehicle.type"
|
||||
@click="previewVehicle(vehicle)"
|
||||
@keydown.enter="previewVehicle(vehicle)"
|
||||
@dblclick="addVehicle(vehicle)"
|
||||
>
|
||||
<td style="width: 120px">
|
||||
<img
|
||||
width="120"
|
||||
:src="getThumbnailURL(vehicle.type, 'small')"
|
||||
:alt="`${vehicle.type}`"
|
||||
loading="lazy"
|
||||
@error="(e) => ((e.target as HTMLElement).style.display = 'none')"
|
||||
/>
|
||||
</td>
|
||||
|
||||
<td :data-sponsoronly="vehicle.isSponsorsOnly">{{ vehicle.type }}</td>
|
||||
|
||||
<td v-if="isLocomotive(vehicle)">{{ $t(`wiki.${vehicle.power}`) }}</td>
|
||||
<td v-else>{{ $t(`wiki.${vehicle.useType}`) }}</td>
|
||||
|
||||
<td>{{ vehicle.constructionType }}</td>
|
||||
<td>{{ vehicle.length }}m</td>
|
||||
<td>{{ vehicle.mass }}t</td>
|
||||
<td>{{ vehicle.maxSpeed }}km/h</td>
|
||||
|
||||
<td v-if="currentFilterMode == 'carriages'">{{ !isLocomotive(vehicle) ? vehicle.cargoList.length : '---' }}</td>
|
||||
<td v-if="currentFilterMode == 'tractions'">
|
||||
{{ isLocomotive(vehicle) ? (locoSupportsColdStart(vehicle.constructionType) ? `✓` : '✗') : '---' }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<span ref="table-bottom"></span>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useStore } from '../../store';
|
||||
import stockPreviewMixin from '../../mixins/stockPreviewMixin';
|
||||
import { Vehicle } from '../../types';
|
||||
import { isLocomotive } from '../../utils/vehicleUtils';
|
||||
import stockMixin from '../../mixins/stockMixin';
|
||||
import imageMixin from '../../mixins/imageMixin';
|
||||
import { locoSupportsColdStart } from '../../utils/locoUtils';
|
||||
|
||||
type SorterID = 'type' | 'constructionType' | 'image' | 'length' | 'mass' | 'maxSpeed' | 'cargoCount' | 'group' | 'coldStart';
|
||||
|
||||
interface IWikiHeader {
|
||||
id: SorterID;
|
||||
sortable: boolean;
|
||||
for: 'all' | 'carriages' | 'tractions';
|
||||
}
|
||||
|
||||
interface IWikiRow {
|
||||
vehicle: Vehicle;
|
||||
show: boolean;
|
||||
}
|
||||
|
||||
const headers: IWikiHeader[] = [
|
||||
{ id: 'image', sortable: false, for: 'all' },
|
||||
{ id: 'type', sortable: true, for: 'all' },
|
||||
{ id: 'group', sortable: true, for: 'all' },
|
||||
{ id: 'constructionType', sortable: true, for: 'all' },
|
||||
{ id: 'length', sortable: true, for: 'all' },
|
||||
{ id: 'mass', sortable: true, for: 'all' },
|
||||
{ id: 'maxSpeed', sortable: true, for: 'all' },
|
||||
{ id: 'coldStart', sortable: true, for: 'tractions' },
|
||||
{ id: 'cargoCount', sortable: true, for: 'carriages' },
|
||||
];
|
||||
|
||||
export default defineComponent({
|
||||
mixins: [stockPreviewMixin, stockMixin, imageMixin],
|
||||
|
||||
data() {
|
||||
return {
|
||||
store: useStore(),
|
||||
headers,
|
||||
|
||||
scrollTop: 0,
|
||||
|
||||
searchedVehicleTypeName: '',
|
||||
|
||||
currentSorter: {
|
||||
id: 'type' as SorterID,
|
||||
direction: 1,
|
||||
},
|
||||
|
||||
currentFilterMode: 'all' as 'all' | 'tractions' | 'carriages',
|
||||
};
|
||||
},
|
||||
|
||||
activated() {
|
||||
const tableWrapperRef = this.$refs['table-wrapper'] as HTMLElement;
|
||||
|
||||
tableWrapperRef.scrollTo({
|
||||
top: this.scrollTop,
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
locoSupportsColdStart,
|
||||
isLocomotive,
|
||||
|
||||
toggleFilter(name: typeof this.currentFilterMode) {
|
||||
this.currentFilterMode = this.currentFilterMode == name ? 'all' : name;
|
||||
},
|
||||
|
||||
toggleSorter(header: IWikiHeader) {
|
||||
if (!header.sortable) return;
|
||||
|
||||
if (header.id == this.currentSorter.id) this.currentSorter.direction *= -1;
|
||||
this.currentSorter.id = header.id;
|
||||
},
|
||||
|
||||
sortTableRows(row1: IWikiRow, row2: IWikiRow) {
|
||||
if (!row1.show) return 0;
|
||||
|
||||
const { id, direction } = this.currentSorter;
|
||||
|
||||
switch (id) {
|
||||
case 'type':
|
||||
case 'constructionType':
|
||||
case 'group':
|
||||
return direction == 1 ? row1.vehicle[id].localeCompare(row2.vehicle[id]) : row2.vehicle[id].localeCompare(row1.vehicle[id]);
|
||||
|
||||
case 'mass':
|
||||
case 'length':
|
||||
case 'maxSpeed':
|
||||
return Math.sign(row1.vehicle[id] - row2.vehicle[id]) * direction;
|
||||
|
||||
case 'cargoCount':
|
||||
return (
|
||||
(!isLocomotive(row1.vehicle) ? Math.sign(row1.vehicle.cargoList.length || -1) : -1) -
|
||||
(!isLocomotive(row2.vehicle) ? (row2.vehicle.cargoList.length || -1) * direction : -1)
|
||||
);
|
||||
|
||||
case 'coldStart':
|
||||
return (locoSupportsColdStart(row1.vehicle.constructionType) > locoSupportsColdStart(row2.vehicle.constructionType) ? 1 : -1) * direction;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return direction == 1 ? row1.vehicle.type.localeCompare(row2.vehicle.type) : row2.vehicle.type.localeCompare(row1.vehicle.type);
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
computedTableData(): IWikiRow[] {
|
||||
return this.store.vehicleDataList
|
||||
.map((vehicle) => ({
|
||||
vehicle,
|
||||
show:
|
||||
new RegExp(`${this.searchedVehicleTypeName.trim()}`, 'i').test(vehicle.type) &&
|
||||
(this.currentFilterMode == 'all' ||
|
||||
(this.currentFilterMode == 'tractions' && isLocomotive(vehicle)) ||
|
||||
(this.currentFilterMode == 'carriages' && !isLocomotive(vehicle))),
|
||||
|
||||
// ((this.filters.tractions && isLocomotive(vehicle)) || (this.filters.carriages && !isLocomotive(vehicle))),
|
||||
}))
|
||||
.sort((a, b) => this.sortTableRows(a, b));
|
||||
},
|
||||
|
||||
visibleHeaders() {
|
||||
const filtersActive = this.currentFilterMode;
|
||||
|
||||
return this.headers.filter((header) => header.for == 'all' || header.for == filtersActive);
|
||||
},
|
||||
|
||||
areTractionVehiclesShown() {
|
||||
return this.currentFilterMode == 'all' || this.currentFilterMode == 'tractions';
|
||||
},
|
||||
|
||||
areCarriagesShown() {
|
||||
return this.currentFilterMode == 'all' || this.currentFilterMode == 'carriages';
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../styles/tab.scss';
|
||||
|
||||
.actions-panel {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5em;
|
||||
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.actions-panel_vehicles {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.actions-panel_search {
|
||||
input {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
overflow: auto;
|
||||
height: 750px;
|
||||
max-height: 95vh;
|
||||
}
|
||||
|
||||
.wiki-list table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
|
||||
thead {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #111;
|
||||
padding: 0.5em;
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
tr {
|
||||
cursor: pointer;
|
||||
background-color: #333;
|
||||
|
||||
&:first-child {
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
&:nth-child(odd) {
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
text-align: center;
|
||||
height: 70px;
|
||||
|
||||
&[data-sponsoronly='true'] {
|
||||
color: salmon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $breakpointMd) {
|
||||
.wiki-list table {
|
||||
th {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $breakpointSm) {
|
||||
.actions-panel {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.actions-panel_vehicles {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.actions-panel_search {
|
||||
display: grid;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,52 +1,52 @@
|
||||
<template>
|
||||
<div
|
||||
class="image-preview"
|
||||
@click="store.vehiclePreviewSrc = ''"
|
||||
@keydown.esc="store.vehiclePreviewSrc = ''"
|
||||
tabindex="0"
|
||||
>
|
||||
<img :src="store.vehiclePreviewSrc" alt="preview" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useStore } from '../../store';
|
||||
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
store: useStore(),
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$el.focus();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.image-preview {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 99;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background: rgba(black, 0.85);
|
||||
cursor: zoom-out;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div
|
||||
class="image-preview"
|
||||
@click="store.vehiclePreviewSrc = ''"
|
||||
@keydown.esc="store.vehiclePreviewSrc = ''"
|
||||
tabindex="0"
|
||||
>
|
||||
<img :src="store.vehiclePreviewSrc" alt="preview" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { useStore } from "../../store";
|
||||
|
||||
export default defineComponent({
|
||||
data() {
|
||||
return {
|
||||
store: useStore(),
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$el.focus();
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.image-preview {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 99;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background: rgba(black, 0.85);
|
||||
cursor: zoom-out;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<div class="stock_thumbnails" ref="thumbnailsRef">
|
||||
<div
|
||||
v-for="(stock, stockIndex) in store.stockList"
|
||||
:key="stockIndex"
|
||||
:data-selected="store.chosenStockListIndex == stockIndex"
|
||||
draggable="true"
|
||||
@dragstart="onDragStart(stockIndex)"
|
||||
@@ -9,7 +10,7 @@
|
||||
@dragover="allowDrop"
|
||||
>
|
||||
<span @click="onListItemClick(stockIndex)" :key="stock.id">
|
||||
<b :class="{ supporter: stock.supportersOnly }">
|
||||
<b :class="{ sponsor: stock.isSponsorsOnly }">
|
||||
{{ stock.type }}
|
||||
</b>
|
||||
|
||||
@@ -52,9 +53,11 @@ watch(
|
||||
if (index < 0) return;
|
||||
|
||||
nextTick(() => {
|
||||
(thumbnailsRef.value as HTMLElement)
|
||||
.querySelector(`div:nth-child(${index + 1})`)
|
||||
?.scrollIntoView({ block: 'nearest', inline: 'start', behavior: 'smooth' });
|
||||
(thumbnailsRef.value as HTMLElement).querySelector(`div:nth-child(${index + 1})`)?.scrollIntoView({
|
||||
block: 'nearest',
|
||||
inline: 'start',
|
||||
behavior: 'smooth',
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -118,8 +121,7 @@ const allowDrop = (e: DragEvent) => {
|
||||
}
|
||||
}
|
||||
|
||||
.supporter {
|
||||
.sponsor {
|
||||
color: salmon;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
},
|
||||
"sameRegions": {
|
||||
"Losowy": [
|
||||
10, 11, 19, 91, 93, 97, 99, 20, 22, 29, 30, 33, 39, 40, 44, 49, 94, 50, 55, 59, 90, 95, 96, 66, 60, 69, 77, 70,
|
||||
79, 88, 80, 89, 92, 98
|
||||
10, 11, 19, 91, 93, 97, 99, 20, 22, 29, 30, 33, 39, 40, 44, 49, 94, 50,
|
||||
55, 59, 90, 95, 96, 66, 60, 69, 77, 70, 79, 88, 80, 89, 92, 98
|
||||
],
|
||||
"Warszawa": [10, 11, 19, 91, 93, 97, 99],
|
||||
"Lublin": [20, 22, 29],
|
||||
@@ -35,4 +35,3 @@
|
||||
"LT": "2:5;3:0-899:3"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,12 @@
|
||||
},
|
||||
"cargo": null
|
||||
},
|
||||
"EP09": {
|
||||
"passenger": {
|
||||
"650": 160
|
||||
},
|
||||
"cargo": null
|
||||
},
|
||||
"ET41": {
|
||||
"passenger": {
|
||||
"700": 125
|
||||
@@ -58,5 +64,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
export const enum EVehicleUseType {
|
||||
LOCO_ELECTRICAL = 'loco-e',
|
||||
LOCO_DIESEL = "loco-s",
|
||||
EMU = "loco-ezt",
|
||||
DMU = "loco-szt",
|
||||
LOCO_ELECTRICAL = "loco-e",
|
||||
LOCO_DIESEL = "loco-s",
|
||||
EMU = "loco-ezt",
|
||||
DMU = "loco-szt",
|
||||
|
||||
CAR_PASSENGER = "car-passenger",
|
||||
CAR_CARGO = "car-cargo"
|
||||
}
|
||||
CAR_PASSENGER = "car-passenger",
|
||||
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;
|
||||
+35
-33
@@ -1,33 +1,35 @@
|
||||
import localePL from './locales/pl.json';
|
||||
import localeEN from './locales/en.json';
|
||||
import { createI18n } from 'vue-i18n';
|
||||
import axios from 'axios';
|
||||
|
||||
type LocaleMessageSchema = typeof localePL;
|
||||
type LocaleKey = 'en' | 'pl';
|
||||
|
||||
const locales: { [key in LocaleKey]: LocaleMessageSchema } = {
|
||||
en: localeEN,
|
||||
pl: localePL,
|
||||
};
|
||||
|
||||
const locale = window.localStorage.getItem('locale') || (/^pl\b/.test(navigator.language) ? 'pl' : 'en');
|
||||
|
||||
const i18n = createI18n<[LocaleMessageSchema], 'en' | 'pl'>({
|
||||
locale,
|
||||
fallbackLocale: 'pl',
|
||||
legacy: false,
|
||||
globalInjection: true,
|
||||
messages: locales,
|
||||
});
|
||||
|
||||
async function fetchBackendTranslations() {
|
||||
const localeData = (await axios.get(`https://spythere.github.io/api/td2/data/locales.json`)).data;
|
||||
|
||||
i18n.global.mergeLocaleMessage('pl', localeData.pl);
|
||||
i18n.global.mergeLocaleMessage('en', localeData.en);
|
||||
}
|
||||
|
||||
fetchBackendTranslations();
|
||||
|
||||
export default i18n;
|
||||
import localePL from "./locales/pl.json";
|
||||
import localeEN from "./locales/en.json";
|
||||
import { createI18n } from "vue-i18n";
|
||||
import http from "./http";
|
||||
|
||||
type LocaleMessageSchema = typeof localePL;
|
||||
type LocaleKey = "en" | "pl";
|
||||
|
||||
const locales: { [key in LocaleKey]: LocaleMessageSchema } = {
|
||||
en: localeEN,
|
||||
pl: localePL,
|
||||
};
|
||||
|
||||
const locale =
|
||||
window.localStorage.getItem("locale") ||
|
||||
(/^pl\b/.test(navigator.language) ? "pl" : "en");
|
||||
|
||||
const i18n = createI18n<[LocaleMessageSchema], "en" | "pl">({
|
||||
locale,
|
||||
fallbackLocale: "pl",
|
||||
legacy: false,
|
||||
globalInjection: true,
|
||||
messages: locales,
|
||||
});
|
||||
|
||||
async function fetchBackendTranslations() {
|
||||
const localeData = (await http.get(`td2/data/locales.json`)).data;
|
||||
|
||||
i18n.global.mergeLocaleMessage("pl", localeData.pl);
|
||||
i18n.global.mergeLocaleMessage("en", localeData.en);
|
||||
}
|
||||
|
||||
fetchBackendTranslations();
|
||||
|
||||
export default i18n;
|
||||
|
||||
+152
-150
@@ -1,150 +1,152 @@
|
||||
{
|
||||
"app": {
|
||||
"title": "ROLLING STOCK EDITOR"
|
||||
},
|
||||
"footer": {
|
||||
"disclaimer": "This site has only an informational intent. The author does not carry any responsibility for creating trains against {tos}!",
|
||||
"tos": "Train Driver 2 simulator rules",
|
||||
"tos-href": "https://docs.google.com/document/d/1UAAPUtN0d_RoS4RgOzEzllJZJhA0VcizzCzKW4QylbY/edit#heading=h.1ldcvhomwjp9",
|
||||
"version-check": "Site is complete for version {version} of Train Driver 2 simulator"
|
||||
},
|
||||
"inputs": {
|
||||
"title": "CHOOSE A VEHICLE",
|
||||
"input-vehicle": "Choose a traction unit",
|
||||
"input-carwagon": "Choose a carriage",
|
||||
"cargo-title": "Cargo (only selected freight cars)",
|
||||
"no-cargo-available": "no cargo available",
|
||||
"cargo-empty": "empty",
|
||||
"loco-e": "ELECTR.",
|
||||
"loco-s": "DIESEL",
|
||||
"loco-ezt": "EMU",
|
||||
"loco-szt": "DMU",
|
||||
"car-passenger": "PASSENGER",
|
||||
"car-cargo": "FREIGHT",
|
||||
"action-add": "ADD NEW",
|
||||
"action-swap": "SWAP WITH",
|
||||
"real-stock": "POLISH TRAIN COMPOSITIONS"
|
||||
},
|
||||
"preview": {
|
||||
"title": "RAILWAY VEHICLE PREVIEW",
|
||||
"loading": "IMAGE LOADING...",
|
||||
"desc": "Choose a railway vehicle above to see its preview",
|
||||
"sponsor-only": "* SPONSORS ONLY",
|
||||
"loco-e": "ELECTRIC LOCO",
|
||||
"loco-s": "DIESEL LOCO",
|
||||
"loco-ezt": "ELECTRIC M.U.",
|
||||
"loco-szt": "DIESEL M.U.",
|
||||
"car-passenger": "PASSENGER CARRIAGE",
|
||||
"car-cargo": "FREIGHT CAR",
|
||||
"cabin": "Cabin type:",
|
||||
"construction": "Construction type:"
|
||||
},
|
||||
"topbar": {
|
||||
"stock-list": "STOCK",
|
||||
"wiki-list": "VEHICLES",
|
||||
"number-generator": "NUMBER GEN.",
|
||||
"stock-generator": "STOCK GEN."
|
||||
},
|
||||
"stocklist": {
|
||||
"title": "STOCK EDITOR",
|
||||
"alert-copied": "The rolling stock has been copied to your clipboard!",
|
||||
"alert-empty": "Lista pojazdów jest pusta!",
|
||||
"prompt-file": "Name a file and download it to the Presets folder (Documents/TTSK/TrainDriver2):",
|
||||
"vehicle-no": "VEHICLE NO.",
|
||||
"no-vehicle-chosen": "NO VEHICLE CHOSEN",
|
||||
"action-move-up": "MOVE UP",
|
||||
"action-move-down": "MOVE DOWN",
|
||||
"action-remove": "REMOVE",
|
||||
"action-upload": "LOAD",
|
||||
"action-download": "DOWNLOAD",
|
||||
"action-copy": "COPY",
|
||||
"action-reset": "RESET",
|
||||
"action-shuffle": "SHUFFLE",
|
||||
"mass": "Mass",
|
||||
"mass-accepted": "accepted",
|
||||
"length": "Length",
|
||||
"vmax": "vMax",
|
||||
"coldstart-info": "Cold start heading locomotive (only locos 303E & 203E type)",
|
||||
"list-empty": "Stock list is empty!",
|
||||
"warning-not-suitable": "EP07 & EP08 type locomotives are designed for passenger traffic only!",
|
||||
"warning-passenger-too-long": "Maximum length of a passenger train may not be greater than 350m!",
|
||||
"warning-freight-too-long": "Maximum length of a freight train may not be greater than 650m!",
|
||||
"warning-too-many-locos": "This train has too many traction units!",
|
||||
"warning-too-heavy": "This train is too heavy! Check {href}",
|
||||
"acceptable-mass-docs": "acceptable rolling stock masses (PL)"
|
||||
},
|
||||
"stockgen": {
|
||||
"title": "FREIGHT TRAIN GENERATOR",
|
||||
"properties-title": "ROLLING STOCK PROPERTIES",
|
||||
"properties-desc": "⇐ Add a locomotive in the first place of the stock list to include it in a drawing!",
|
||||
"input-mass": "Max. mass (t)",
|
||||
"input-length": "Max. length (m)",
|
||||
"input-carcount": "Max. car count",
|
||||
"cargo-title": "CARGO",
|
||||
"cargo-desc": "Choose cargo you want to fill available cars with:",
|
||||
"chosen-title": "CARS WITH CHOSEN CARGO",
|
||||
"chosen-empty-warning": "Choose at least one cargo type to see available cars!",
|
||||
"chosen-warning": "Cars containing chosen cargo are shown below. Hover over a type to see a preview of the car. Click it to include/exclude it from a drawing (only highlighted types will be included).",
|
||||
"action-generate": "GENERATE",
|
||||
"action-generate-empty": "GENERATE EMPTY",
|
||||
"action-reset": "RESET CARGO"
|
||||
},
|
||||
"numgen": {
|
||||
"title": "TRAIN NUMBER GENERATOR",
|
||||
"alert": "The number has been copied to your clipboard!",
|
||||
"start-region": "Beginning construction region",
|
||||
"end-region": "Terminating construction region",
|
||||
"train-category": "Train category",
|
||||
"number-info": "Generated train number:",
|
||||
"warning": "Choose category and (optionally) construction regions",
|
||||
"td2-wiki": "> Polish rules of train numbering (TD2 wiki)",
|
||||
"td2-wiki-link": "https://wiki.td2.info.pl/index.php?title=Zasady_numeracji_poci%C4%85g%C3%B3w/en",
|
||||
"action-random-region": "DRAW REGIONS",
|
||||
"action-random-number": "DRAW A NUMBER",
|
||||
"categories": {
|
||||
"EI": "domestic express (EI)",
|
||||
"MP/RP": "(inter)voivodeship bullet (MP/RP)",
|
||||
"RO": "regional passenger (RO)",
|
||||
"PW": "empty passenger (PW)",
|
||||
"TM": "mass transport freight (TM)",
|
||||
"TK": "non-mass transport freight (TK)",
|
||||
"LT": "locomotive alone (LT)"
|
||||
},
|
||||
"rules": {
|
||||
"EI": "4 digits - ends within the range of 00-99",
|
||||
"MP/RP": "5 digits - ends within the range of 050-169",
|
||||
"RO": "5 digits - ends within the range of 200-999",
|
||||
"PW": "6 digits - '6' on the 3rd place; ends within the range of 000-899",
|
||||
"TM": "6 digits - '4' on the 3rd place; ends within the range of 000-899",
|
||||
"TK": "6 digits - '3' on the 3rd place; ends within the range of 000-899",
|
||||
"LT": "6 digits - '5' on the 3rd place; ends within the range of 000-899"
|
||||
}
|
||||
},
|
||||
"wiki": {
|
||||
"title": "LIST OF AVAILABLE VEHICLES",
|
||||
"action-vehicles": "TRACTION UNITS",
|
||||
"action-carriages": "CARRIAGES",
|
||||
"search": "Search for a vehicle...",
|
||||
"header": {
|
||||
"image": "Image",
|
||||
"type": "Name",
|
||||
"power": "Type",
|
||||
"constructionType": "Construction",
|
||||
"coldStart": "Cold start",
|
||||
"length": "Length",
|
||||
"mass": "Mass",
|
||||
"maxSpeed": "Speed",
|
||||
"cargoCount": "Cargo count"
|
||||
},
|
||||
"loco-ezt": "EMU",
|
||||
"loco-szt": "DMU",
|
||||
"loco-s": "Diesel locomotive",
|
||||
"loco-e": "Electric locomotive"
|
||||
},
|
||||
"realstock": {
|
||||
"title": "POLISH TRAIN COMPOSITIONS by",
|
||||
"search-name": "Search by name",
|
||||
"search-stock": "Search by vehicles",
|
||||
"action-reset": "RESET"
|
||||
}
|
||||
}
|
||||
{
|
||||
"app": {
|
||||
"title": "ROLLING STOCK EDITOR"
|
||||
},
|
||||
"footer": {
|
||||
"disclaimer": "This site has only an informational intent. The author does not carry any responsibility for creating trains against {tos}!",
|
||||
"tos": "Train Driver 2 simulator rules",
|
||||
"tos-href": "https://docs.google.com/document/d/1UAAPUtN0d_RoS4RgOzEzllJZJhA0VcizzCzKW4QylbY/edit#heading=h.1ldcvhomwjp9",
|
||||
"version-check": "Site is complete for version {version} of Train Driver 2 simulator"
|
||||
},
|
||||
"inputs": {
|
||||
"title": "CHOOSE A VEHICLE",
|
||||
"input-vehicle": "Choose a traction unit",
|
||||
"input-carwagon": "Choose a carriage",
|
||||
"cargo-title": "Cargo (only selected freight cars)",
|
||||
"no-cargo-available": "no cargo available",
|
||||
"cargo-empty": "empty",
|
||||
"loco-e": "ELECTR.",
|
||||
"loco-s": "DIESEL",
|
||||
"loco-ezt": "EMU",
|
||||
"loco-szt": "DMU",
|
||||
"car-passenger": "PASSENGER",
|
||||
"car-cargo": "FREIGHT",
|
||||
"action-add": "ADD NEW",
|
||||
"action-swap": "SWAP WITH",
|
||||
"real-stock": "POLISH TRAIN COMPOSITIONS"
|
||||
},
|
||||
"preview": {
|
||||
"title": "RAILWAY VEHICLE PREVIEW",
|
||||
"loading": "IMAGE LOADING...",
|
||||
"desc": "Choose a railway vehicle above to see its preview",
|
||||
"sponsor-only": "* SPONSORS ONLY UNTIL {0}",
|
||||
"loco-e": "ELECTRIC LOCO",
|
||||
"loco-s": "DIESEL LOCO",
|
||||
"loco-ezt": "ELECTRIC M.U.",
|
||||
"loco-szt": "DIESEL M.U.",
|
||||
"car-passenger": "PASSENGER CARRIAGE",
|
||||
"car-cargo": "FREIGHT CARRIAGE",
|
||||
"cabin": "Cabin type:",
|
||||
"construction": "Construction type:"
|
||||
},
|
||||
"topbar": {
|
||||
"stock-list": "STOCK",
|
||||
"wiki-list": "VEHICLES",
|
||||
"number-generator": "NUMBER GEN.",
|
||||
"stock-generator": "STOCK GEN."
|
||||
},
|
||||
"stocklist": {
|
||||
"title": "STOCK EDITOR",
|
||||
"alert-copied": "The rolling stock has been copied to your clipboard!",
|
||||
"alert-empty": "Lista pojazdów jest pusta!",
|
||||
"prompt-file": "Name a file and download it to the Presets folder (Documents/TTSK/TrainDriver2):",
|
||||
"vehicle-no": "VEHICLE NO.",
|
||||
"no-vehicle-chosen": "NO VEHICLE CHOSEN",
|
||||
"action-move-up": "MOVE UP",
|
||||
"action-move-down": "MOVE DOWN",
|
||||
"action-remove": "REMOVE",
|
||||
"action-upload": "LOAD",
|
||||
"action-download": "DOWNLOAD",
|
||||
"action-copy": "COPY",
|
||||
"action-reset": "RESET",
|
||||
"action-shuffle": "SHUFFLE",
|
||||
"mass": "Mass",
|
||||
"mass-accepted": "accepted",
|
||||
"length": "Length",
|
||||
"vmax": "vMax",
|
||||
"coldstart-info": "Cold start heading locomotive (only locos 303E & 203E type)",
|
||||
"list-empty": "Stock list is empty!",
|
||||
"warning-not-suitable": "EP07 & EP08 type locomotives are designed for passenger traffic only!",
|
||||
"warning-passenger-too-long": "Maximum length of a passenger train may not be greater than 350m!",
|
||||
"warning-freight-too-long": "Maximum length of a freight train may not be greater than 650m!",
|
||||
"warning-too-many-locos": "This train has too many traction units!",
|
||||
"warning-too-heavy": "This train is too heavy! Check {href}",
|
||||
"acceptable-mass-docs": "acceptable rolling stock masses (PL)"
|
||||
},
|
||||
"stockgen": {
|
||||
"title": "FREIGHT TRAIN GENERATOR",
|
||||
"properties-title": "ROLLING STOCK PROPERTIES",
|
||||
"properties-desc": "⇐ Add a locomotive in the first place of the stock list to include it in a drawing!",
|
||||
"input-mass": "Max. mass (t)",
|
||||
"input-length": "Max. length (m)",
|
||||
"input-carcount": "Max. car count",
|
||||
"cargo-title": "CARGO",
|
||||
"cargo-desc": "Choose cargo you want to fill available cars with:",
|
||||
"chosen-title": "CARS WITH CHOSEN CARGO",
|
||||
"chosen-empty-warning": "Choose at least one cargo type to see available cars!",
|
||||
"chosen-warning": "Cars containing chosen cargo are shown below. Hover over a type to see a preview of the car. Click it to include/exclude it from a drawing (only highlighted types will be included).",
|
||||
"action-generate": "GENERATE",
|
||||
"action-generate-empty": "GENERATE EMPTY",
|
||||
"action-reset": "RESET CARGO"
|
||||
},
|
||||
"numgen": {
|
||||
"title": "TRAIN NUMBER GENERATOR",
|
||||
"alert": "The number has been copied to your clipboard!",
|
||||
"start-region": "Beginning construction region",
|
||||
"end-region": "Terminating construction region",
|
||||
"train-category": "Train category",
|
||||
"number-info": "Generated train number:",
|
||||
"warning": "Choose category and (optionally) construction regions",
|
||||
"td2-wiki": "> Polish rules of train numbering (TD2 wiki)",
|
||||
"td2-wiki-link": "https://wiki.td2.info.pl/index.php?title=Zasady_numeracji_poci%C4%85g%C3%B3w/en",
|
||||
"action-random-region": "DRAW REGIONS",
|
||||
"action-random-number": "DRAW A NUMBER",
|
||||
"categories": {
|
||||
"EI": "domestic express (EI)",
|
||||
"MP/RP": "(inter)voivodeship bullet (MP/RP)",
|
||||
"RO": "regional passenger (RO)",
|
||||
"PW": "empty passenger (PW)",
|
||||
"TM": "mass transport freight (TM)",
|
||||
"TK": "non-mass transport freight (TK)",
|
||||
"LT": "locomotive alone (LT)"
|
||||
},
|
||||
"rules": {
|
||||
"EI": "4 digits - ends within the range of 00-99",
|
||||
"MP/RP": "5 digits - ends within the range of 050-169",
|
||||
"RO": "5 digits - ends within the range of 200-999",
|
||||
"PW": "6 digits - '6' on the 3rd place; ends within the range of 000-899",
|
||||
"TM": "6 digits - '4' on the 3rd place; ends within the range of 000-899",
|
||||
"TK": "6 digits - '3' on the 3rd place; ends within the range of 000-899",
|
||||
"LT": "6 digits - '5' on the 3rd place; ends within the range of 000-899"
|
||||
}
|
||||
},
|
||||
"wiki": {
|
||||
"title": "LIST OF AVAILABLE VEHICLES",
|
||||
"action-vehicles": "TRACTION UNITS",
|
||||
"action-carriages": "CARRIAGES",
|
||||
"search": "Search for a vehicle...",
|
||||
"header": {
|
||||
"image": "Image",
|
||||
"type": "Name",
|
||||
"group": "Type group",
|
||||
"constructionType": "Construction",
|
||||
"coldStart": "Cold start",
|
||||
"length": "Length",
|
||||
"mass": "Mass",
|
||||
"maxSpeed": "Speed",
|
||||
"cargoCount": "Cargo count"
|
||||
},
|
||||
"loco-ezt": "EMU",
|
||||
"loco-szt": "DMU",
|
||||
"loco-s": "Diesel locomotive",
|
||||
"loco-e": "Electric locomotive",
|
||||
"car-passenger": "Passenger carriage",
|
||||
"car-cargo": "Frieght carriage"
|
||||
},
|
||||
"realstock": {
|
||||
"title": "POLISH TRAIN COMPOSITIONS by",
|
||||
"search-name": "Search by name",
|
||||
"search-stock": "Search by vehicles",
|
||||
"action-reset": "RESET"
|
||||
}
|
||||
}
|
||||
|
||||
+152
-150
@@ -1,150 +1,152 @@
|
||||
{
|
||||
"app": {
|
||||
"title": "EDYTOR SKŁADÓW ONLINE"
|
||||
},
|
||||
"footer": {
|
||||
"disclaimer": "Ta strona ma charakter informacyjny. Autor nie ponosi odpowiedzialności za tworzenie pociągów niezgodnych z {tos}!",
|
||||
"tos": "regulaminem symulatora Train Driver 2",
|
||||
"tos-href": "https://docs.google.com/document/d/1UAAPUtN0d_RoS4RgOzEzllJZJhA0VcizzCzKW4QylbY/edit",
|
||||
"version-check": "Strona jest kompletna dla wersji {version} symulatora TD2"
|
||||
},
|
||||
"inputs": {
|
||||
"title": "WYBIERZ POJAZD SZYNOWY",
|
||||
"input-vehicle": "Wybierz pojazd trakcyjny",
|
||||
"input-carwagon": "Wybierz wagon",
|
||||
"cargo-title": "Ładunek (tylko wybrane towarowe)",
|
||||
"no-cargo-available": "brak dostępnych ładunków",
|
||||
"cargo-empty": "próżny",
|
||||
"loco-e": "ELEKTR.",
|
||||
"loco-s": "SPAL.",
|
||||
"loco-ezt": "EZT",
|
||||
"loco-szt": "SZT",
|
||||
"car-passenger": "PASAŻERSKIE",
|
||||
"car-cargo": "TOWAROWE",
|
||||
"action-add": "DODAJ NOWY",
|
||||
"action-swap": "ZAMIEŃ ZA",
|
||||
"real-stock": "REALNE ZESTAWIENIA"
|
||||
},
|
||||
"preview": {
|
||||
"title": "PODGLĄD WYBRANEGO POJAZDU",
|
||||
"loading": "ŁADOWANIE OBRAZU...",
|
||||
"desc": "Wybierz pojazd lub wagon, aby zobaczyć jego podgląd powyżej",
|
||||
"sponsor-only": "* TYLKO DLA SPONSORÓW",
|
||||
"loco-e": "ELEKTROWÓZ",
|
||||
"loco-s": "SPALINOWÓZ",
|
||||
"loco-ezt": "EZT",
|
||||
"loco-szt": "SZT",
|
||||
"car-passenger": "WAGON PASAŻERSKI",
|
||||
"car-cargo": "WAGON TOWAROWY",
|
||||
"cabin": "Typ kabiny:",
|
||||
"construction": "Typ konstrukcji:"
|
||||
},
|
||||
"topbar": {
|
||||
"stock-list": "SKŁAD",
|
||||
"wiki-list": "POJAZDY",
|
||||
"number-generator": "GNR NUMERU",
|
||||
"stock-generator": "GNR SKŁADU"
|
||||
},
|
||||
"stocklist": {
|
||||
"title": "EDYTOR SKŁADU",
|
||||
"alert-copied": "Skład został skopiowany do twojego schowka!",
|
||||
"alert-empty": "Lista pojazdów jest pusta!",
|
||||
"prompt-file": "Nazwij plik, a następnie pobierz do folderu Presets (Dokumenty/TTSK/TrainDriver2):",
|
||||
"vehicle-no": "POJAZD NR",
|
||||
"no-vehicle-chosen": "NIE WYBRANO POJAZDU",
|
||||
"action-move-up": "PRZENIEŚ WYŻEJ",
|
||||
"action-move-down": "PRZENIEŚ NIŻEJ",
|
||||
"action-remove": "USUŃ",
|
||||
"action-upload": "WCZYTAJ",
|
||||
"action-download": "POBIERZ",
|
||||
"action-copy": "SKOPIUJ",
|
||||
"action-reset": "ZRESETUJ",
|
||||
"action-shuffle": "PRZETASUJ",
|
||||
"mass": "Masa",
|
||||
"mass-accepted": "dopuszczalna",
|
||||
"length": "Długość",
|
||||
"vmax": "vMax",
|
||||
"coldstart-info": "Zimny start lokomotywy czołowej (tylko elektrowozy typów 303E i 203E)",
|
||||
"list-empty": "Lista pojazdów jest pusta!",
|
||||
"warning-not-suitable": "Lokomotywy EP07 i EP08 są przeznaczone jedynie do ruchu pasażerskiego!",
|
||||
"warning-passenger-too-long": "Maksymalna długość składów pasażerskich nie może przekraczać 350m!",
|
||||
"warning-freight-too-long": "Maksymalna długość składów innych niż pasażerskie nie może przekraczać 650m!",
|
||||
"warning-too-many-locos": "Ten skład posiada za dużo pojazdów trakcyjnych!",
|
||||
"warning-too-heavy": "Ten skład jest za ciężki! Sprawdź {href}",
|
||||
"acceptable-mass-docs": "dopuszczalne masy składów"
|
||||
},
|
||||
"stockgen": {
|
||||
"title": "GENERATOR SKŁADU TOWAROWEGO",
|
||||
"properties-title": "WŁAŚCIWOŚCI SKŁADU",
|
||||
"properties-desc": "⇐ Dodaj lokomotywę na pierwsze miejsce listy, aby uwzględnić ją przy losowaniu składu!",
|
||||
"input-mass": "Maksymalna masa (t)",
|
||||
"input-length": "Maks. długość (m)",
|
||||
"input-carcount": "Maks. liczba wagonów",
|
||||
"cargo-title": "ŁADUNEK",
|
||||
"cargo-desc": "Wybierz ładunki, którymi chcesz wypełnić dostępne wagony:",
|
||||
"chosen-title": "WAGONY Z WYBRANYMI ŁADUNKAMI",
|
||||
"chosen-empty-warning": "Wybierz co najmniej jeden ładunek, aby zobaczyć wagony, które go posiadają!",
|
||||
"chosen-warning": "Wagony posiadające wybrane ładunki. Najedź na nazwę, aby zobaczyć podgląd wagonu. Kliknij, aby wyłączyć z losowania (tylko podświetlone nazwy będą uwzględnione).",
|
||||
"action-generate": "WYGENERUJ",
|
||||
"action-generate-empty": "WYGENERUJ PRÓŻNE WAGONY",
|
||||
"action-reset": "ZRESETUJ ŁADUNKI"
|
||||
},
|
||||
"numgen": {
|
||||
"title": "GENERATOR NUMERU POCIĄGU",
|
||||
"alert": "Numer został skopiowany do twojego schowka!",
|
||||
"start-region": "Początkowy obszar konstrukcyjny",
|
||||
"end-region": "Końcowy obszar konstrukcyjny",
|
||||
"train-category": "Kategoria pociągu",
|
||||
"number-info": "Wygenerowany numer pociągu:",
|
||||
"warning": "Wybierz kategorię oraz (opcjonalnie) obszary konstrukcyjne",
|
||||
"td2-wiki": "> Szczegółowe zasady numeracji (wikipedia TD2)",
|
||||
"td2-wiki-link": "https://wiki.td2.info.pl/index.php?title=Zasady_numeracji_poci%C4%85g%C3%B3w",
|
||||
"action-random-region": "LOSUJ OBSZARY",
|
||||
"action-random-number": "LOSUJ NUMER",
|
||||
"categories": {
|
||||
"EI": "ekspres krajowy (EI)",
|
||||
"MP/RP": "(między)wojewódzki pośpieszny (MP/RP)",
|
||||
"RO": "wojewódzki osobowy (RO)",
|
||||
"PW": "próżny \"służbowy\" (PW)",
|
||||
"TM": "towarowy do przewozów masowych (TM)",
|
||||
"TK": "towarowy do obsługi stacji (TK)",
|
||||
"LT": "lokomotywa luzem (LT)"
|
||||
},
|
||||
"rules": {
|
||||
"EI": "4 cyfry - końcówka z przedziału 00-99",
|
||||
"MP/RP": "5 cyfr - końcówka z przedziału 050-169",
|
||||
"RO": "5 cyfr - końcówka z przedziału 200-999",
|
||||
"PW": "6 cyfr - '6' na 3. miejscu; końcówka z przedziału 000-899",
|
||||
"TM": "6 cyfr - '4' na 3. miejscu; końcówka z przedziału 000-899",
|
||||
"TK": "6 cyfr - '3' na 3. miejscu; końcówka z przedziału 000-899",
|
||||
"LT": "6 cyfr - '5' na 3. miejscu; końcówka z przedziału 000-899"
|
||||
}
|
||||
},
|
||||
"wiki": {
|
||||
"title": "LISTA DOSTĘPNYCH POJAZDÓW",
|
||||
"action-vehicles": "POJ. TRAKCYJNE",
|
||||
"action-carriages": "WAGONY",
|
||||
"search": "Wyszukaj pojazd...",
|
||||
"header": {
|
||||
"image": "Zdjęcie",
|
||||
"type": "Nazwa",
|
||||
"power": "Rodzaj",
|
||||
"constructionType": "Konstrukcja",
|
||||
"coldStart": "Zimny start",
|
||||
"length": "Długość",
|
||||
"mass": "Masa",
|
||||
"maxSpeed": "Prędkość",
|
||||
"cargoCount": "Ładunki"
|
||||
},
|
||||
"loco-ezt": "EZT",
|
||||
"loco-szt": "SZT",
|
||||
"loco-s": "Spalinowóz",
|
||||
"loco-e": "Elektrowóz"
|
||||
},
|
||||
"realstock": {
|
||||
"title": "ZESTAWIENIA REALNE by",
|
||||
"search-name": "Szukaj po nazwie",
|
||||
"search-stock": "Szukaj po pojazdach",
|
||||
"action-reset": "RESETUJ"
|
||||
}
|
||||
}
|
||||
{
|
||||
"app": {
|
||||
"title": "EDYTOR SKŁADÓW ONLINE"
|
||||
},
|
||||
"footer": {
|
||||
"disclaimer": "Ta strona ma charakter informacyjny. Autor nie ponosi odpowiedzialności za tworzenie pociągów niezgodnych z {tos}!",
|
||||
"tos": "regulaminem symulatora Train Driver 2",
|
||||
"tos-href": "https://docs.google.com/document/d/1UAAPUtN0d_RoS4RgOzEzllJZJhA0VcizzCzKW4QylbY/edit",
|
||||
"version-check": "Strona jest kompletna dla wersji {version} symulatora TD2"
|
||||
},
|
||||
"inputs": {
|
||||
"title": "WYBIERZ POJAZD SZYNOWY",
|
||||
"input-vehicle": "Wybierz pojazd trakcyjny",
|
||||
"input-carwagon": "Wybierz wagon",
|
||||
"cargo-title": "Ładunek (tylko wybrane towarowe)",
|
||||
"no-cargo-available": "brak dostępnych ładunków",
|
||||
"cargo-empty": "próżny",
|
||||
"loco-e": "ELEKTR.",
|
||||
"loco-s": "SPAL.",
|
||||
"loco-ezt": "EZT",
|
||||
"loco-szt": "SZT",
|
||||
"car-passenger": "PASAŻERSKIE",
|
||||
"car-cargo": "TOWAROWE",
|
||||
"action-add": "DODAJ NOWY",
|
||||
"action-swap": "ZAMIEŃ ZA",
|
||||
"real-stock": "REALNE ZESTAWIENIA"
|
||||
},
|
||||
"preview": {
|
||||
"title": "PODGLĄD WYBRANEGO POJAZDU",
|
||||
"loading": "ŁADOWANIE OBRAZU...",
|
||||
"desc": "Wybierz pojazd lub wagon, aby zobaczyć jego podgląd powyżej",
|
||||
"sponsor-only": "* TYLKO DLA SPONSORÓW DO {0}",
|
||||
"loco-e": "ELEKTROWÓZ",
|
||||
"loco-s": "SPALINOWÓZ",
|
||||
"loco-ezt": "EZT",
|
||||
"loco-szt": "SZT",
|
||||
"car-passenger": "WAGON PASAŻERSKI",
|
||||
"car-cargo": "WAGON TOWAROWY",
|
||||
"cabin": "Typ kabiny:",
|
||||
"construction": "Typ konstrukcji:"
|
||||
},
|
||||
"topbar": {
|
||||
"stock-list": "SKŁAD",
|
||||
"wiki-list": "POJAZDY",
|
||||
"number-generator": "GNR NUMERU",
|
||||
"stock-generator": "GNR SKŁADU"
|
||||
},
|
||||
"stocklist": {
|
||||
"title": "EDYTOR SKŁADU",
|
||||
"alert-copied": "Skład został skopiowany do twojego schowka!",
|
||||
"alert-empty": "Lista pojazdów jest pusta!",
|
||||
"prompt-file": "Nazwij plik, a następnie pobierz do folderu Presets (Dokumenty/TTSK/TrainDriver2):",
|
||||
"vehicle-no": "POJAZD NR",
|
||||
"no-vehicle-chosen": "NIE WYBRANO POJAZDU",
|
||||
"action-move-up": "PRZENIEŚ WYŻEJ",
|
||||
"action-move-down": "PRZENIEŚ NIŻEJ",
|
||||
"action-remove": "USUŃ",
|
||||
"action-upload": "WCZYTAJ",
|
||||
"action-download": "POBIERZ",
|
||||
"action-copy": "SKOPIUJ",
|
||||
"action-reset": "ZRESETUJ",
|
||||
"action-shuffle": "PRZETASUJ",
|
||||
"mass": "Masa",
|
||||
"mass-accepted": "dopuszczalna",
|
||||
"length": "Długość",
|
||||
"vmax": "vMax",
|
||||
"coldstart-info": "Zimny start lokomotywy czołowej (tylko elektrowozy typów 303E i 203E)",
|
||||
"list-empty": "Lista pojazdów jest pusta!",
|
||||
"warning-not-suitable": "Lokomotywy EP07 i EP08 są przeznaczone jedynie do ruchu pasażerskiego!",
|
||||
"warning-passenger-too-long": "Maksymalna długość składów pasażerskich nie może przekraczać 350m!",
|
||||
"warning-freight-too-long": "Maksymalna długość składów innych niż pasażerskie nie może przekraczać 650m!",
|
||||
"warning-too-many-locos": "Ten skład posiada za dużo pojazdów trakcyjnych!",
|
||||
"warning-too-heavy": "Ten skład jest za ciężki! Sprawdź {href}",
|
||||
"acceptable-mass-docs": "dopuszczalne masy składów"
|
||||
},
|
||||
"stockgen": {
|
||||
"title": "GENERATOR SKŁADU TOWAROWEGO",
|
||||
"properties-title": "WŁAŚCIWOŚCI SKŁADU",
|
||||
"properties-desc": "⇐ Dodaj lokomotywę na pierwsze miejsce listy, aby uwzględnić ją przy losowaniu składu!",
|
||||
"input-mass": "Maksymalna masa (t)",
|
||||
"input-length": "Maks. długość (m)",
|
||||
"input-carcount": "Maks. liczba wagonów",
|
||||
"cargo-title": "ŁADUNEK",
|
||||
"cargo-desc": "Wybierz ładunki, którymi chcesz wypełnić dostępne wagony:",
|
||||
"chosen-title": "WAGONY Z WYBRANYMI ŁADUNKAMI",
|
||||
"chosen-empty-warning": "Wybierz co najmniej jeden ładunek, aby zobaczyć wagony, które go posiadają!",
|
||||
"chosen-warning": "Wagony posiadające wybrane ładunki. Najedź na nazwę, aby zobaczyć podgląd wagonu. Kliknij, aby wyłączyć z losowania (tylko podświetlone nazwy będą uwzględnione).",
|
||||
"action-generate": "WYGENERUJ",
|
||||
"action-generate-empty": "WYGENERUJ PRÓŻNE WAGONY",
|
||||
"action-reset": "ZRESETUJ ŁADUNKI"
|
||||
},
|
||||
"numgen": {
|
||||
"title": "GENERATOR NUMERU POCIĄGU",
|
||||
"alert": "Numer został skopiowany do twojego schowka!",
|
||||
"start-region": "Początkowy obszar konstrukcyjny",
|
||||
"end-region": "Końcowy obszar konstrukcyjny",
|
||||
"train-category": "Kategoria pociągu",
|
||||
"number-info": "Wygenerowany numer pociągu:",
|
||||
"warning": "Wybierz kategorię oraz (opcjonalnie) obszary konstrukcyjne",
|
||||
"td2-wiki": "> Szczegółowe zasady numeracji (wikipedia TD2)",
|
||||
"td2-wiki-link": "https://wiki.td2.info.pl/index.php?title=Zasady_numeracji_poci%C4%85g%C3%B3w",
|
||||
"action-random-region": "LOSUJ OBSZARY",
|
||||
"action-random-number": "LOSUJ NUMER",
|
||||
"categories": {
|
||||
"EI": "ekspres krajowy (EI)",
|
||||
"MP/RP": "(między)wojewódzki pośpieszny (MP/RP)",
|
||||
"RO": "wojewódzki osobowy (RO)",
|
||||
"PW": "próżny \"służbowy\" (PW)",
|
||||
"TM": "towarowy do przewozów masowych (TM)",
|
||||
"TK": "towarowy do obsługi stacji (TK)",
|
||||
"LT": "lokomotywa luzem (LT)"
|
||||
},
|
||||
"rules": {
|
||||
"EI": "4 cyfry - końcówka z przedziału 00-99",
|
||||
"MP/RP": "5 cyfr - końcówka z przedziału 050-169",
|
||||
"RO": "5 cyfr - końcówka z przedziału 200-999",
|
||||
"PW": "6 cyfr - '6' na 3. miejscu; końcówka z przedziału 000-899",
|
||||
"TM": "6 cyfr - '4' na 3. miejscu; końcówka z przedziału 000-899",
|
||||
"TK": "6 cyfr - '3' na 3. miejscu; końcówka z przedziału 000-899",
|
||||
"LT": "6 cyfr - '5' na 3. miejscu; końcówka z przedziału 000-899"
|
||||
}
|
||||
},
|
||||
"wiki": {
|
||||
"title": "LISTA DOSTĘPNYCH POJAZDÓW",
|
||||
"action-vehicles": "POJ. TRAKCYJNE",
|
||||
"action-carriages": "WAGONY",
|
||||
"search": "Wyszukaj pojazd...",
|
||||
"header": {
|
||||
"image": "Zdjęcie",
|
||||
"type": "Nazwa",
|
||||
"group": "Rodzaj",
|
||||
"constructionType": "Konstrukcja",
|
||||
"coldStart": "Zimny start",
|
||||
"length": "Długość",
|
||||
"mass": "Masa",
|
||||
"maxSpeed": "Prędkość",
|
||||
"cargoCount": "Ładunki"
|
||||
},
|
||||
"loco-ezt": "EZT",
|
||||
"loco-szt": "SZT",
|
||||
"loco-s": "Spalinowóz",
|
||||
"loco-e": "Elektrowóz",
|
||||
"car-passenger": "Wagon pasażerski",
|
||||
"car-cargo": "Wagon towarowy"
|
||||
},
|
||||
"realstock": {
|
||||
"title": "ZESTAWIENIA REALNE by",
|
||||
"search-name": "Szukaj po nazwie",
|
||||
"search-stock": "Szukaj po pojazdach",
|
||||
"action-reset": "RESETUJ"
|
||||
}
|
||||
}
|
||||
|
||||
+6
-7
@@ -1,14 +1,13 @@
|
||||
import { createApp } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
import { registerSW } from 'virtual:pwa-register';
|
||||
import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
import { registerSW } from "virtual:pwa-register";
|
||||
|
||||
import App from './App.vue';
|
||||
import i18n from './i18n-setup';
|
||||
import App from "./App.vue";
|
||||
import i18n from "./i18n-setup";
|
||||
const pinia = createPinia();
|
||||
|
||||
registerSW({
|
||||
immediate: true,
|
||||
});
|
||||
|
||||
createApp(App).use(pinia).use(i18n).mount('#app');
|
||||
|
||||
createApp(App).use(pinia).use(i18n).mount("#app");
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import { defineComponent } from 'vue';
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
methods: {
|
||||
getIconURL(name: string, ext = 'svg'): string {
|
||||
getIconURL(name: string, ext = "svg"): string {
|
||||
return `/images/icon-${name}.${ext}`;
|
||||
},
|
||||
|
||||
getThumbnailURL(vehicleType: string, size: "small" | "large") {
|
||||
return `${
|
||||
import.meta.env.VITE_API_DEV === "1"
|
||||
? "http://localhost:5500"
|
||||
: "https://spythere.github.io/api"
|
||||
}/td2/images/${vehicleType}--${size == "small" ? 300 : 800}px.jpg`;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -29,8 +29,9 @@ export default defineComponent({
|
||||
count,
|
||||
imgSrc: vehicle.imageSrc,
|
||||
useType: isLoco ? vehicle.power : vehicle.useType,
|
||||
supportersOnly: vehicle.supportersOnly,
|
||||
isSponsorsOnly: vehicle.isSponsorsOnly,
|
||||
constructionType: vehicle.constructionType,
|
||||
sponsorsOnlyTimestamp: vehicle.sponsorsOnlyTimestamp,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -91,4 +92,3 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { defineComponent } from 'vue';
|
||||
import { useStore } from '../store';
|
||||
import { ICarWagon, ILocomotive, IStock, Vehicle } from '../types';
|
||||
import { isLocomotive } from '../utils/vehicleUtils';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
@@ -9,45 +10,9 @@ export default defineComponent({
|
||||
};
|
||||
},
|
||||
|
||||
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);
|
||||
},
|
||||
},
|
||||
computed: {},
|
||||
|
||||
methods: {
|
||||
selectLocoType(locoTypeId: string) {
|
||||
this.store.chosenLocoPower = locoTypeId;
|
||||
this.store.chosenVehicle = this.locoOptions[0];
|
||||
this.store.chosenLoco = this.locoOptions[0];
|
||||
},
|
||||
|
||||
selectCarWagonType(carWagonTypeId: string) {
|
||||
this.store.chosenCarUseType = carWagonTypeId;
|
||||
this.store.chosenVehicle = this.carOptions[0];
|
||||
this.store.chosenCar = this.carOptions[0];
|
||||
this.store.chosenCargo = null;
|
||||
},
|
||||
|
||||
previewVehicleByType(type: 'loco' | 'car' | 'cargo') {
|
||||
this.$nextTick(() => {
|
||||
if (!this.store.chosenLoco && !this.store.chosenCar) return;
|
||||
|
||||
this.store.chosenVehicle = type == 'loco' ? this.store.chosenLoco : this.store.chosenCar;
|
||||
|
||||
this.store.chosenCargo =
|
||||
this.store.chosenCar?.cargoList.find((cargo) => cargo.id == this.store.chosenCargo?.id) || null;
|
||||
});
|
||||
},
|
||||
|
||||
previewStock(stock: IStock) {
|
||||
if (this.store.chosenVehicle?.imageSrc != stock.imgSrc) this.store.imageLoading = true;
|
||||
|
||||
@@ -81,6 +46,11 @@ export default defineComponent({
|
||||
this.store.chosenCargo = null;
|
||||
},
|
||||
|
||||
previewVehicle(vehicle: Vehicle) {
|
||||
if (isLocomotive(vehicle)) this.previewLocomotive(vehicle);
|
||||
else this.previewCarWagon(vehicle);
|
||||
},
|
||||
|
||||
resetPreview() {
|
||||
this.store.chosenVehicle = null;
|
||||
this.store.chosenCar = null;
|
||||
@@ -89,4 +59,3 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defineComponent } from 'vue';
|
||||
import { useStore } from '../store';
|
||||
import { defineComponent } from "vue";
|
||||
import { useStore } from "../store";
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
@@ -18,7 +18,10 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
trainTooHeavy() {
|
||||
return this.store.acceptableMass && this.store.totalMass > this.store.acceptableMass;
|
||||
return (
|
||||
this.store.acceptableMass &&
|
||||
this.store.totalMass > this.store.acceptableMass
|
||||
);
|
||||
},
|
||||
|
||||
locoNotSuitable() {
|
||||
@@ -26,15 +29,19 @@ export default defineComponent({
|
||||
!this.store.isTrainPassenger &&
|
||||
this.store.stockList.length > 1 &&
|
||||
!this.store.stockList.every((stock) => stock.isLoco) &&
|
||||
this.store.stockList.some((stock) => stock.isLoco && stock.type.startsWith('EP'))
|
||||
this.store.stockList.some(
|
||||
(stock) => stock.isLoco && stock.type.startsWith("EP"),
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
tooManyLocomotives() {
|
||||
return this.store.stockList.reduce((acc, stock) => {
|
||||
if (stock.isLoco) acc += stock.count;
|
||||
return acc;
|
||||
}, 0) > 2;
|
||||
return (
|
||||
this.store.stockList.reduce((acc, stock) => {
|
||||
if (stock.isLoco) acc += stock.count;
|
||||
return acc;
|
||||
}, 0) > 2
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
+7
-5
@@ -1,4 +1,4 @@
|
||||
import { IStore } from './types';
|
||||
import { IStockData, IStore } from './types';
|
||||
import { defineStore } from 'pinia';
|
||||
import {
|
||||
acceptableMass,
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
totalLength,
|
||||
totalMass,
|
||||
} from './utils/vehicleUtils';
|
||||
import http from './http';
|
||||
|
||||
export const useStore = defineStore({
|
||||
id: 'store',
|
||||
@@ -46,11 +47,14 @@ export const useStore = defineStore({
|
||||
isRealStockListCardOpen: false,
|
||||
|
||||
stockData: undefined,
|
||||
} as IStore),
|
||||
|
||||
lastFocusedElement: null,
|
||||
}) as IStore,
|
||||
|
||||
getters: {
|
||||
locoDataList: (state) => locoDataList(state),
|
||||
carDataList: (state) => carDataList(state),
|
||||
vehicleDataList: (state) => [...locoDataList(state), ...carDataList(state)],
|
||||
totalMass: (state) => totalMass(state),
|
||||
totalLength: (state) => totalLength(state),
|
||||
maxStockSpeed: (state) => maxStockSpeed(state),
|
||||
@@ -61,7 +65,7 @@ export const useStore = defineStore({
|
||||
|
||||
actions: {
|
||||
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;
|
||||
},
|
||||
|
||||
@@ -82,5 +86,3 @@ export const useStore = defineStore({
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
+305
-268
@@ -1,268 +1,305 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Lato:wght@400;700;900&display=swap');
|
||||
|
||||
$breakpointMd: 960px;
|
||||
$breakpointSm: 550px;
|
||||
|
||||
$bgColor: #2b3552;
|
||||
$textColor: #fff;
|
||||
$secondaryColor: #222;
|
||||
$accentColor: #e4c428;
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
|
||||
&-track {
|
||||
background: #222;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&-thumb {
|
||||
border-radius: 1rem;
|
||||
background: #777;
|
||||
}
|
||||
|
||||
&-corner {
|
||||
background: #222;
|
||||
}
|
||||
}
|
||||
|
||||
body,
|
||||
html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
font-family: 'Lato', sans-serif;
|
||||
|
||||
background-color: $bgColor;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
|
||||
transition: color 250ms;
|
||||
|
||||
&:visited {
|
||||
color: white;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $accentColor;
|
||||
}
|
||||
}
|
||||
|
||||
select,
|
||||
option,
|
||||
input,
|
||||
button {
|
||||
font-family: 'Lato', sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
outline: none;
|
||||
background: none;
|
||||
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
font-size: 1em;
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
color: $accentColor;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.4em 0.75em;
|
||||
|
||||
outline: none;
|
||||
background-color: #222;
|
||||
border-radius: 8px;
|
||||
font-weight: bold;
|
||||
|
||||
transition: all 250ms;
|
||||
|
||||
&:hover {
|
||||
color: $accentColor;
|
||||
}
|
||||
|
||||
&.btn--outline {
|
||||
background: none;
|
||||
font-weight: bold;
|
||||
outline: 1px solid $accentColor;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
color: $accentColor;
|
||||
outline: 1px solid white;
|
||||
}
|
||||
|
||||
&[data-disabled='true'] {
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
|
||||
opacity: 0.75;
|
||||
background-color: #2b2b2b;
|
||||
}
|
||||
|
||||
&--image {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
|
||||
img {
|
||||
width: 1.3em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
&--text {
|
||||
font-weight: bold;
|
||||
transition: all 250ms;
|
||||
background: none;
|
||||
padding: 0;
|
||||
|
||||
&:focus-visible {
|
||||
outline: 1px solid white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
select,
|
||||
input[type='text'],
|
||||
input[type='number'] {
|
||||
background: none;
|
||||
border: 2px solid #aaa;
|
||||
outline: none;
|
||||
|
||||
padding: 0.25em 0.35em;
|
||||
|
||||
color: white;
|
||||
font-size: 1em;
|
||||
|
||||
width: 18em;
|
||||
|
||||
&:focus-visible {
|
||||
border-color: $accentColor;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: #aaa;
|
||||
}
|
||||
}
|
||||
|
||||
option {
|
||||
color: white;
|
||||
border: none;
|
||||
background-color: $bgColor;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.text {
|
||||
&--accent {
|
||||
color: $accentColor;
|
||||
}
|
||||
|
||||
&--grayed {
|
||||
color: #aaa;
|
||||
}
|
||||
}
|
||||
|
||||
.g-card {
|
||||
position: fixed;
|
||||
top: 1em;
|
||||
left: 0;
|
||||
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
z-index: 200;
|
||||
|
||||
&_bg {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
|
||||
background-color: #000000aa;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
||||
.g-choice {
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
span {
|
||||
padding: 0.25em 1em;
|
||||
border-radius: 0.25em;
|
||||
border: 2px solid white;
|
||||
margin: 0.25em;
|
||||
cursor: pointer;
|
||||
|
||||
transition: all 100ms ease;
|
||||
}
|
||||
|
||||
span:focus {
|
||||
color: $accentColor;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
label > input:checked + span {
|
||||
color: $accentColor;
|
||||
border-color: $accentColor;
|
||||
}
|
||||
}
|
||||
|
||||
// Vue Transition anims
|
||||
.slide-top {
|
||||
&-enter-from,
|
||||
&-leave-to {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
&-enter-active,
|
||||
&-leave-active {
|
||||
transition: transform 100ms ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
.card-appear {
|
||||
&-enter-from,
|
||||
&-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&-enter-active,
|
||||
&-leave-active {
|
||||
transition: all 100ms ease-in-out;
|
||||
}
|
||||
}
|
||||
$breakpointMd: 960px;
|
||||
$breakpointSm: 550px;
|
||||
|
||||
$bgColor: #2b3552;
|
||||
$textColor: #fff;
|
||||
$secondaryColor: #222;
|
||||
$accentColor: #e4c428;
|
||||
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src:
|
||||
url('$fonts/Lato-Light.woff2') format('woff2'),
|
||||
url('$fonts/Lato-Light.woff') format('woff');
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src:
|
||||
url('$fonts/Lato-Bold.woff2') format('woff2'),
|
||||
url('$fonts/Lato-Bold.woff') format('woff');
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src:
|
||||
url('$fonts/Lato-Regular.woff2') format('woff2'),
|
||||
url('$fonts/Lato-Regular.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
|
||||
&-track {
|
||||
background: #222;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&-thumb {
|
||||
border-radius: 1rem;
|
||||
background: #777;
|
||||
}
|
||||
|
||||
&-corner {
|
||||
background: #222;
|
||||
}
|
||||
}
|
||||
|
||||
body,
|
||||
html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
font-family: Lato, sans-serif;
|
||||
|
||||
background-color: $bgColor;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
|
||||
transition: color 250ms;
|
||||
|
||||
&:visited {
|
||||
color: white;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $accentColor;
|
||||
}
|
||||
}
|
||||
|
||||
select,
|
||||
option,
|
||||
input,
|
||||
button {
|
||||
font-family: Lato, sans-serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
outline: none;
|
||||
background: none;
|
||||
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
font-size: 1em;
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
color: $accentColor;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.4em 0.75em;
|
||||
|
||||
outline: none;
|
||||
background-color: #222;
|
||||
border-radius: 8px;
|
||||
font-weight: bold;
|
||||
|
||||
transition:
|
||||
color 150ms,
|
||||
background-color 150ms;
|
||||
|
||||
&:hover {
|
||||
color: $accentColor;
|
||||
}
|
||||
|
||||
&.btn--outline {
|
||||
background: none;
|
||||
font-weight: bold;
|
||||
outline: 1px solid $accentColor;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
color: $accentColor;
|
||||
outline: 1px solid white;
|
||||
}
|
||||
|
||||
&[data-chosen='true'] {
|
||||
background-color: $accentColor;
|
||||
color: black;
|
||||
|
||||
box-shadow: 0 0 5px 1px $accentColor;
|
||||
}
|
||||
|
||||
&[data-disabled='true'] {
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
|
||||
opacity: 0.75;
|
||||
background-color: #2b2b2b;
|
||||
}
|
||||
|
||||
&--image {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
|
||||
img {
|
||||
width: 1.3em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
&--text {
|
||||
font-weight: bold;
|
||||
transition: all 250ms;
|
||||
background: none;
|
||||
padding: 0;
|
||||
|
||||
&:focus-visible {
|
||||
outline: 1px solid white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
select,
|
||||
input[type='text'],
|
||||
input[type='number'] {
|
||||
background: $bgColor;
|
||||
border: 2px solid #aaa;
|
||||
outline: none;
|
||||
|
||||
padding: 0.25em 0.35em;
|
||||
|
||||
color: white;
|
||||
font-size: 1em;
|
||||
|
||||
width: 18em;
|
||||
|
||||
&:focus-visible {
|
||||
border-color: $accentColor;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: #aaa;
|
||||
}
|
||||
}
|
||||
|
||||
option {
|
||||
color: white;
|
||||
border: none;
|
||||
background-color: $bgColor;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.text {
|
||||
&--accent {
|
||||
color: $accentColor;
|
||||
}
|
||||
|
||||
&--grayed {
|
||||
color: #aaa;
|
||||
}
|
||||
}
|
||||
|
||||
.g-card {
|
||||
position: fixed;
|
||||
top: 1em;
|
||||
left: 0;
|
||||
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
z-index: 200;
|
||||
|
||||
&_bg {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
|
||||
background-color: #000000aa;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
||||
.g-choice {
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
span {
|
||||
padding: 0.25em 1em;
|
||||
border-radius: 0.25em;
|
||||
border: 2px solid white;
|
||||
margin: 0.25em;
|
||||
cursor: pointer;
|
||||
|
||||
transition: all 100ms ease;
|
||||
}
|
||||
|
||||
span:focus {
|
||||
color: $accentColor;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
label > input:checked + span {
|
||||
color: $accentColor;
|
||||
border-color: $accentColor;
|
||||
}
|
||||
}
|
||||
|
||||
// Vue Transition anims
|
||||
.slide-top {
|
||||
&-enter-from,
|
||||
&-leave-to {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
&-enter-active,
|
||||
&-leave-active {
|
||||
transition: transform 100ms ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
.card-appear {
|
||||
&-enter-from,
|
||||
&-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&-enter-active,
|
||||
&-leave-active {
|
||||
transition: all 100ms ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -1,4 +1,4 @@
|
||||
@import './global.scss';
|
||||
@import "./global.scss";
|
||||
|
||||
.tab {
|
||||
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';
|
||||
stockData?: IStockData;
|
||||
|
||||
lastFocusedElement: HTMLElement | null;
|
||||
}
|
||||
|
||||
export type TStockInfoKey = 'loco-e' | 'loco-s' | 'loco-ezt' | 'loco-szt' | 'car-passenger' | 'car-cargo';
|
||||
export type TLocoGroup = 'loco-e' | 'loco-s' | 'loco-ezt' | 'loco-szt';
|
||||
export type TCarWagonGroup = 'car-passenger' | 'car-cargo';
|
||||
|
||||
export interface IStockProps {
|
||||
type: string;
|
||||
@@ -52,12 +55,12 @@ export interface IStockData {
|
||||
};
|
||||
|
||||
info: {
|
||||
'car-cargo': [string, string, boolean, boolean, string][];
|
||||
'car-passenger': [string, string, boolean, boolean, string][];
|
||||
'loco-e': [string, string, string, string, boolean][];
|
||||
'loco-s': [string, string, string, string, boolean][];
|
||||
'loco-szt': [string, string, string, string, boolean][];
|
||||
'loco-ezt': [string, string, string, string, boolean][];
|
||||
'car-cargo': [string, string, boolean, number | null, string][];
|
||||
'car-passenger': [string, string, boolean, number | null, string][];
|
||||
'loco-e': [string, string, string, string, number | null][];
|
||||
'loco-s': [string, string, string, string, number | null][];
|
||||
'loco-szt': [string, string, string, string, number | null][];
|
||||
'loco-ezt': [string, string, string, string, number | null][];
|
||||
};
|
||||
|
||||
props: IStockProps[];
|
||||
@@ -67,11 +70,13 @@ export interface IStockData {
|
||||
|
||||
export interface ILocomotive {
|
||||
type: string;
|
||||
power: string;
|
||||
power: TLocoGroup;
|
||||
group: TLocoGroup;
|
||||
constructionType: string;
|
||||
cabinType: string;
|
||||
maxSpeed: number;
|
||||
supportersOnly: boolean;
|
||||
isSponsorsOnly: boolean;
|
||||
sponsorsOnlyTimestamp: number;
|
||||
imageSrc: string;
|
||||
|
||||
mass: number;
|
||||
@@ -79,12 +84,13 @@ export interface ILocomotive {
|
||||
}
|
||||
|
||||
export interface ICarWagon {
|
||||
//"203V_PKPC_Fll_01","203V",true,false,"100",img
|
||||
type: string;
|
||||
useType: 'car-passenger' | 'car-cargo';
|
||||
useType: TCarWagonGroup;
|
||||
group: TCarWagonGroup;
|
||||
constructionType: string;
|
||||
loadable: boolean;
|
||||
supportersOnly: boolean;
|
||||
isSponsorsOnly: boolean;
|
||||
sponsorsOnlyTimestamp: number;
|
||||
maxSpeed: number;
|
||||
imageSrc: string;
|
||||
|
||||
@@ -108,7 +114,8 @@ export interface IStock {
|
||||
maxSpeed: number;
|
||||
cargo?: { id: string; totalMass: number };
|
||||
isLoco: boolean;
|
||||
supportersOnly: boolean;
|
||||
isSponsorsOnly: boolean;
|
||||
sponsorsOnlyTimestamp: number;
|
||||
count: number;
|
||||
imgSrc?: string;
|
||||
}
|
||||
@@ -120,4 +127,3 @@ export interface IReadyStockItem {
|
||||
number: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
const supportedConstructions = ['303e', '203e'];
|
||||
|
||||
export function locoSupportsColdStart(constructionType: string) {
|
||||
return new RegExp(`(${supportedConstructions.join('|')})`).test(constructionType);
|
||||
}
|
||||
const supportedConstructions = ["303e", "203e"];
|
||||
|
||||
export function locoSupportsColdStart(constructionType: string) {
|
||||
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 const calculateSpeedLimit = (locoType: LocoType, stockMass: number, isTrainPassenger: boolean) => {
|
||||
const speedTable = speedLimitTable[locoType][isTrainPassenger ? 'passenger' : 'cargo'];
|
||||
export const calculateSpeedLimit = (
|
||||
locoType: LocoType,
|
||||
stockMass: number,
|
||||
isTrainPassenger: boolean,
|
||||
) => {
|
||||
const speedTable =
|
||||
speedLimitTable[locoType][isTrainPassenger ? "passenger" : "cargo"];
|
||||
|
||||
if (!speedTable) return undefined;
|
||||
|
||||
let speedLimit = 0;
|
||||
for (let mass in speedTable) if (stockMass > Number(mass)) speedLimit = (speedTable as any)[mass];
|
||||
for (const mass in speedTable)
|
||||
if (stockMass > Number(mass)) speedLimit = (speedTable as any)[mass];
|
||||
|
||||
return speedLimit;
|
||||
};
|
||||
|
||||
|
||||
+23
-26
@@ -1,5 +1,5 @@
|
||||
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';
|
||||
|
||||
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) => {
|
||||
if (!vehiclePower.startsWith('loco')) return acc;
|
||||
|
||||
const locoVehiclesData = stockData.info[vehiclePower as 'loco-e' | 'loco-s' | 'loco-ezt' | 'loco-szt'];
|
||||
const locoVehiclesData = stockData.info[vehiclePower as TLocoGroup];
|
||||
|
||||
locoVehiclesData.forEach((loco) => {
|
||||
if (state.showSupporter && !loco[4]) return;
|
||||
|
||||
const [type, constructionType, cabinType, maxSpeed, supportersOnly] = loco;
|
||||
const [type, constructionType, cabinType, maxSpeed, sponsorsTimestamp] = loco;
|
||||
const locoProps = stockData.props.find((prop) => constructionType == prop.type);
|
||||
|
||||
acc.push({
|
||||
power: vehiclePower,
|
||||
power: vehiclePower as TLocoGroup,
|
||||
group: vehiclePower as TLocoGroup,
|
||||
type,
|
||||
constructionType,
|
||||
cabinType,
|
||||
maxSpeed: Number(maxSpeed),
|
||||
supportersOnly,
|
||||
isSponsorsOnly: Number(sponsorsTimestamp) > Date.now(),
|
||||
sponsorsOnlyTimestamp: Number(sponsorsTimestamp),
|
||||
imageSrc: '',
|
||||
|
||||
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) => {
|
||||
if (!vehicleUseType.startsWith('car')) return acc;
|
||||
|
||||
const carVehiclesData = stockData.info[vehicleUseType as 'car-passenger' | 'car-cargo'];
|
||||
const carVehiclesData = stockData.info[vehicleUseType as TCarWagonGroup];
|
||||
|
||||
carVehiclesData.forEach((car) => {
|
||||
if (state.showSupporter && !car[3]) return;
|
||||
const [type, constructionType, loadable, sponsorsOnlyTimestamp, maxSpeed] = car;
|
||||
|
||||
const carPropsData = stockData.props.find((v) => car[0].toString().startsWith(v.type));
|
||||
if (state.showSupporter && Number(sponsorsOnlyTimestamp) <= Date.now()) return;
|
||||
|
||||
const carPropsData = stockData.props.find((v) => type.toString().startsWith(v.type));
|
||||
|
||||
acc.push({
|
||||
useType: vehicleUseType as 'car-passenger' | 'car-cargo',
|
||||
type: car[0],
|
||||
constructionType: car[1],
|
||||
loadable: car[2],
|
||||
supportersOnly: car[3],
|
||||
maxSpeed: Number(car[4]),
|
||||
useType: vehicleUseType as TCarWagonGroup,
|
||||
group: vehicleUseType as TCarWagonGroup,
|
||||
type,
|
||||
constructionType,
|
||||
loadable,
|
||||
isSponsorsOnly: Number(sponsorsOnlyTimestamp) > Date.now(),
|
||||
sponsorsOnlyTimestamp: Number(sponsorsOnlyTimestamp),
|
||||
maxSpeed: Number(maxSpeed),
|
||||
imageSrc: '',
|
||||
cargoList:
|
||||
!carPropsData || carPropsData.cargo === null
|
||||
@@ -81,10 +87,7 @@ export function carDataList(state: IStore) {
|
||||
}
|
||||
|
||||
export function totalMass(state: IStore) {
|
||||
return ~~state.stockList.reduce(
|
||||
(acc, stock) => acc + (stock.cargo ? stock.cargo.totalMass : stock.mass) * stock.count,
|
||||
0
|
||||
);
|
||||
return ~~state.stockList.reduce((acc, stock) => acc + (stock.cargo ? stock.cargo.totalMass : stock.mass) * stock.count, 0);
|
||||
}
|
||||
|
||||
export function totalLength(state: IStore) {
|
||||
@@ -92,10 +95,7 @@ export function totalLength(state: IStore) {
|
||||
}
|
||||
|
||||
export function maxStockSpeed(state: IStore) {
|
||||
const stockSpeedLimit = state.stockList.reduce(
|
||||
(acc, stock) => (stock.maxSpeed < acc || acc == 0 ? stock.maxSpeed : acc),
|
||||
0
|
||||
);
|
||||
const stockSpeedLimit = state.stockList.reduce((acc, stock) => (stock.maxSpeed < acc || acc == 0 ? stock.maxSpeed : acc), 0);
|
||||
const headingLoco = state.stockList[0]?.isLoco ? state.stockList[0] : undefined;
|
||||
|
||||
if (!headingLoco) return stockSpeedLimit;
|
||||
@@ -139,9 +139,7 @@ export function isTrainPassenger(state: IStore) {
|
||||
if (state.stockList.length == 0) return false;
|
||||
if (state.stockList.every((stock) => stock.isLoco)) return false;
|
||||
|
||||
return state.stockList
|
||||
.filter((stock) => !stock.isLoco)
|
||||
.every((stock) => stock.useType === EVehicleUseType.CAR_PASSENGER);
|
||||
return state.stockList.filter((stock) => !stock.isLoco).every((stock) => stock.useType === EVehicleUseType.CAR_PASSENGER);
|
||||
}
|
||||
|
||||
export function chosenRealStock(state: IStore) {
|
||||
@@ -158,4 +156,3 @@ export function chosenRealStock(state: IStore) {
|
||||
|
||||
return realStockObj;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<MainContainer />
|
||||
<Footer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { useStore } from '../store';
|
||||
|
||||
import MainContainer from '../components/app/MainContainer.vue';
|
||||
import Footer from '../components/app/Footer.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MainContainer,
|
||||
Footer,
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
store: useStore(),
|
||||
}),
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-container {
|
||||
min-height: 100vh;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<MainContainer />
|
||||
<FooterVue />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import { useStore } from "../store";
|
||||
|
||||
import MainContainer from "../components/app/MainContainer.vue";
|
||||
import FooterVue from "../components/app/Footer.vue";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MainContainer,
|
||||
FooterVue,
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
store: useStore(),
|
||||
}),
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-container {
|
||||
min-height: 100vh;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
Vendored
+4
-4
@@ -1,7 +1,7 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
declare module "*.vue" {
|
||||
import type { DefineComponent } from "vue";
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
||||
|
||||
+23
-5
@@ -2,19 +2,26 @@ import { defineConfig } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
|
||||
import { VitePWA } from 'vite-plugin-pwa';
|
||||
import { resolve } from 'path';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
server: {
|
||||
port: 2137,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
$fonts: resolve('/fonts'),
|
||||
$images: resolve('/images'),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
VitePWA({
|
||||
registerType: 'autoUpdate',
|
||||
|
||||
workbox: {
|
||||
// globPatterns: ['**/*.{js,css,html,png,svg,img}'],
|
||||
globPatterns: ['**/*.{js,css,html,png,svg,img,woff,woff2}'],
|
||||
|
||||
runtimeCaching: [
|
||||
{
|
||||
@@ -24,10 +31,23 @@ export default defineConfig({
|
||||
cacheName: 'swdr-images-cache',
|
||||
expiration: {
|
||||
maxEntries: 50,
|
||||
maxAgeSeconds: 60 * 60 * 24 * 7, // <== 7 days
|
||||
maxAgeSeconds: 60 * 60 * 24, // <== 1 day
|
||||
},
|
||||
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