Explorar o código

refactor: started working on filter autosuggest for AdvancedTable and genres/artists

Kristian Vos %!s(int64=3) %!d(string=hai) anos
pai
achega
aac4d6bfe7

+ 68 - 1
backend/logic/actions/songs.js

@@ -1627,7 +1627,6 @@ export default {
 	 * @param youtubeId - the youtube id
 	 * @param cb
 	 */
-
 	getOwnSongRatings: isLoginRequired(async function getOwnSongRatings(session, youtubeId, cb) {
 		const playlistModel = await DBModule.runJob("GET_MODEL", { modelName: "playlist" }, this);
 		const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this);
@@ -1699,5 +1698,73 @@ export default {
 				});
 			}
 		);
+	}),
+
+	/**
+	 * Gets a list of all genres
+	 *
+	 * @param session
+	 * @param cb
+	 */
+	getGenres: isAdminRequired(function getModule(session, cb) {
+		async.waterfall(
+			[
+				next => {
+					SongsModule.runJob("GET_GENRES", this).then(res => {
+						next(null, res.genres);
+					}).catch(next);
+				}
+			],
+			async (err, genres) => {
+				if (err && err !== true) {
+					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+					this.log("ERROR", "GET_GENRES", `User ${session.userId} failed to get genres. '${err}'`);
+					cb({ status: "error", message: err });
+				} else {
+					this.log("SUCCESS", "GET_GENRES", `User ${session.userId} has successfully got the genres.`);
+					cb({
+						status: "success",
+						message: "Successfully got genres.",
+						data: {
+							genres
+						}
+					});
+				}
+			}
+		);
+	}),
+
+	/**
+	 * Gets a list of all artists
+	 *
+	 * @param session
+	 * @param cb
+	 */
+	 getArtists: isAdminRequired(function getModule(session, cb) {
+		async.waterfall(
+			[
+				next => {
+					SongsModule.runJob("GET_ARTISTS", this).then(res => {
+						next(null, res.artists);
+					}).catch(next);
+				}
+			],
+			async (err, artists) => {
+				if (err && err !== true) {
+					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+					this.log("ERROR", "GET_ARTISTS", `User ${session.userId} failed to get artists. '${err}'`);
+					cb({ status: "error", message: err });
+				} else {
+					this.log("SUCCESS", "GET_ARTISTS", `User ${session.userId} has successfully got the artists.`);
+					cb({
+						status: "success",
+						message: "Successfully got artists.",
+						data: {
+							artists
+						}
+					});
+				}
+			}
+		);
 	})
 };

+ 42 - 0
backend/logic/songs.js

@@ -1467,6 +1467,48 @@ class _SongsModule extends CoreClass {
 				.catch(reject);
 		});
 	}
+
+	/**
+	 * Gets a list of all genres
+	 *
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	 GET_GENRES() {
+		return new Promise((resolve, reject) => {
+			async.waterfall(
+				[
+					next => {
+						SongsModule.SongModel.distinct("genres", next);
+					}
+				],
+				(err, genres) => {
+					if (err) reject(err);
+					resolve({ genres });
+				}
+			);
+		});
+	}
+
+	/**
+	 * Gets a list of all artists
+	 *
+	 * @returns {Promise} - returns promise (reject, resolve)
+	 */
+	 GET_ARTISTS() {
+		return new Promise((resolve, reject) => {
+			async.waterfall(
+				[
+					next => {
+						SongsModule.SongModel.distinct("artists", next);
+					}
+				],
+				(err, artists) => {
+					if (err) reject(err);
+					resolve({ artists });
+				}
+			);
+		});
+	}
 }
 
 export default new _SongsModule();

+ 86 - 3
frontend/src/components/AdvancedTable.vue

@@ -133,7 +133,7 @@
 										</option>
 									</select>
 								</div>
-								<p v-else class="control is-expanded">
+								<div v-else class="control is-expanded">
 									<input
 										v-if="
 											filter.filterType.name &&
@@ -166,8 +166,53 @@
 										placeholder="Search value"
 										:disabled="!filter.filterType"
 										@keydown.enter="applyFilterAndGetData()"
+										@blur="blurFilterInput(filter)"
+										@focus="focusFilterInput(filter)"
+										@keydown="keydownFilterInput(filter)"
 									/>
-								</p>
+									<div
+										class="autosuggest-container"
+										v-if="
+											filter.filter.autocomplete &&
+											(autosuggest.inputFocussed[
+												filter.filter.name
+											] ||
+												autosuggest.containerFocussed[
+													filter.filter.name
+												]) &&
+											autosuggest.items[
+												filter.filter.name
+											]?.length > 0
+										"
+										@mouseover="
+											focusFilterAutosuggestContainer(
+												filter
+											)
+										"
+										@mouseleave="
+											blurFilterAutosuggestContainer(
+												filter
+											)
+										"
+									>
+										<span
+											class="autosuggest-item"
+											tabindex="0"
+											@click="
+												selectAutosuggestItem(
+													index,
+													filter,
+													item
+												)
+											"
+											v-for="item in autosuggest.items[
+												filter.filter.name
+											]"
+											:key="item"
+											>{{ item }}
+										</span>
+									</div>
+								</div>
 								<div class="control">
 									<button
 										class="button material-icons is-danger"
@@ -809,7 +854,16 @@ export default {
 			showColumnsDropdown: false,
 			lastColumnResizerTapped: null,
 			lastColumnResizerTappedDate: 0,
-			lastBulkActionsTappedDate: 0
+			lastBulkActionsTappedDate: 0,
+			autosuggest: {
+				inputFocussed: {},
+				containerFocussed: {},
+				keydownInputTimeout: {},
+				items: {},
+				allItems: {
+					genres: ["edm", "pop", "test"]
+				}
+			}
 		};
 	},
 	computed: {
@@ -1554,6 +1608,35 @@ export default {
 				selected: false,
 				removed: true
 			};
+		},
+		blurFilterInput(filter) {
+			this.autosuggest.inputFocussed[filter.filter.name] = false;
+		},
+		focusFilterInput(filter) {
+			this.autosuggest.inputFocussed[filter.filter.name] = true;
+		},
+		keydownFilterInput(filter) {
+			const { name } = filter.filter;
+			clearTimeout(this.autosuggest.keydownInputTimeout[name]);
+			this.autosuggest.keydownInputTimeout[name] = setTimeout(() => {
+				if (filter.data.length > 1) {
+					this.autosuggest.items[name] = this.autosuggest.allItems[
+						name
+					]?.filter(item =>
+						item.toLowerCase().startsWith(filter.data.toLowerCase())
+					);
+				} else this.autosuggest.items[name] = [];
+			}, 1000);
+		},
+		focusFilterAutosuggestContainer(filter) {
+			this.autosuggest.containerFocussed[filter.filter.name] = true;
+		},
+		blurFilterAutosuggestContainer(filter) {
+			this.autosuggest.containerFocussed[filter.filter.name] = false;
+		},
+		selectAutosuggestItem(index, filter, item) {
+			this.editingFilters[index].data = item;
+			this.autosuggest.items[filter.filter.name] = [];
 		}
 	}
 };

+ 3 - 1
frontend/src/pages/Admin/tabs/Songs.vue

@@ -505,7 +505,9 @@ export default {
 					displayName: "Genres",
 					property: "genres",
 					filterTypes: ["contains", "exact", "regex"],
-					defaultFilterType: "contains"
+					defaultFilterType: "contains",
+					autocomplete: true,
+					autocompleteDataAction: "songs.getGenres"
 				},
 				{
 					name: "thumbnail",