From de830b9c1f1ee20aea8fdb3ad28a656a9f1e86b8 Mon Sep 17 00:00:00 2001
From: Qstick <qstick@gmail.com>
Date: Mon, 12 Dec 2022 20:15:04 -0600
Subject: [PATCH] Lazy Loading fuse-worker and fixed some potential timing
 issues

Fixes #1489
Fixes #1463

Co-Authored-By: Taloth <Taloth@users.noreply.github.com>
---
 .../Page/Header/ArtistSearchInput.css         |  10 +-
 .../Page/Header/ArtistSearchInput.js          | 129 +++++++++++++-----
 .../src/Components/Page/Header/fuse.worker.js |  17 ++-
 3 files changed, 114 insertions(+), 42 deletions(-)

diff --git a/frontend/src/Components/Page/Header/ArtistSearchInput.css b/frontend/src/Components/Page/Header/ArtistSearchInput.css
index 95a796c80..bb2b5193c 100644
--- a/frontend/src/Components/Page/Header/ArtistSearchInput.css
+++ b/frontend/src/Components/Page/Header/ArtistSearchInput.css
@@ -4,15 +4,15 @@
 }
 
 .loading {
-  margin-top: 18px;
-  margin-bottom: 18px;
-  text-align: center;
+  position: absolute;
+  display: inline-block;
+  margin-left: 5px;
 }
 
 .ripple {
   composes: ripple from '~Components/Loading/LoadingIndicator.css';
 
-  border: 2px solid var(--toolbarColor);
+  border: 1px solid var(--toolbarColor);
 }
 
 .input {
@@ -91,7 +91,7 @@
 }
 
 .addNewArtistSuggestion {
-  padding: 0 3px;
+  padding: 5px 3px;
   cursor: pointer;
 }
 
diff --git a/frontend/src/Components/Page/Header/ArtistSearchInput.js b/frontend/src/Components/Page/Header/ArtistSearchInput.js
index 948c8b0b0..0abb555e0 100644
--- a/frontend/src/Components/Page/Header/ArtistSearchInput.js
+++ b/frontend/src/Components/Page/Header/ArtistSearchInput.js
@@ -10,9 +10,7 @@ import ArtistSearchResult from './ArtistSearchResult';
 import FuseWorker from './fuse.worker';
 import styles from './ArtistSearchInput.css';
 
-const LOADING_TYPE = 'suggestionsLoading';
 const ADD_NEW_TYPE = 'addNew';
-const workerInstance = new FuseWorker();
 
 class ArtistSearchInput extends Component {
 
@@ -23,6 +21,7 @@ class ArtistSearchInput extends Component {
     super(props, context);
 
     this._autosuggest = null;
+    this._worker = null;
 
     this.state = {
       value: '',
@@ -32,7 +31,23 @@ class ArtistSearchInput extends Component {
 
   componentDidMount() {
     this.props.bindShortcut(shortcuts.ARTIST_SEARCH_INPUT.key, this.focusInput);
-    workerInstance.addEventListener('message', this.onSuggestionsReceived, false);
+  }
+
+  componentWillUnmount() {
+    if (this._worker) {
+      this._worker.removeEventListener('message', this.onSuggestionsReceived, false);
+      this._worker.terminate();
+      this._worker = null;
+    }
+  }
+
+  getWorker() {
+    if (!this._worker) {
+      this._worker = new FuseWorker();
+      this._worker.addEventListener('message', this.onSuggestionsReceived, false);
+    }
+
+    return this._worker;
   }
 
   //
@@ -55,6 +70,15 @@ class ArtistSearchInput extends Component {
     return (
       <div className={styles.sectionTitle}>
         {section.title}
+
+        {
+          section.loading &&
+            <LoadingIndicator
+              className={styles.loading}
+              rippleClassName={styles.ripple}
+              size={20}
+            />
+        }
       </div>
     );
   }
@@ -72,16 +96,6 @@ class ArtistSearchInput extends Component {
       );
     }
 
-    if (item.type === LOADING_TYPE) {
-      return (
-        <LoadingIndicator
-          className={styles.loading}
-          rippleClassName={styles.ripple}
-          size={30}
-        />
-      );
-    }
-
     return (
       <ArtistSearchResult
         {...item.item}
@@ -98,7 +112,8 @@ class ArtistSearchInput extends Component {
   reset() {
     this.setState({
       value: '',
-      suggestions: []
+      suggestions: [],
+      loading: false
     });
   }
 
@@ -114,6 +129,15 @@ class ArtistSearchInput extends Component {
   };
 
   onKeyDown = (event) => {
+    if (event.shiftKey || event.altKey || event.ctrlKey) {
+      return;
+    }
+
+    if (event.key === 'Escape') {
+      this.reset();
+      return;
+    }
+
     if (event.key !== 'Tab' && event.key !== 'Enter') {
       return;
     }
@@ -154,35 +178,74 @@ class ArtistSearchInput extends Component {
   };
 
   onSuggestionsFetchRequested = ({ value }) => {
-    this.setState({
-      suggestions: [
-        {
-          type: LOADING_TYPE,
-          title: value
-        }
-      ]
-    });
+    if (!this.state.loading) {
+      this.setState({
+        loading: true
+      });
+    }
+
     this.requestSuggestions(value);
   };
 
   requestSuggestions = _.debounce((value) => {
-    const payload = {
-      value,
-      artists: this.props.artists
-    };
+    if (!this.state.loading) {
+      return;
+    }
 
-    workerInstance.postMessage(payload);
-  }, 250);
+    const requestLoading = this.state.requestLoading;
 
-  onSuggestionsReceived = (message) => {
     this.setState({
-      suggestions: message.data
+      requestValue: value,
+      requestLoading: true
     });
+
+    if (!requestLoading) {
+      const payload = {
+        value,
+        artists: this.props.artists
+      };
+
+      this.getWorker().postMessage(payload);
+    }
+  }, 250);
+
+  onSuggestionsReceived = (message) => {
+    const {
+      value,
+      suggestions
+    } = message.data;
+
+    if (!this.state.loading) {
+      this.setState({
+        requestValue: null,
+        requestLoading: false
+      });
+    } else if (value === this.state.requestValue) {
+      this.setState({
+        suggestions,
+        requestValue: null,
+        requestLoading: false,
+        loading: false
+      });
+    } else {
+      this.setState({
+        suggestions,
+        requestLoading: true
+      });
+
+      const payload = {
+        value: this.state.requestValue,
+        artists: this.props.artists
+      };
+
+      this.getWorker().postMessage(payload);
+    }
   };
 
   onSuggestionsClearRequested = () => {
     this.setState({
-      suggestions: []
+      suggestions: [],
+      loading: false
     });
   };
 
@@ -200,14 +263,16 @@ class ArtistSearchInput extends Component {
   render() {
     const {
       value,
+      loading,
       suggestions
     } = this.state;
 
     const suggestionGroups = [];
 
-    if (suggestions.length) {
+    if (suggestions.length || loading) {
       suggestionGroups.push({
         title: 'Existing Artist',
+        loading,
         suggestions
       });
     }
diff --git a/frontend/src/Components/Page/Header/fuse.worker.js b/frontend/src/Components/Page/Header/fuse.worker.js
index 76e655a1b..3b4b13161 100644
--- a/frontend/src/Components/Page/Header/fuse.worker.js
+++ b/frontend/src/Components/Page/Header/fuse.worker.js
@@ -3,9 +3,9 @@ import Fuse from 'fuse.js';
 const fuseOptions = {
   shouldSort: true,
   includeMatches: true,
+  ignoreLocation: true,
   threshold: 0.3,
-  location: 0,
-  distance: 100,
+  maxPatternLength: 32,
   minMatchCharLength: 1,
   keys: [
     'artistName',
@@ -47,7 +47,7 @@ function getSuggestions(artists, value) {
   return suggestions;
 }
 
-self.addEventListener('message', (e) => {
+onmessage = function(e) {
   if (!e) {
     return;
   }
@@ -57,5 +57,12 @@ self.addEventListener('message', (e) => {
     value
   } = e.data;
 
-  self.postMessage(getSuggestions(artists, value));
-});
+  const suggestions = getSuggestions(artists, value);
+
+  const results = {
+    value,
+    suggestions
+  };
+
+  self.postMessage(results);
+};