Skip to content

Commit

Permalink
Merge pull request #1337 from ebkr/better-online-filtering
Browse files Browse the repository at this point in the history
Improved online category filtering
  • Loading branch information
ebkr authored May 31, 2024
2 parents 5a1b467 + 1d57549 commit 5e93b1d
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 110 deletions.
123 changes: 56 additions & 67 deletions src/components/modals/CategoryFilterModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,30 @@
<p class="card-header-title">Filter mod categories</p>
</template>
<template v-slot:body>
<div class="input-group">
<label>Categories</label>
<select class="select select--content-spacing" @change="selectCategory($event)">
<option selected disabled>
Select a category
</option>
<option v-for="(key, index) in unselectedCategories" :key="`category--${key}-${index}`">
{{ key }}
</option>
</select>
</div>
<br/>
<div class="input-group">
<label>Selected categories:</label>
<div class="field has-addons" v-if="selectedCategories.length > 0">
<div class="control" v-for="(key, index) in selectedCategories" :key="`${key}-${index}`">
<span class="block margin-right">
<a href="#" @click="unselectCategory(key)">
<span class="tags has-addons">
<span class="tag">{{ key }}</span>
<span class="tag is-danger">
<i class="fas fa-times"></i>
</span>
</span>
</a>
</span>
</div>
</div>
<div class="field has-addons" v-else>
<span class="tags">
<span class="tag">No categories selected</span>
</span>
</div>
<div>
<CategorySelectorModal
title="Mods must contain at least one of these categories"
:selected-categories="selectedCategoriesCompareOne"
:selectable-categories="unselectedCategories"
@selected-category="selectCompareOneCategory"
@deselected-category="unselectCategory"
/>
<hr/>
<CategorySelectorModal
title="Mods must contain all of these categories"
:selected-categories="selectedCategoriesCompareAll"
:selectable-categories="unselectedCategories"
@selected-category="selectCompareAllCategory"
@deselected-category="unselectCategory"
/>
<hr/>
<CategorySelectorModal
title="Mods cannot contain any of these categories"
:selected-categories="selectedCategoriesToExclude"
:selectable-categories="unselectedCategories"
@selected-category="selectToExcludeCategory"
@deselected-category="unselectCategory"
/>
</div>
<hr/>
<div>
Expand All @@ -61,22 +52,6 @@
<label for="showDeprecatedCheckbox">Show deprecated mods</label>
</div>
</div>
<br/>
<div>
<div v-for="(key, index) in categoryFilterValues" :key="`cat-filter-${key}-${index}`">
<input
name="categoryFilterCondition"
type="radio"
:id="`cat-filter-${key}-${index}`"
:value=key
:checked="index === 0 ? true : undefined" v-model="categoryFilterMode"
/>
<label :for="`cat-filter-${key}-${index}`">
<span class="margin-right margin-right--half-width" />
{{ key }}
</label>
</div>
</div>
</template>
<template v-slot:footer>
<button class="button is-info" @click="close">
Expand All @@ -90,10 +65,10 @@
import { Component, Vue } from "vue-property-decorator";
import { Modal } from '../../components/all';
import CategoryFilterMode from '../../model/enums/CategoryFilterMode';
import CategorySelectorModal from '../../components/modals/CategorySelectorModal.vue';
@Component({
components: { Modal }
components: { CategorySelectorModal, Modal }
})
export default class CategoryFilterModal extends Vue {
get allowNsfw(): boolean {
Expand All @@ -104,18 +79,6 @@ export default class CategoryFilterModal extends Vue {
this.$store.commit("modFilters/setAllowNsfw", value);
}
get categoryFilterMode(): CategoryFilterMode {
return this.$store.state.modFilters.categoryFilterMode;
}
set categoryFilterMode(value: CategoryFilterMode) {
this.$store.commit("modFilters/setCategoryFilterMode", value);
}
get categoryFilterValues() {
return Object.values(CategoryFilterMode);
}
get showDeprecatedPackages(): boolean {
return this.$store.state.modFilters.showDeprecatedPackages;
}
Expand All @@ -136,17 +99,43 @@ export default class CategoryFilterModal extends Vue {
return this.$store.state.modals.isCategoryFilterModalOpen;
}
selectCategory(event: Event) {
selectCompareOneCategory(event: Event) {
if (!(event.target instanceof HTMLSelectElement)) {
return;
}
this.$store.commit("modFilters/selectCategoryToCompareOne", event.target.value);
event.target.selectedIndex = 0;
}
selectCompareAllCategory(event: Event) {
if (!(event.target instanceof HTMLSelectElement)) {
return;
}
this.$store.commit("modFilters/selectCategoryToCompareAll", event.target.value);
event.target.selectedIndex = 0;
}
selectToExcludeCategory(event: Event) {
if (!(event.target instanceof HTMLSelectElement)) {
return;
}
this.$store.commit("modFilters/selectCategory", event.target.value);
this.$store.commit("modFilters/selectCategoryToExclude", event.target.value);
event.target.selectedIndex = 0;
}
get selectedCategories(): string[] {
return this.$store.state.modFilters.selectedCategories;
get selectedCategoriesCompareOne(): string[] {
return this.$store.state.modFilters.selectedCategoriesCompareOne;
}
get selectedCategoriesCompareAll(): string[] {
return this.$store.state.modFilters.selectedCategoriesCompareAll;
}
get selectedCategoriesToExclude(): string[] {
return this.$store.state.modFilters.selectedCategoriesToExclude;
}
unselectCategory(category: string) {
Expand Down
56 changes: 56 additions & 0 deletions src/components/modals/CategorySelectorModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<template>
<div class="input-group">
<label>{{ title }}:</label>
<div class="input-group margin-bottom">
<select class="select select--content-spacing" @change="emitSelected">
<option selected disabled>
Select a category
</option>
<option v-for="(key, index) in selectableCategories" :key="`category--${key}-${index}`">
{{ key }}
</option>
</select>
</div>
<div class="tags has-addons" v-if="selectedCategories.length > 0">
<span class="margin-right" v-for="(key, index) in selectedCategories" :key="`${key}-${index}`">
<a href="#" @click="emitDeselected(key)">
<div class="tag has-addons">
<span>{{ key }}</span>
</div>
<span class="tag is-delete is-danger">&nbsp;</span>
</a>
</span>
</div>
<div class="field has-addons" v-else>
<span class="tags">
<span class="tag">No categories selected</span>
</span>
</div>
</div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class ChangeSelectorModal extends Vue {
@Prop({required: true})
private title!: string;
@Prop({required: true})
private selectedCategories!: string[]
@Prop({required: true})
private selectableCategories!: string[]
emitSelected(event: Event) {
this.$emit("selected-category", event);
}
emitDeselected(key: string) {
this.$emit("deselected-category", key);
}
}
</script>
48 changes: 27 additions & 21 deletions src/components/views/OnlineModView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,6 @@
<script lang="ts">
import Component from 'vue-class-component';
import { Vue, Watch } from 'vue-property-decorator';
import CategoryFilterMode from '../../model/enums/CategoryFilterMode';
import SortingDirection from '../../model/enums/SortingDirection';
import SortingStyle from '../../model/enums/SortingStyle';
import ManifestV2 from '../../model/ManifestV2';
Expand Down Expand Up @@ -134,42 +132,50 @@ export default class OnlineModView extends Vue {
}
@Watch("$store.state.modFilters.allowNsfw")
@Watch("$store.state.modFilters.categoryFilterMode")
@Watch("$store.state.modFilters.selectedCategories")
@Watch("$store.state.modFilters.selectedCategoriesCompareOne")
@Watch("$store.state.modFilters.selectedCategoriesCompareAll")
@Watch("$store.state.modFilters.selectedCategoriesToExclude")
@Watch("$store.state.modFilters.showDeprecatedPackages")
filterThunderstoreModList() {
const allowNsfw = this.$store.state.modFilters.allowNsfw;
const categoryFilterMode = this.$store.state.modFilters.categoryFilterMode;
const filterCategories = this.$store.state.modFilters.selectedCategories;
const filterCategoriesToCompareOne = this.$store.state.modFilters.selectedCategoriesCompareOne;
const filterCategoriesToCompareAll = this.$store.state.modFilters.selectedCategoriesCompareAll;
const filterCategoriesToExclude = this.$store.state.modFilters.selectedCategoriesToExclude;
const showDeprecatedPackages = this.$store.state.modFilters.showDeprecatedPackages;
this.searchableThunderstoreModList = this.sortedThunderstoreModList;
let searchableList = this.sortedThunderstoreModList;
const searchKeys = SearchUtils.makeKeys(this.thunderstoreSearchFilter);
if (searchKeys.length > 0) {
this.searchableThunderstoreModList = this.sortedThunderstoreModList.filter((x: ThunderstoreMod) => {
searchableList = this.sortedThunderstoreModList.filter((x: ThunderstoreMod) => {
return SearchUtils.isSearched(searchKeys, x.getFullName(), x.getVersions()[0].getDescription())
});
}
if (!allowNsfw) {
this.searchableThunderstoreModList = this.searchableThunderstoreModList.filter(mod => !mod.getNsfwFlag());
searchableList = searchableList.filter(mod => !mod.getNsfwFlag());
}
if (!showDeprecatedPackages) {
this.searchableThunderstoreModList = this.searchableThunderstoreModList.filter(
searchableList = searchableList.filter(
mod => !this.$store.state.tsMods.deprecated.get(mod.getFullName())
);
}
if (filterCategories.length > 0) {
this.searchableThunderstoreModList = this.searchableThunderstoreModList.filter((x: ThunderstoreMod) => {
switch(categoryFilterMode) {
case CategoryFilterMode.OR:
return filterCategories.some((category: string) => x.getCategories().includes(category));
case CategoryFilterMode.AND:
return filterCategories.every((category: string) => x.getCategories().includes(category));
case CategoryFilterMode.EXCLUDE:
return !filterCategories.some((category: string) => x.getCategories().includes(category));
}
})
// Category filters
if (filterCategoriesToExclude.length > 0) {
searchableList = searchableList.filter((x: ThunderstoreMod) =>
!filterCategoriesToExclude.some((category: string) => x.getCategories().includes(category)))
}
if (filterCategoriesToCompareOne.length > 0) {
searchableList = searchableList.filter((x: ThunderstoreMod) =>
filterCategoriesToCompareOne.some((category: string) => x.getCategories().includes(category)))
}
if (filterCategoriesToCompareAll.length > 0) {
searchableList = searchableList.filter((x: ThunderstoreMod) =>
filterCategoriesToCompareAll.every((category: string) => x.getCategories().includes(category)))
}
this.searchableThunderstoreModList = [...searchableList];
// Update results
this.changePage();
}
Expand Down
7 changes: 0 additions & 7 deletions src/model/enums/CategoryFilterMode.ts

This file was deleted.

43 changes: 28 additions & 15 deletions src/store/modules/ModFilterModule.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { GetterTree } from 'vuex';

import { State as RootState } from '../index';
import CategoryFilterMode from '../../model/enums/CategoryFilterMode';

interface State {
allowNsfw: boolean;
categoryFilterMode: CategoryFilterMode;
selectedCategories: string[];
selectedCategoriesCompareOne: string[];
selectedCategoriesCompareAll: string[];
selectedCategoriesToExclude: string[];
showDeprecatedPackages: boolean;
}

Expand All @@ -18,39 +18,52 @@ export default {

state: (): State => ({
allowNsfw: false,
categoryFilterMode: CategoryFilterMode.OR,
selectedCategories: [],
selectedCategoriesCompareOne: [],
selectedCategoriesCompareAll: [],
selectedCategoriesToExclude: [],
showDeprecatedPackages: false
}),

getters: <GetterTree<State, RootState>>{
unselectedCategories (state, _getters, _rootState, rootGetters) {
const categories: string[] = rootGetters['tsMods/categories'];
return categories.filter((c: string) => !state.selectedCategories.includes(c));
const selectedCategories = [
...state.selectedCategoriesCompareOne,
...state.selectedCategoriesCompareAll,
...state.selectedCategoriesToExclude
]
return categories.filter((c: string) => !selectedCategories.includes(c));
}
},

mutations: {
reset: function(state: State) {
state.allowNsfw = false;
state.categoryFilterMode = CategoryFilterMode.OR;
state.selectedCategories = [];
state.selectedCategoriesCompareOne = [];
state.selectedCategoriesCompareAll = [];
state.selectedCategoriesToExclude = [];
},

selectCategory: function(state: State, category: string) {
state.selectedCategories = [...state.selectedCategories, category];
selectCategoryToCompareOne: function(state: State, category: string) {
state.selectedCategoriesCompareOne = [...state.selectedCategoriesCompareOne, category];
},

setAllowNsfw: function(state: State, value: boolean) {
state.allowNsfw = value;
selectCategoryToCompareAll: function(state: State, category: string) {
state.selectedCategoriesCompareAll = [...state.selectedCategoriesCompareAll, category];
},

selectCategoryToExclude: function(state: State, category: string) {
state.selectedCategoriesToExclude = [...state.selectedCategoriesToExclude, category];
},

setCategoryFilterMode: function(state: State, value: CategoryFilterMode) {
state.categoryFilterMode = value;
setAllowNsfw: function(state: State, value: boolean) {
state.allowNsfw = value;
},

unselectCategory: function(state: State, category: string) {
state.selectedCategories = state.selectedCategories.filter((c) => c !== category);
state.selectedCategoriesCompareOne = state.selectedCategoriesCompareOne.filter((c) => c !== category);
state.selectedCategoriesCompareAll = state.selectedCategoriesCompareAll.filter((c) => c !== category);
state.selectedCategoriesToExclude = state.selectedCategoriesToExclude.filter((c) => c !== category);
},

setShowDeprecatedPackages: function(state: State, value: boolean) {
Expand Down

0 comments on commit 5e93b1d

Please sign in to comment.