chore: wiki list tab revamp

This commit is contained in:
2024-04-14 00:13:01 +02:00
parent 99cbde3828
commit baa39a5a99
4 changed files with 138 additions and 331 deletions
+8 -17
View File
@@ -44,7 +44,7 @@
}} }}
</div> </div>
<b style="color: salmon" v-if="store.chosenVehicle.restrictions['sponsorOnly']">{{ <b v-if="store.chosenVehicle.restrictions.sponsorOnly > 0" class="sponsor-only">{{
$t('preview.sponsor-only', [ $t('preview.sponsor-only', [
new Date(store.chosenVehicle.restrictions['sponsorOnly']).toLocaleDateString( new Date(store.chosenVehicle.restrictions['sponsorOnly']).toLocaleDateString(
$i18n.locale == 'pl' ? 'pl-PL' : 'en-GB' $i18n.locale == 'pl' ? 'pl-PL' : 'en-GB'
@@ -52,7 +52,7 @@
]) ])
}}</b> }}</b>
<b style="color: gold" v-if="store.chosenVehicle.restrictions['teamOnly']">{{ <b v-if="store.chosenVehicle.restrictions['teamOnly']" class="team-only">{{
$t('preview.team-only') $t('preview.team-only')
}}</b> }}</b>
</div> </div>
@@ -157,22 +157,13 @@ img {
} }
} }
// .train-image { .sponsor-only {
// &__content { color: $sponsorColor;
// &.sponsor img { }
// border: 1px solid salmon;
// }
// img { .team-only {
// max-width: 380px; color: $teamColor;
// width: 100%; }
// height: 100%;
// border: 1px solid white;
// cursor: zoom-in;
// }
// }
// }
.image-info { .image-info {
font-size: 1.1em; font-size: 1.1em;
+94 -300
View File
@@ -5,116 +5,65 @@
</div> </div>
<div class="tab_content"> <div class="tab_content">
<div class="actions-panel"> <div class="actions">
<div class="actions-panel_vehicles"> <label>
<button <span>Pojazdy</span>
class="btn" <select name="filter-type" id="filter-type" v-model="filterType">
:data-chosen="currentFilterMode == 'tractions'" <option v-for="filter in filters" :key="filter" :value="filter">
@click="toggleFilter('tractions')" {{ $t(`wiki.filters.${filter}`) }}
>
{{ $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">
<select name="" id="">
<option v-for="header in visibleHeaders" :key="header.id" :value="header.id">
{{ $t(`wiki.header.${header.id}`) }}
</option> </option>
</select> </select>
</label>
<select name="" id=""> <label>
<option value="">rosnąco</option> <span>Sortuj wg</span>
<option value="">malejąco</option> <select name="sorter-type" id="sorter-type" v-model="sorterType">
<option v-for="sorter in sorters" :key="sorter" :value="sorter">
{{ $t(`wiki.sort-by.${sorter}`) }}
</option>
</select> </select>
</label>
<input type="text" :placeholder="$t('wiki.search')" v-model="searchedVehicleTypeName" /> <label>
</div> <span>Kierunek sortowania</span>
<select name="sorter-direction" id="sorter-direction" v-model="sorterDirection">
<option value="asc">{{ $t('wiki.sort-direction.asc') }}</option>
<option value="desc">{{ $t('wiki.sort-direction.desc') }}</option>
</select>
</label>
<label>
<span>{{ $t('wiki.labels.search-vehicle') }}</span>
<input
type="text"
:placeholder="$t('wiki.labels.search-vehicle-placeholder')"
v-model="searchedVehicleTypeName"
/>
</label>
</div> </div>
<ul class="vehicles" ref="vehicles"> <ul class="vehicles" ref="vehicles">
<li <li
v-for="{ vehicle } in computedTableData" v-for="vehicle in computedVehicles"
:key="vehicle.type" :key="vehicle.type"
:data-preview="vehicle.type === store.chosenVehicle?.type" :data-preview="vehicle.type === store.chosenVehicle?.type"
tabindex="0" :data-sponsor-only="vehicle.restrictions.sponsorOnly > 0"
:data-team-only="vehicle.restrictions.teamOnly"
@click.prevent="onItemSelect(vehicle)" @click.prevent="onItemSelect(vehicle)"
@keydown.enter="onItemSelect(vehicle)" @keydown.enter="onItemSelect(vehicle)"
tabindex="0"
> >
<img <img loading="lazy" width="120" :src="getThumbnailURL(vehicle.type, 'small')" />
loading="lazy"
width="120"
:src="getThumbnailURL(vehicle.type, 'small')"
:data-src="getThumbnailURL(vehicle.type, 'small')"
:data-sponsor-only="vehicle.restrictions.sponsorOnly > 0"
:data-team-only="vehicle.restrictions.teamOnly"
/>
<span> <span>
<b> {{ vehicle.type.replace(/_/g, ' ') }} </b><br /> <b> {{ vehicle.type.replace(/_/g, ' ') }} </b><br />
{{ $t(`wiki.${vehicle.group}`) }} | {{ $t(`wiki.${vehicle.group}`) }} |
{{ isTractionUnit(vehicle) ? vehicle.cabinType : vehicle.constructionType }} <br /> {{ isTractionUnit(vehicle) ? vehicle.cabinType : vehicle.constructionType }} <br />
{{ vehicle.maxSpeed }}km/h | {{ (vehicle.weight / 1000).toFixed(1) }}t | {{ vehicle.length }}m | {{ (vehicle.weight / 1000).toFixed(1) }}t |
{{ vehicle.length }}m {{ vehicle.maxSpeed }}km/h
</span> </span>
</li> </li>
</ul> </ul>
<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 ? `&uArr;` : `&dArr;` }}
</span>
</th>
</tr>
</thead>
<tbody>
<tr
v-for="{ vehicle, show } in computedTableData"
v-show="show"
tabindex="0"
:key="vehicle.type"
@click="previewVehicle(vehicle)"
@keydown.enter="previewVehicle(vehicle)"
@dblclick="addVehicle(vehicle)"
ref="itemRefs"
>
<td style="width: 120px">
<img width="120" src="" :data-src="getThumbnailURL(vehicle.type, 'small')" />
</td>
<td
:data-sponsor-only="vehicle.restrictions.sponsorOnly > 0"
:data-team-only="vehicle.restrictions.teamOnly"
style="min-width: 150px"
>
{{ vehicle.type.replace(/_/g, ' ') }}
</td>
<td style="min-width: 100px">{{ $t(`wiki.${vehicle.group}`) }}</td>
<td>{{ vehicle.constructionType }}</td>
<td>{{ vehicle.length }}</td>
<td>{{ (vehicle.weight / 1000).toFixed(1) }}</td>
<td>{{ vehicle.maxSpeed }}</td>
</tr>
</tbody>
<span ref="table-bottom"></span>
</table> -->
</div>
</div> </div>
</section> </section>
</template> </template>
@@ -128,40 +77,13 @@ import { isTractionUnit } from '../../utils/vehicleUtils';
import stockMixin from '../../mixins/stockMixin'; import stockMixin from '../../mixins/stockMixin';
import imageMixin from '../../mixins/imageMixin'; import imageMixin from '../../mixins/imageMixin';
type SorterID = const sorters = ['type', 'group', 'length', 'weight', 'maxSpeed'] as const;
| 'type' const filters = ['vehicles-all', 'vehicles-traction', 'vehicles-wagon'] as const;
| 'constructionType'
| 'image'
| 'length'
| 'weight'
| 'maxSpeed'
| 'cargoCount'
| 'group'
| 'coldStart';
interface IWikiHeader { type SorterType = (typeof sorters)[number];
id: SorterID; type SorterDirection = 'asc' | 'desc';
sortable: boolean;
for: 'all' | 'carriages' | 'tractions';
}
interface IWikiRow { type FilterType = (typeof filters)[number];
vehicle: IVehicle;
show: boolean;
showImage: 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: 'weight', 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({ export default defineComponent({
mixins: [stockPreviewMixin, stockMixin, imageMixin], mixins: [stockPreviewMixin, stockMixin, imageMixin],
@@ -170,31 +92,27 @@ export default defineComponent({
return { return {
store: useStore(), store: useStore(),
observer: null as IntersectionObserver | null, observer: null as IntersectionObserver | null,
headers,
scrollTop: 0, sorters: sorters,
filters: filters,
searchedVehicleTypeName: '', searchedVehicleTypeName: '',
currentSorter: { sorterType: 'type' as SorterType,
id: 'type' as SorterID, sorterDirection: 'asc' as SorterDirection,
direction: 1,
},
currentFilterMode: 'all' as 'all' | 'tractions' | 'carriages', filterType: 'vehicles-all' as FilterType,
}; };
}, },
mounted() { watch: {
this.mountObserver(); computedVehicles() {
}, const vehiclesRef = this.$refs['vehicles'] as HTMLElement;
activated() { vehiclesRef.scrollTo({
const tableWrapperRef = this.$refs['table-wrapper'] as HTMLElement; top: 0,
});
tableWrapperRef.scrollTo({ },
top: this.scrollTop,
});
}, },
methods: { methods: {
@@ -206,115 +124,59 @@ export default defineComponent({
this.previewVehicle(vehicle); this.previewVehicle(vehicle);
}, },
mountObserver() { sortVehicles(v1: IVehicle, v2: IVehicle) {
if (this.observer) return; const direction = this.sorterDirection == 'asc' ? 1 : -1;
this.observer = new IntersectionObserver((entries) => { switch (this.sorterType) {
entries.forEach((entry) => {
if (entry.intersectionRatio > 0) {
entry.target
.querySelector('img')!
.setAttribute('src', entry.target.querySelector('img')!.getAttribute('data-src')!);
}
});
});
(this.$refs['itemRefs'] as HTMLElement[])?.forEach((el) => this.observer?.observe(el));
},
toggleFilter(name: typeof this.currentFilterMode) {
this.currentFilterMode = this.currentFilterMode == name ? 'all' : name;
const vehiclesRef = this.$refs['vehicles'] as HTMLElement;
vehiclesRef.scrollTo({
top: this.scrollTop,
});
},
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 'type':
case 'constructionType':
case 'group': case 'group':
return direction == 1 return direction * v1[this.sorterType].localeCompare(v2[this.sorterType]);
? row1.vehicle[id].localeCompare(row2.vehicle[id])
: row2.vehicle[id].localeCompare(row1.vehicle[id]);
case 'weight': case 'weight':
case 'length': case 'length':
case 'maxSpeed': case 'maxSpeed':
return Math.sign(row1.vehicle[id] - row2.vehicle[id]) * direction; return Math.sign(v1[this.sorterType] - v2[this.sorterType]) * direction;
case 'cargoCount': // case 'cargoCount':
return ( // return (
Math.sign( // Math.sign(
(!isTractionUnit(row1.vehicle) ? row1.vehicle.cargoTypes.length || -1 : -1) - // (!isTractionUnit(v1) ? v1.cargoTypes.length || -1 : -1) -
(!isTractionUnit(row2.vehicle) ? row2.vehicle.cargoTypes.length || -1 : -1) // (!isTractionUnit(row2.vehicle) ? row2.vehicle.cargoTypes.length || -1 : -1)
) * direction // ) * direction
); // );
case 'coldStart': // case 'coldStart':
return ( // return (
((isTractionUnit(row1.vehicle) && row1.vehicle.coldStart ? 1 : -1) - // ((isTractionUnit(v1) && v1.coldStart ? 1 : -1) -
(isTractionUnit(row2.vehicle) && row2.vehicle.coldStart ? 1 : -1)) * // (isTractionUnit(row2.vehicle) && row2.vehicle.coldStart ? 1 : -1)) *
direction // direction
); // );
default: default:
break; return v1.type.localeCompare(v2.type) * direction;
} }
return direction == 1
? row1.vehicle.type.localeCompare(row2.vehicle.type)
: row2.vehicle.type.localeCompare(row1.vehicle.type);
}, },
}, },
computed: { computed: {
computedTableData(): IWikiRow[] { computedVehicles() {
return this.store.vehicleDataList return this.store.vehicleDataList
.filter( .filter(
(vehicle) => (vehicle) =>
new RegExp(`${this.searchedVehicleTypeName.trim()}`, 'i').test(vehicle.type) && new RegExp(`${this.searchedVehicleTypeName.trim()}`, 'i').test(vehicle.type) &&
(this.currentFilterMode == 'all' || (this.filterType == 'vehicles-all' ||
(this.currentFilterMode == 'tractions' && isTractionUnit(vehicle)) || (this.filterType == 'vehicles-traction' && isTractionUnit(vehicle)) ||
(this.currentFilterMode == 'carriages' && !isTractionUnit(vehicle))) (this.filterType == 'vehicles-wagon' && !isTractionUnit(vehicle)))
) )
.map((vehicle) => ({ .sort((v1, v2) => this.sortVehicles(v1, v2));
vehicle,
showImage: false,
show:
new RegExp(`${this.searchedVehicleTypeName.trim()}`, 'i').test(vehicle.type) &&
(this.currentFilterMode == 'all' ||
(this.currentFilterMode == 'tractions' && isTractionUnit(vehicle)) ||
(this.currentFilterMode == 'carriages' && !isTractionUnit(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() { areTractionVehiclesShown() {
return this.currentFilterMode == 'all' || this.currentFilterMode == 'tractions'; return this.filterType == 'vehicles-all' || this.filterType == 'vehicles-traction';
}, },
areCarriagesShown() { areCarriagesShown() {
return this.currentFilterMode == 'all' || this.currentFilterMode == 'carriages'; return this.filterType == 'vehicles-all' || this.filterType == 'vehicles-wagon';
}, },
}, },
}); });
@@ -323,26 +185,21 @@ export default defineComponent({
<style lang="scss" scoped> <style lang="scss" scoped>
@import '../../styles/tab.scss'; @import '../../styles/tab.scss';
.actions-panel { .actions {
display: flex; display: grid;
justify-content: space-between; grid-template-columns: repeat(auto-fit, minmax(10em, 1fr));
align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.5em; gap: 0.5em;
} }
.actions-panel_vehicles { .actions > label {
display: flex; display: flex;
gap: 0.5em; flex-direction: column;
} gap: 0.25em;
.actions-panel_search { span {
display: flex; color: #ccc;
gap: 0.5em;
input {
width: auto;
} }
} }
@@ -367,80 +224,17 @@ export default defineComponent({
min-height: 75px; min-height: 75px;
cursor: pointer; cursor: pointer;
}
.vehicles > li[data-preview='true'] { &[data-preview='true'] {
background-color: #364165; background-color: #303a58;
}
.vehicles > li > img[data-team-only='true'] {
border: 3px solid green;
}
.table-wrapper {
overflow: auto;
}
.wiki-list table {
border-collapse: collapse;
width: 100%;
thead {
position: sticky;
top: 0;
} }
th { &[data-sponsor-only='true'] {
background-color: #111; box-shadow: 0 0 5px 0 $sponsorColor;
padding: 0.5em;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
} }
tr { &[data-team-only='true'] {
cursor: pointer; box-shadow: 0 0 5px 0 $teamColor;
background-color: #333;
&:nth-child(odd) {
background-color: #444;
}
&:hover {
background-color: #666;
}
}
td {
text-align: center;
padding: 0.25em;
height: 75px;
min-width: 95px;
&[data-sponsor-only='true'] {
color: $sponsorColor;
}
&[data-team-only='true'] {
color: $teamColor;
}
img {
vertical-align: middle;
}
}
}
@media screen and (max-width: $breakpointMd) {
.wiki-list table {
th {
min-width: 100px;
}
img {
max-width: 100px;
}
} }
} }
+18 -7
View File
@@ -162,20 +162,31 @@
}, },
"wiki": { "wiki": {
"title": "LIST OF AVAILABLE VEHICLES", "title": "LIST OF AVAILABLE VEHICLES",
"action-vehicles": "TRACTION UNITS", "labels": {
"action-carriages": "CARRIAGES", "vehicles": "Vehicles",
"search": "Search for a vehicle...", "sort-by": "Sort by",
"header": { "sort-direction": "Sort direction",
"image": "Image", "search-vehicle": "Find a vehicle",
"search-vehicle-placeholder": "Input a vehicle name"
},
"filters": {
"vehicles-all": "all",
"vehicles-traction": "traction units",
"vehicles-wagon": "wagons"
},
"sort-by": {
"type": "Name", "type": "Name",
"group": "Type group", "group": "Type group",
"constructionType": "Construction",
"coldStart": "Cold start",
"length": "Length", "length": "Length",
"weight": "Mass", "weight": "Mass",
"maxSpeed": "Speed", "maxSpeed": "Speed",
"coldStart": "Cold start",
"cargoCount": "Cargo count" "cargoCount": "Cargo count"
}, },
"sort-direction": {
"asc": "ascending",
"desc": "descending"
},
"unit-electric": "EMU", "unit-electric": "EMU",
"unit-diesel": "DMU", "unit-diesel": "DMU",
"loco-diesel": "Diesel locomotive", "loco-diesel": "Diesel locomotive",
+18 -7
View File
@@ -162,20 +162,31 @@
}, },
"wiki": { "wiki": {
"title": "LISTA DOSTĘPNYCH POJAZDÓW", "title": "LISTA DOSTĘPNYCH POJAZDÓW",
"action-vehicles": "POJ. TRAKCYJNE", "labels": {
"action-carriages": "WAGONY", "vehicles": "Pojazdy",
"search": "Wyszukaj pojazd...", "sort-by": "Sortuj wg",
"header": { "sort-direction": "Kierunek sortowania",
"image": "Zdjęcie", "search-vehicle": "Wyszukaj pojazd",
"search-vehicle-placeholder": "Wpisz nazwę pojazdu"
},
"filters": {
"vehicles-all": "wszystkie",
"vehicles-traction": "trakcyjne",
"vehicles-wagon": "wagony"
},
"sort-by": {
"type": "Nazwa", "type": "Nazwa",
"group": "Rodzaj", "group": "Rodzaj",
"constructionType": "Konstrukcja",
"coldStart": "Zimny start",
"length": "Długość", "length": "Długość",
"weight": "Masa", "weight": "Masa",
"maxSpeed": "Prędkość", "maxSpeed": "Prędkość",
"coldStart": "Zimny start",
"cargoCount": "Ładunki" "cargoCount": "Ładunki"
}, },
"sort-direction": {
"asc": "rosnąco",
"desc": "malejąco"
},
"loco-diesel": "Spalinowóz", "loco-diesel": "Spalinowóz",
"loco-electric": "Elektrowóz", "loco-electric": "Elektrowóz",
"unit-electric": "EZT", "unit-electric": "EZT",