diff options
Diffstat (limited to 'src-vue/src/views/ThunderstoreModsView.vue')
-rw-r--r-- | src-vue/src/views/ThunderstoreModsView.vue | 158 |
1 files changed, 106 insertions, 52 deletions
diff --git a/src-vue/src/views/ThunderstoreModsView.vue b/src-vue/src/views/ThunderstoreModsView.vue index 5b74bd1c..5733d30b 100644 --- a/src-vue/src/views/ThunderstoreModsView.vue +++ b/src-vue/src/views/ThunderstoreModsView.vue @@ -3,7 +3,7 @@ <div v-if="mods.length === 0" class="fc__changelog__container"> <el-progress :show-text="false" :percentage="50" :indeterminate="true" /> </div> - <el-scrollbar v-else class="container"> + <el-scrollbar v-else class="container" ref="scrollbar"> <div class="card-container"> <!-- Search filters --> <div class="filter_container"> @@ -12,6 +12,16 @@ <div v-if="userIsTyping" class="modMessage search"> Searching mods... </div> + + <!-- Pagination --> + <el-pagination + v-if="shouldDisplayPagination" + :currentPage="currentPageIndex + 1" + layout="prev, pager, next" + :page-size="modsPerPage" + :total="modsList.length" + @current-change="(e: number) => currentPageIndex = e - 1" + /> </div> <!-- Message displayed if no mod matched searched words --> @@ -21,16 +31,32 @@ </div> <!-- Mod cards --> - <thunderstore-mod-card v-for="mod of modsList" v-bind:key="mod.name" :mod="mod" /> + <thunderstore-mod-card v-for="mod of currentPageMods" v-bind:key="mod.name" :mod="mod" /> + </div> + + <!-- Bottom pagination --> + <div class="card-container"> + <div class="filter_container"> + <el-pagination + class="fc_bottom__pagination" + v-if="shouldDisplayPagination" + :currentPage="currentPageIndex + 1" + layout="prev, pager, next" + :page-size="modsPerPage" + :total="modsList.length" + @current-change="onBottomPaginationChange" + /> + </div> </div> </el-scrollbar> </div> </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { defineComponent, ref } from 'vue'; import { ThunderstoreMod } from "../utils/thunderstore/ThunderstoreMod"; import ThunderstoreModCard from "../components/ThunderstoreModCard.vue"; +import { ElScrollbar, ScrollbarInstance } from "element-plus"; export default defineComponent({ name: "ThunderstoreModsView", @@ -42,68 +68,69 @@ export default defineComponent({ mods(): ThunderstoreMod[] { return this.$store.state.thunderstoreMods; }, + filteredMods(): ThunderstoreMod[] { + if (this.searchValue.length === 0) { + return this.mods; + } + + return this.mods.filter((mod: ThunderstoreMod) => { + return mod.name.toLowerCase().includes(this.searchValue) + || mod.owner.toLowerCase().includes(this.searchValue) + || mod.versions[0].description.toLowerCase().includes(this.searchValue); + }); + }, modsList(): ThunderstoreMod[] { - return this.input.length === 0 || this.userIsTyping ? this.mods : this.filteredMods; + return this.input.length !== 0 || this.userIsTyping ? this.filteredMods : this.mods; + }, + modsPerPage(): number { + return parseInt(this.$store.state.mods_per_page); + }, + currentPageMods(): ThunderstoreMod[] { + // User might want to display all mods on one page. + const perPageValue = this.modsPerPage != 0 ? this.modsPerPage : this.modsList.length; + + const startIndex = this.currentPageIndex * perPageValue; + const endIndexCandidate = startIndex + perPageValue; + const endIndex = endIndexCandidate > this.modsList.length ? this.modsList.length : endIndexCandidate; + return this.modsList.slice(startIndex, endIndex); + }, + shouldDisplayPagination(): boolean { + return this.modsPerPage != 0 && this.modsList.length > this.modsPerPage; } }, data() { return { + // This is the model for the search input. input: '', - filteredMods: [] as ThunderstoreMod[], + // This is the treated value of search input + searchValue: '', + modsBeingInstalled: [] as string[], userIsTyping: false, - debouncedSearch: this.debounce((i: string) => this.filterMods(i)) + + currentPageIndex: 0 }; }, methods: { /** - * This is a debounced version of the filterMods method, that calls - * filterMods when user has stopped typing in the search bar (i.e. - * waits 300ms). - * It allows not to trigger filtering method (which is costly) each - * time user inputs a character. - */ - onFilterTextChange (searchString: string) { - this.debouncedSearch(searchString); - }, - - /** * This method is called each time search input is modified, and - * filters mods matching the input string. + * triggered filtered mods recomputing by updating the `searchValue` + * variable. * * This converts research string and all researched fields to * lower case, to match mods regardless of font case. */ - filterMods(value: string) { - if (value === '') { - this.filteredMods = []; - return; - } - - const searchValue = value.toLowerCase(); - - this.filteredMods = this.mods.filter((mod: ThunderstoreMod) => { - return mod.name.toLowerCase().includes(searchValue) - || mod.owner.toLowerCase().includes(searchValue) - || mod.versions[0].description.toLowerCase().includes(searchValue); - }); + onFilterTextChange(value: string) { + this.currentPageIndex = 0; + this.searchValue = value.toLowerCase(); }, /** - * This debounces a method, i.e. it prevents input method from being called - * multiple times in a short period of time. - * Stolen from https://www.freecodecamp.org/news/javascript-debounce-example/ + * This updates current pagination and scrolls view to the top. */ - debounce (func: Function, timeout = 200) { - let timer: any; - return (...args: any) => { - this.userIsTyping = true; - clearTimeout(timer); - timer = setTimeout(() => { - this.userIsTyping = false; - func.apply(this, args); - }, timeout); - }; + onBottomPaginationChange(index: number) { + this.currentPageIndex = index - 1; + (this.$refs.scrollbar as ScrollbarInstance).scrollTo({ top: 0, behavior: 'smooth' }); } } }); @@ -119,14 +146,6 @@ export default defineComponent({ user-select: none !important; } -.filter_container { - margin: 5px; -} - -.el-input { - max-width: 300px; -} - .search { display: inline-block; margin: 0 0 0 10px !important; @@ -141,6 +160,11 @@ export default defineComponent({ margin: 0 auto; } +.fc_bottom__pagination { + padding-bottom: 20px !important; + padding-right: 10px; +} + /* Card container dynamic size */ @media (max-width: 1000px) { .card-container { @@ -152,6 +176,11 @@ export default defineComponent({ .card-container { width: 574px; } + + .el-pagination { + float: none; + margin-top: 5px; + } } @media (max-width: 624px) { @@ -160,10 +189,35 @@ export default defineComponent({ } } +.filter_container { + margin-bottom: 10px; +} + +.el-input { + max-width: 200px; +} + +@media (min-width: 812px) { + .filter_container { + margin: 5px auto; + padding: 0 5px; + max-width: 1000px; + } + + .el-pagination { + float: right; + margin: 0; + } +} + @media (min-width: 1000px) { .card-container { width: 940px; } + + .el-input { + max-width: 300px; + } } @media (min-width: 1188px) { |