aboutsummaryrefslogtreecommitdiff
path: root/src-vue/src
diff options
context:
space:
mode:
authorGeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com>2023-05-15 17:50:39 +0200
committerGitHub <noreply@github.com>2023-05-15 17:50:39 +0200
commit5edffe63d3b6f503ad29da08f281ea88682f5f90 (patch)
treed11d54223f2e2a0fefcf53f9506cf3aa0edbfe30 /src-vue/src
parent002d967aac31a7c7b883cc6f2003e2db7afa6247 (diff)
downloadFlightCore-5edffe63d3b6f503ad29da08f281ea88682f5f90.tar.gz
FlightCore-5edffe63d3b6f503ad29da08f281ea88682f5f90.zip
feat: Make search fuzzy (#342)
* feat: Enable fuzzy search for Thunderstore mods * feat: Enable fuzzy search for pull requests * refactor: Move filter into own util file * docs: Add comment explaining function in detail
Diffstat (limited to 'src-vue/src')
-rw-r--r--src-vue/src/components/PullRequestsSelector.vue13
-rw-r--r--src-vue/src/utils/filter.ts26
-rw-r--r--src-vue/src/views/mods/ThunderstoreModsView.vue9
3 files changed, 37 insertions, 11 deletions
diff --git a/src-vue/src/components/PullRequestsSelector.vue b/src-vue/src/components/PullRequestsSelector.vue
index 4bc8f9f5..fe103edc 100644
--- a/src-vue/src/components/PullRequestsSelector.vue
+++ b/src-vue/src/components/PullRequestsSelector.vue
@@ -80,6 +80,7 @@
import { defineComponent } from 'vue'
import { PullRequestType } from '../../../src-tauri/bindings/PullRequestType';
import { PullsApiResponseElement } from '../../../src-tauri/bindings/PullsApiResponseElement';
+import { fuzzy_filter } from "../utils/filter";
export default defineComponent({
name: 'PullRequestsSelector',
@@ -101,10 +102,8 @@ export default defineComponent({
}
return this.pull_requests_launcher.filter(pr =>
- // Check PR id
- pr.number.toString().indexOf(this.launcherSearch) !== -1
- // Check PR title
- || pr.title.toLowerCase().indexOf(this.launcherSearch.toLowerCase()) !== -1);
+ // Check PR id and title
+ fuzzy_filter(pr.number.toString(), this.launcherSearch) || fuzzy_filter(pr.title, this.launcherSearch));
},
filtered_mods_pull_requests(): PullsApiResponseElement[] {
if (this.modsSearch.length === 0) {
@@ -112,10 +111,8 @@ export default defineComponent({
}
return this.pull_requests_mods.filter(pr =>
- // Check PR id
- pr.number.toString().indexOf(this.modsSearch) !== -1
- // Check PR title
- || pr.title.toLowerCase().indexOf(this.modsSearch.toLowerCase()) !== -1);
+ // Check PR id and title
+ fuzzy_filter(pr.number.toString(), this.modsSearch) || fuzzy_filter(pr.title, this.modsSearch));
},
},
methods: {
diff --git a/src-vue/src/utils/filter.ts b/src-vue/src/utils/filter.ts
new file mode 100644
index 00000000..b85b9623
--- /dev/null
+++ b/src-vue/src/utils/filter.ts
@@ -0,0 +1,26 @@
+/**
+ * Implements a fuzzy filter
+ * Iterates through chars of `search_term` and checks if each char exists in consecutive order in `text`.
+ * For example, this means that `text="Gecko"` and `search_term="geo"` will return `true`
+ * but using `text="Gecko"` and `search_term="goe"` will return `false`
+ *
+ * Implements a subset of "fuzzy string searching"
+ * https://en.wikipedia.org/wiki/Approximate_string_matching
+ */
+function fuzzy_filter(text: string, search_term: string): boolean {
+ const lowercase_text = text.toLowerCase();
+ const lowercase_search_term = search_term.toLowerCase();
+
+ let previousIndex = -1;
+ for (let i = 0; i < lowercase_search_term.length; i++) {
+ const char = lowercase_search_term[i];
+ const currentIndex = lowercase_text.indexOf(char, previousIndex + 1);
+ if (currentIndex === -1) {
+ return false;
+ }
+ previousIndex = currentIndex;
+ }
+
+ return true;
+}
+export { fuzzy_filter };
diff --git a/src-vue/src/views/mods/ThunderstoreModsView.vue b/src-vue/src/views/mods/ThunderstoreModsView.vue
index 410af5f3..58a00367 100644
--- a/src-vue/src/views/mods/ThunderstoreModsView.vue
+++ b/src-vue/src/views/mods/ThunderstoreModsView.vue
@@ -51,6 +51,7 @@ import ThunderstoreModCard from "../../components/ThunderstoreModCard.vue";
import { ElScrollbar, ScrollbarInstance } from "element-plus";
import { SortOptions } from "../../utils/SortOptions.d";
import { ThunderstoreModVersion } from "../../../../src-tauri/bindings/ThunderstoreModVersion";
+import { fuzzy_filter } from "../../utils/filter";
export default defineComponent({
name: "ThunderstoreModsView",
@@ -79,9 +80,11 @@ export default defineComponent({
return this.mods.filter((mod: ThunderstoreMod) => {
// Filter with search words (only if search field isn't empty)
const inputMatches: boolean = this.searchValue.length === 0
- || (mod.name.toLowerCase().includes(this.searchValue)
- || mod.owner.toLowerCase().includes(this.searchValue)
- || mod.versions[0].description.toLowerCase().includes(this.searchValue));
+ || (
+ fuzzy_filter(mod.name, this.searchValue) ||
+ fuzzy_filter(mod.owner, this.searchValue) ||
+ mod.versions[0].description.toLowerCase().includes(this.searchValue)
+ );
// Filter with categories (only if some categories are selected)
const categoriesMatch: boolean = this.selectedCategories.length === 0