mirror of
https://github.com/Spythere/pojazdownik.git
synced 2026-05-03 11:45:34 +00:00
373 lines
8.4 KiB
Vue
373 lines
8.4 KiB
Vue
<template>
|
|
<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>
|
|
</h1>
|
|
<button class="btn action-exit" @click="store.isRealStockListCardOpen = false">
|
|
<img src="/images/icon-exit.svg" alt="" />
|
|
</button>
|
|
</div>
|
|
|
|
<div class="filters" ref="focus" tabindex="0">
|
|
<input
|
|
list="readyStockDataList"
|
|
v-model="searchedReadyStockName"
|
|
:placeholder="$t('realstock.search-name')"
|
|
/>
|
|
|
|
<datalist id="readyStockDataList">
|
|
<option
|
|
v-for="stock in filteredCompositionList"
|
|
:value="stock.stockId"
|
|
:key="stock.name"
|
|
>
|
|
{{ stock.stockId }}
|
|
</option>
|
|
</datalist>
|
|
|
|
<input
|
|
list="readyStockStringList"
|
|
v-model="searchedReadyStockString"
|
|
:placeholder="$t('realstock.search-stock')"
|
|
/>
|
|
|
|
<datalist id="readyStockStringList">
|
|
<option
|
|
v-for="stockType in availableCompositionTypes"
|
|
:value="stockType"
|
|
:key="stockType"
|
|
>
|
|
{{ stockType }}
|
|
</option>
|
|
</datalist>
|
|
|
|
<button class="btn action-reset" @click="resetStockFilters">
|
|
{{ $t('realstock.action-reset') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<ul class="card_list" ref="list" @scroll="onListScroll">
|
|
<li v-for="rStock in filteredCompositionList" :key="rStock.stockId">
|
|
<!-- :data-last-selected="store.ch === rStock.stockId" -->
|
|
<div
|
|
class="stock-title"
|
|
tabindex="0"
|
|
@click="chooseStock(rStock)"
|
|
@keydown.enter="chooseStock(rStock)"
|
|
>
|
|
<img class="stock-icon" :src="getIconURL(rStock.type)" :alt="rStock.type" />
|
|
<b class="text--accent" style="margin-left: 5px"> {{ rStock.name }}</b>
|
|
<div>{{ rStock.number }}</div>
|
|
</div>
|
|
|
|
<div class="stock-thumbnails" ref="thumbnailsRef">
|
|
<div
|
|
class="thumbnail-item"
|
|
v-for="stockType in rStock.stockString.split(';')"
|
|
:key="stockType"
|
|
>
|
|
<div class="thumbnail-container">
|
|
<div>{{ stockType }}</div>
|
|
<img
|
|
:src="`https://stacjownik.spythere.eu/static/thumbnails/${stockType}.png`"
|
|
:title="stockType"
|
|
style="opacity: 0"
|
|
@error="(e) => onStockItemError(e, stockType)"
|
|
@load="(e) => ((e.target as HTMLElement).style.opacity = '1')"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
|
|
<div class="bottom" ref="bottom"></div>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { defineComponent } from 'vue';
|
|
|
|
import { useStore } from '../../store';
|
|
import imageMixin from '../../mixins/imageMixin';
|
|
import stockMixin from '../../mixins/stockMixin';
|
|
|
|
import { IRealComposition, VehicleGroupType } from '../../types/common.types';
|
|
|
|
function getVehicleType(stockType: string): VehicleGroupType {
|
|
if (/^E/.test(stockType)) return 'loco-electric';
|
|
if (/^S/.test(stockType)) return 'loco-diesel';
|
|
|
|
return 'wagon-passenger';
|
|
}
|
|
|
|
export default defineComponent({
|
|
mixins: [imageMixin, stockMixin],
|
|
|
|
data: () => ({
|
|
store: useStore(),
|
|
responseStatus: 'loading',
|
|
isMobile:
|
|
'ontouchstart' in document.documentElement && navigator.userAgent.match(/Mobi/)
|
|
? true
|
|
: false,
|
|
observer: null as IntersectionObserver | null,
|
|
searchedReadyStockName: '',
|
|
searchedReadyStockString: '',
|
|
visibleIndexesTo: 0,
|
|
lastSelectedStockId: null as string | null,
|
|
scrollTop: 0,
|
|
}),
|
|
|
|
mounted() {
|
|
this.mountObserver();
|
|
},
|
|
|
|
activated() {
|
|
(this.$refs['focus'] as HTMLElement).focus();
|
|
|
|
(this.$refs['list'] as HTMLElement).scrollTo({
|
|
top: this.scrollTop,
|
|
behavior: 'auto',
|
|
});
|
|
},
|
|
|
|
computed: {
|
|
filteredCompositionList() {
|
|
return this.store.realCompositionList
|
|
.filter(
|
|
(rc) =>
|
|
rc.stockId
|
|
.toLocaleLowerCase()
|
|
.includes(this.searchedReadyStockName.toLocaleLowerCase()) &&
|
|
rc.stockString
|
|
.toLocaleLowerCase()
|
|
.includes(this.searchedReadyStockString.toLocaleLowerCase())
|
|
)
|
|
.filter((_, i) => i <= this.visibleIndexesTo);
|
|
},
|
|
|
|
availableCompositionTypes() {
|
|
return this.store.realCompositionList
|
|
.reduce((acc, rs) => {
|
|
rs.stockString.split(';').forEach((s) => {
|
|
if (!acc.includes(s)) acc.push(s);
|
|
});
|
|
|
|
return acc;
|
|
}, [] as string[])
|
|
.sort((a, b) => (a > b ? 1 : -1));
|
|
},
|
|
},
|
|
|
|
watch: {
|
|
filteredCompositionList(curr, prev) {
|
|
if (curr.length < prev.length) {
|
|
this.visibleIndexesTo = 20;
|
|
(this.$refs['list'] as HTMLElement).scrollTo({
|
|
top: 0,
|
|
});
|
|
}
|
|
},
|
|
},
|
|
|
|
methods: {
|
|
mountObserver() {
|
|
this.observer = new IntersectionObserver((entries) => {
|
|
if (entries[0].intersectionRatio > 0) this.visibleIndexesTo += 20;
|
|
});
|
|
|
|
this.observer.observe(this.$refs['bottom'] as HTMLElement);
|
|
},
|
|
|
|
getImageUrl(name: string) {
|
|
return new URL(`./dir/${name}.png`, import.meta.url).href;
|
|
},
|
|
|
|
resetStockFilters() {
|
|
this.searchedReadyStockName = '';
|
|
this.searchedReadyStockString = '';
|
|
},
|
|
|
|
chooseStock(stockItem: IRealComposition) {
|
|
this.loadStockFromString(stockItem.stockString);
|
|
this.lastSelectedStockId = stockItem.stockId;
|
|
this.store.isRealStockListCardOpen = false;
|
|
},
|
|
|
|
onStockItemError(e: Event, stockType: string) {
|
|
const imageEl = e.target as HTMLImageElement;
|
|
imageEl.src = `images/${getVehicleType(stockType)}-unknown.png`;
|
|
imageEl.style.opacity = '1';
|
|
},
|
|
|
|
onListScroll(e: Event) {
|
|
const listElement = e.target as HTMLElement;
|
|
const scrollTop = listElement.scrollTop;
|
|
|
|
this.scrollTop = scrollTop;
|
|
},
|
|
},
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
@use '@/styles/responsive';
|
|
|
|
.action-exit {
|
|
display: flex;
|
|
background-color: #333;
|
|
border-radius: 0.25em;
|
|
padding: 0.5em;
|
|
}
|
|
|
|
.action-reset {
|
|
background-color: #333;
|
|
}
|
|
|
|
.card_content {
|
|
display: grid;
|
|
grid-template-rows: auto 1fr auto;
|
|
gap: 0.5em;
|
|
|
|
background-color: #1c1c1c;
|
|
border-radius: 1em;
|
|
|
|
height: 95vh;
|
|
max-width: 1000px;
|
|
width: 90vw;
|
|
|
|
padding: 0 1em;
|
|
|
|
z-index: 100;
|
|
|
|
@include responsive.smallScreen {
|
|
height: 80vh;
|
|
}
|
|
}
|
|
|
|
.top-pane {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
gap: 0.5em;
|
|
|
|
h1 {
|
|
color: #aaa;
|
|
}
|
|
}
|
|
|
|
.top-sticky {
|
|
position: sticky;
|
|
top: 0;
|
|
background: #1c1c1c;
|
|
}
|
|
|
|
.filters {
|
|
display: flex;
|
|
gap: 0.5em;
|
|
|
|
padding: 0.5em 0;
|
|
|
|
input {
|
|
width: 35%;
|
|
}
|
|
|
|
@include responsive.smallScreen {
|
|
flex-wrap: wrap;
|
|
|
|
input {
|
|
width: 100%;
|
|
}
|
|
|
|
.btn {
|
|
width: 100%;
|
|
}
|
|
}
|
|
}
|
|
|
|
ul {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5em;
|
|
|
|
overflow: auto;
|
|
|
|
li {
|
|
display: grid;
|
|
grid-template-columns: 1fr 2fr;
|
|
background: #2b2b2b;
|
|
gap: 1rem;
|
|
padding: 0.1em;
|
|
|
|
&[data-last-selected='true'] .stock-title {
|
|
border: 1px solid var(--accentColor);
|
|
}
|
|
|
|
.stock-title {
|
|
cursor: pointer;
|
|
padding: 0.5em;
|
|
}
|
|
|
|
.stock-icon {
|
|
height: 0.85em;
|
|
}
|
|
|
|
&:hover {
|
|
background: #222;
|
|
}
|
|
|
|
@include responsive.smallScreen {
|
|
grid-template-columns: 1fr;
|
|
// grid-template-rows: 1fr 1fr;
|
|
}
|
|
}
|
|
}
|
|
|
|
.stock-thumbnails {
|
|
display: flex;
|
|
align-items: flex-end;
|
|
|
|
width: 100%;
|
|
|
|
overflow: auto;
|
|
padding: 0.5em;
|
|
}
|
|
|
|
.thumbnail-item {
|
|
display: flex;
|
|
align-items: flex-end;
|
|
justify-content: center;
|
|
height: 100%;
|
|
|
|
div {
|
|
font-size: 0.9em;
|
|
text-align: center;
|
|
}
|
|
|
|
img {
|
|
max-width: 250px;
|
|
max-height: 50px;
|
|
}
|
|
|
|
& > .thumbnail-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
height: 100%;
|
|
}
|
|
}
|
|
|
|
.bottom {
|
|
padding: 1em;
|
|
}
|
|
</style>
|