mirror of
				https://github.com/PhoenixTwoFive/karaoqueue.git
				synced 2025-10-31 07:09:59 +01:00 
			
		
		
		
	Merge pull request #79 from PhoenixTwoFive/77-past-playback-based-score-and-recommendations
77 past playback based score and recommendations
This commit is contained in:
		| @@ -7,6 +7,7 @@ import os | |||||||
| import json | import json | ||||||
| from flask_basicauth import BasicAuth | from flask_basicauth import BasicAuth | ||||||
| from helpers import nocache | from helpers import nocache | ||||||
|  | from werkzeug.utils import secure_filename | ||||||
| app = Flask(__name__, static_url_path='/static') | app = Flask(__name__, static_url_path='/static') | ||||||
|  |  | ||||||
| basic_auth = BasicAuth(app) | basic_auth = BasicAuth(app) | ||||||
| @@ -170,6 +171,70 @@ def query_songs_with_details(input_string=""): | |||||||
|     return jsonify(result) |     return jsonify(result) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @app.route("/api/songs/suggest") | ||||||
|  | @nocache | ||||||
|  | def query_songs_with_details_suggest(input_string=""): | ||||||
|  |     input_string = request.args.get("count", input_string) | ||||||
|  |     if input_string == "": | ||||||
|  |         return Response(status=400) | ||||||
|  |     result = [] | ||||||
|  |     if not input_string.isnumeric(): | ||||||
|  |         return Response(status=400) | ||||||
|  |     count: int = int(input_string) | ||||||
|  |     for x in database.get_song_suggestions(count): | ||||||
|  |         # Turn row into dict. Add field labels. | ||||||
|  |         result.append(dict(zip(['karafun_id', 'title', 'artist', 'year', 'duo', 'explicit', 'styles', 'languages'], x))) | ||||||
|  |     return jsonify(result) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @app.route("/api/songs/stats") | ||||||
|  | @nocache | ||||||
|  | # Return the data from long_term_stats as json | ||||||
|  | def get_stats(): | ||||||
|  |     db_result = database.get_long_term_stats() | ||||||
|  |     data = [] | ||||||
|  |     for row in db_result: | ||||||
|  |         data.append(dict(zip(['id', 'count'], row))) | ||||||
|  |     return jsonify(data) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @app.route("/api/songs/stats.csv") | ||||||
|  | @nocache | ||||||
|  | # Return data from long_term_stats as csv | ||||||
|  | def get_stats_csv(): | ||||||
|  |     db_result = database.get_long_term_stats() | ||||||
|  |     print(db_result) | ||||||
|  |     csv = "Id,Playbacks\n" | ||||||
|  |     for row in db_result: | ||||||
|  |         csv += str(row[0]) + "," + str(row[1]) + "\n" | ||||||
|  |     return Response(csv, mimetype='text/csv') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @app.route("/api/songs/stats.csv", methods=['POST']) | ||||||
|  | @nocache | ||||||
|  | @basic_auth.required | ||||||
|  | # Update long_term_stats from csv | ||||||
|  | def update_stats_csv(): | ||||||
|  |     if not request.files: | ||||||
|  |         abort(400) | ||||||
|  |     file = request.files['file'] | ||||||
|  |     if file.filename is None: | ||||||
|  |         abort(400) | ||||||
|  |     else: | ||||||
|  |         filename = secure_filename(file.filename) | ||||||
|  |     if filename == '': | ||||||
|  |         abort(400) | ||||||
|  |     if not filename.endswith('.csv'): | ||||||
|  |         abort(400) | ||||||
|  |     if file: | ||||||
|  |         if database.import_stats(file): | ||||||
|  |             return Response('{"status": "OK"}', mimetype='text/json') | ||||||
|  |         else: | ||||||
|  |             return Response('{"status": "FAIL"}', mimetype='text/json', status=400) | ||||||
|  |     else: | ||||||
|  |         abort(400) | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.route("/api/songs/details/<song_id>") | @app.route("/api/songs/details/<song_id>") | ||||||
| def get_song_details(song_id): | def get_song_details(song_id): | ||||||
|     result = database.get_song_details(song_id) |     result = database.get_song_details(song_id) | ||||||
| @@ -261,14 +326,20 @@ def get_accept_entries(): | |||||||
|     return Response('{"status": "OK", "value": ' + str(int(accept_entries)) + '}', mimetype='text/json') |     return Response('{"status": "OK", "value": ' + str(int(accept_entries)) + '}', mimetype='text/json') | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.route("/api/played/clear") | @app.route("/api/event/close") | ||||||
| @nocache | @nocache | ||||||
| @basic_auth.required | @basic_auth.required | ||||||
| def clear_played_songs(): | def close_event(): | ||||||
|     if database.clear_played_songs(): |     try: | ||||||
|  |         database.transfer_playbacks() | ||||||
|  |         database.clear_played_songs() | ||||||
|  |         database.delete_all_entries() | ||||||
|  |         helpers.reset_current_event_id(app) | ||||||
|         return Response('{"status": "OK"}', mimetype='text/json') |         return Response('{"status": "OK"}', mimetype='text/json') | ||||||
|     else: |     except Exception: | ||||||
|         return Response('{"status": "FAIL"}', mimetype='text/json') |         response = jsonify({"status": "FAIL", "message": "An error occured while closing the event."}) | ||||||
|  |         response.status_code = 400 | ||||||
|  |         return response | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.route("/api/entries/delete_all") | @app.route("/api/entries/delete_all") | ||||||
| @@ -298,6 +369,7 @@ def activate_job(): | |||||||
|     with app.app_context(): |     with app.app_context(): | ||||||
|         helpers.load_dbconfig(app) |         helpers.load_dbconfig(app) | ||||||
|         helpers.load_version(app) |         helpers.load_version(app) | ||||||
|  |         database.create_schema() | ||||||
|         database.create_entry_table() |         database.create_entry_table() | ||||||
|         database.create_song_table() |         database.create_song_table() | ||||||
|         database.create_done_song_table() |         database.create_done_song_table() | ||||||
|   | |||||||
| @@ -40,6 +40,31 @@ def import_songs(song_csv): | |||||||
|     return ("Imported songs ({} in Database)".format(num_songs)) |     return ("Imported songs ({} in Database)".format(num_songs)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def import_stats(stats_csv): | ||||||
|  |     print("Start importing Stats...") | ||||||
|  |     df = pandas.read_csv(stats_csv, sep=',') | ||||||
|  |     if (df.columns[0] != "Id" or df.columns[1] != "Playbacks"): | ||||||
|  |         return False | ||||||
|  |     with get_db_engine().connect() as conn: | ||||||
|  |         for index, row in df.iterrows(): | ||||||
|  |             stmt = text( | ||||||
|  |                 "INSERT INTO long_term_stats (Id,Playbacks) VALUES (:par_id,:par_playbacks) ON DUPLICATE KEY UPDATE Playbacks=:par_playbacks") | ||||||
|  |             conn.execute(stmt, {"par_id": row["Id"], "par_playbacks": row["Playbacks"]}) | ||||||
|  |         conn.commit() | ||||||
|  |     return True | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def create_schema(): | ||||||
|  |     create_song_table() | ||||||
|  |     create_entry_table() | ||||||
|  |     create_done_song_table() | ||||||
|  |     create_config_table() | ||||||
|  |     create_long_term_stats_table() | ||||||
|  |     create_list_view() | ||||||
|  |     create_done_song_view() | ||||||
|  |     init_event_id() | ||||||
|  |  | ||||||
|  |  | ||||||
| def create_entry_table(): | def create_entry_table(): | ||||||
|     with get_db_engine().connect() as conn: |     with get_db_engine().connect() as conn: | ||||||
|         stmt = text( |         stmt = text( | ||||||
| @@ -75,6 +100,17 @@ def create_song_table(): | |||||||
|         conn.commit() |         conn.commit() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def create_long_term_stats_table(): | ||||||
|  |     with get_db_engine().connect() as conn: | ||||||
|  |         stmt = text("""CREATE TABLE IF NOT EXISTS `long_term_stats` ( | ||||||
|  |         `Id` INTEGER, | ||||||
|  |         `Playbacks` INTEGER, | ||||||
|  |         PRIMARY KEY (`Id`) | ||||||
|  |         )""") | ||||||
|  |         conn.execute(stmt) | ||||||
|  |         conn.commit() | ||||||
|  |  | ||||||
|  |  | ||||||
| def create_list_view(): | def create_list_view(): | ||||||
|     with get_db_engine().connect() as conn: |     with get_db_engine().connect() as conn: | ||||||
|         stmt = text("""CREATE OR REPLACE VIEW `Liste` AS |         stmt = text("""CREATE OR REPLACE VIEW `Liste` AS | ||||||
| @@ -121,6 +157,34 @@ def get_played_list(): | |||||||
|     return cur.fetchall() |     return cur.fetchall() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def get_song_suggestions(count: int): | ||||||
|  |     with get_db_engine().connect() as conn: | ||||||
|  |         # Get the top 10 songs with the most plays from the long_term_stats table and join them with the songs table to get the song details. | ||||||
|  |         # Exclude songs that are already in the queue, or in the done_songs table. | ||||||
|  |         stmt = text(""" | ||||||
|  |                     SELECT s.Id, s.Title, s.Artist, s.Year, s.Duo, s.Explicit, s.Styles, s.Languages | ||||||
|  |                     FROM long_term_stats lts | ||||||
|  |                     LEFT JOIN songs s ON lts.Id = s.Id | ||||||
|  |                     LEFT JOIN entries e ON lts.Id = e.Song_Id | ||||||
|  |                     LEFT JOIN done_songs ds ON lts.Id = ds.Song_Id | ||||||
|  |                     WHERE e.Id IS NULL AND ds.Song_Id IS NULL | ||||||
|  |                     ORDER BY lts.Playbacks DESC | ||||||
|  |                     LIMIT :count; | ||||||
|  |                     """) | ||||||
|  |         cur = conn.execute(stmt, {"count": count}) | ||||||
|  |     return cur.fetchall() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def get_long_term_stats(): | ||||||
|  |     with get_db_engine().connect() as conn: | ||||||
|  |         stmt = text(""" | ||||||
|  |                     SELECT lts.Id, lts.Playbacks | ||||||
|  |                     FROM long_term_stats lts | ||||||
|  |                     """) | ||||||
|  |         cur = conn.execute(stmt) | ||||||
|  |     return cur.fetchall() | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_song_list(): | def get_song_list(): | ||||||
|     with get_db_engine().connect() as conn: |     with get_db_engine().connect() as conn: | ||||||
|         stmt = text("SELECT Artist || \" - \" || Title AS Song, Id FROM songs;") |         stmt = text("SELECT Artist || \" - \" || Title AS Song, Id FROM songs;") | ||||||
| @@ -224,6 +288,23 @@ def check_queue_length(): | |||||||
|     return cur.fetchall()[0][0] |     return cur.fetchall()[0][0] | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def transfer_playbacks(): | ||||||
|  |     with get_db_engine().connect() as conn: | ||||||
|  |         # Use SQL to update the long_term_stats table. Add the playbacks of the songs in the done_songs table to the playbacks of the songs in the long_term_stats table. | ||||||
|  |         stmt = text(""" | ||||||
|  |                     INSERT INTO long_term_stats(Id, Playbacks) | ||||||
|  |                     SELECT ds.Song_Id, ds.Plays | ||||||
|  |                     FROM done_songs ds | ||||||
|  |                     LEFT JOIN long_term_stats lts ON ds.Song_Id = lts.Id | ||||||
|  |                     ON DUPLICATE KEY | ||||||
|  |                     UPDATE Playbacks = lts.Playbacks + VALUES(Playbacks); | ||||||
|  |                     """) | ||||||
|  |         result = conn.execute(stmt) | ||||||
|  |         print(result) | ||||||
|  |         conn.commit() | ||||||
|  |     return True | ||||||
|  |  | ||||||
|  |  | ||||||
| def clear_played_songs(): | def clear_played_songs(): | ||||||
|     with get_db_engine().connect() as conn: |     with get_db_engine().connect() as conn: | ||||||
|         conn.execute(text("DELETE FROM done_songs")) |         conn.execute(text("DELETE FROM done_songs")) | ||||||
|   | |||||||
| @@ -2,8 +2,8 @@ | |||||||
| {% block title %}Abspielliste{% endblock %} | {% block title %}Abspielliste{% endblock %} | ||||||
| {% block content %} | {% block content %} | ||||||
| <div id="toolbar"> | <div id="toolbar"> | ||||||
|     <button type="button" class="topbutton btn btn-danger" onclick="confirmDeleteAllEntries()"><i |     <button type="button" class="topbutton btn btn-danger" onclick="confirmCloseEvent()"><i | ||||||
|             class="fas fa-trash mr-2"></i>Abspielliste löschen</button> |             class="fas fa-trash mr-2"></i>Event beenden</button> | ||||||
|     <button type="button" class="topbutton btn btn-primary" onclick="exportPDF()"><i |     <button type="button" class="topbutton btn btn-primary" onclick="exportPDF()"><i | ||||||
|             class="fas fa-file-pdf mr-2"></i>Als PDF herunterladen</button> |             class="fas fa-file-pdf mr-2"></i>Als PDF herunterladen</button> | ||||||
|     <button type="button" class="topbutton btn btn-secondary" onclick="printPDF()"><i |     <button type="button" class="topbutton btn btn-secondary" onclick="printPDF()"><i | ||||||
| @@ -41,9 +41,17 @@ | |||||||
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.5.3/jspdf.min.js"></script> | <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.5.3/jspdf.min.js"></script> | ||||||
| <script src="https://unpkg.com/jspdf-autotable@3.0.10/dist/jspdf.plugin.autotable.js"></script> | <script src="https://unpkg.com/jspdf-autotable@3.0.10/dist/jspdf.plugin.autotable.js"></script> | ||||||
| <script> | <script> | ||||||
|     function confirmDeleteAllEntries() { |     function confirmCloseEvent() { | ||||||
|         bootbox.confirm({ |         bootbox.confirm({ | ||||||
|             message: "Wirklich Abspielliste löschen?<br>Stelle sicher, dass du sie vorher zwecks Abrechnung gedruckt und/oder heruntergeladen hast!",  |             message: `Wirklich Ereignis beenden?<br> | ||||||
|  |                         Folgendes wird passieren:<br> | ||||||
|  |                         <ul> | ||||||
|  |                             <li>Die Warteschlange wird geleert</li> | ||||||
|  |                             <li>Die Abspielliste wird gelöscht</li> | ||||||
|  |                             <li>Eine neue Event-ID wird vergeben</li> | ||||||
|  |                         </ul> | ||||||
|  |                         Diese Aktion kann nicht rückgängig gemacht werden! | ||||||
|  |             `,  | ||||||
|             buttons: { |             buttons: { | ||||||
|                 confirm: { |                 confirm: { | ||||||
|                     label: 'Ja', |                     label: 'Ja', | ||||||
| @@ -56,15 +64,15 @@ | |||||||
|             }, |             }, | ||||||
|             callback: function(result){ |             callback: function(result){ | ||||||
|                 if (result) { |                 if (result) { | ||||||
|                     deleteAllEntries() |                     closeEvent() | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
|     function deleteAllEntries() { |     function closeEvent() { | ||||||
|         $.ajax({ |         $.ajax({ | ||||||
|             type: 'GET', |             type: 'GET', | ||||||
|             url: '/api/played/clear', |             url: '/api/event/close', | ||||||
|             contentType: "application/json", |             contentType: "application/json", | ||||||
|             dataType: 'json', |             dataType: 'json', | ||||||
|             async: false |             async: false | ||||||
|   | |||||||
| @@ -4,7 +4,8 @@ | |||||||
| <form method="post"> | <form method="post"> | ||||||
|     <p> |     <p> | ||||||
|         <label for="entryquota">Maximale Anzahl an Einträgen pro Nutzer</label> |         <label for="entryquota">Maximale Anzahl an Einträgen pro Nutzer</label> | ||||||
|         <input type="number" class="form-control" id="entryquota" name="entryquota" min=1 value={{app.config['ENTRY_QUOTA']}}> |         <input type="number" class="form-control" id="entryquota" name="entryquota" min=1 | ||||||
|  |             value={{app.config['ENTRY_QUOTA']}}> | ||||||
|     </p> |     </p> | ||||||
|     <p> |     <p> | ||||||
|         <label for="maxqueue">Maximale Anzahl an Einträgen Insgesamt</label> |         <label for="maxqueue">Maximale Anzahl an Einträgen Insgesamt</label> | ||||||
| @@ -24,7 +25,8 @@ | |||||||
|     </div> |     </div> | ||||||
|     <p> |     <p> | ||||||
|         <label for="username">Benutzername</label> |         <label for="username">Benutzername</label> | ||||||
|         <input type="text" class="form-control" id="username" name="username" value={{app.config['BASIC_AUTH_USERNAME']}}> |         <input type="text" class="form-control" id="username" name="username" | ||||||
|  |             value={{app.config['BASIC_AUTH_USERNAME']}}> | ||||||
|     </p> |     </p> | ||||||
|     <p> |     <p> | ||||||
|         <label for="password">Passwort ändern</label> |         <label for="password">Passwort ändern</label> | ||||||
| @@ -32,10 +34,69 @@ | |||||||
|     </p> |     </p> | ||||||
|     <input type="submit" class="btn btn-primary mr-1 mb-2" value="Einstellungen anwenden"> |     <input type="submit" class="btn btn-primary mr-1 mb-2" value="Einstellungen anwenden"> | ||||||
| </form> | </form> | ||||||
|  | <form> | ||||||
|  |     <p> | ||||||
|  |         <label for="statsImport">Statistiken importieren/exportieren</label> | ||||||
|  |     </p> | ||||||
|  |     <div class="row"> | ||||||
|  |         <div class="col-sm-4"> | ||||||
|  |             <a class="btn btn-secondary" type="button" id="statsExport" href="/api/songs/stats.csv"><i class="fas fa-download mr-1"></i>Exportieren</a> | ||||||
|  |         </div> | ||||||
|  |         <div class="col input-group mb-3"> | ||||||
|  |             <div class="custom-file mr-1"> | ||||||
|  |                 <input type="file" class="custom-file-input" id="statsImport" data-allowed-file-extensions='["csv"]'> | ||||||
|  |                 <label class="custom-file-label" for="statsImport">CSV-Datei auswählen</label> | ||||||
|  |             </div> | ||||||
|  |             <button class="btn btn-secondary" type="button" id="statsImportBtn"><i class="fas fa-upload mr-1"></i>Importieren</button> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  |      | ||||||
|  |     <p> | ||||||
|  |     </p> | ||||||
|  | </form> | ||||||
| <details> | <details> | ||||||
|     <summary>Current config:</summary> |     <summary>Current config:</summary> | ||||||
|     <pre>{% for key, val in config.items() %}{{key}}: {{val}}<br>{% endfor %}</pre> |     <pre>{% for key, val in config.items() %}{{key}}: {{val}}<br>{% endfor %}</pre> | ||||||
| </details> | </details> | ||||||
| {% endblock %} | {% endblock %} | ||||||
| {% block extrajs %} | {% block extrajs %} | ||||||
|  | <script> | ||||||
|  |     $(document).ready(function () { | ||||||
|  |         $('#statsImport').on('change', function () { | ||||||
|  |             var fileName = $(this).val().split('\\').pop(); | ||||||
|  |             $(this).next('.custom-file-label').html(fileName); | ||||||
|  |         }); | ||||||
|  |         $('#statsImportBtn').on('click', function () { | ||||||
|  |             var file_data = $('#statsImport').prop('files')[0]; | ||||||
|  |             var form_data = new FormData(); | ||||||
|  |             form_data.append('file', file_data); | ||||||
|  |             $.ajax({ | ||||||
|  |                 url: '/api/songs/stats.csv', | ||||||
|  |                 cache: false, | ||||||
|  |                 contentType: false, | ||||||
|  |                 processData: false, | ||||||
|  |                 data: form_data, | ||||||
|  |                 type: 'post', | ||||||
|  |                 success: function (response) { | ||||||
|  |                     toast = { | ||||||
|  |                         title: "Erfolgreich importiert", | ||||||
|  |                         message: "Die Statistiken wurden erfolgreich importiert.", | ||||||
|  |                         status: TOAST_STATUS.SUCCESS, | ||||||
|  |                         timeout: 5000 | ||||||
|  |                     } | ||||||
|  |                     Toast.create(toast); | ||||||
|  |                 }, | ||||||
|  |                 error: function (response) { | ||||||
|  |                     toast = { | ||||||
|  |                         title: "Fehler beim Importieren", | ||||||
|  |                         message: "Die Statistiken konnten nicht importiert werden.", | ||||||
|  |                         status: TOAST_STATUS.ERROR, | ||||||
|  |                         timeout: 5000 | ||||||
|  |                     } | ||||||
|  |                     Toast.create(toast); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |     }); | ||||||
|  | </script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
| @@ -1,7 +1,8 @@ | |||||||
| {% extends 'base.html' %} | {% extends 'base.html' %} | ||||||
| {% block title %}Songsuche{% endblock %} | {% block title %}Songsuche{% endblock %} | ||||||
| {% block content %} | {% block content %} | ||||||
| <input class="form-control" id="filter" type="text" placeholder="Suchen..."> | <input class="form-control" id="filter" type="text" placeholder="Suche nach einem Song..."> | ||||||
|  | <h4 id="suggestionExplainer" class="mt-3 mb-3 text-center" style="display: none;">Oder probiere es mit einem dieser Vorschläge:</h4> | ||||||
| <table class="table"> | <table class="table"> | ||||||
|     <tbody id="songtable"> |     <tbody id="songtable"> | ||||||
|     </tbody> |     </tbody> | ||||||
| @@ -68,7 +69,7 @@ | |||||||
| {% block extrajs %} | {% block extrajs %} | ||||||
| <script> | <script> | ||||||
|     $(document).ready(function () { |     $(document).ready(function () { | ||||||
|         $("#filter").focus(); |         getSuggestions(10); | ||||||
|         $("#filter").keyup(debounce(() => songSearch())); |         $("#filter").keyup(debounce(() => songSearch())); | ||||||
|  |  | ||||||
|         $("#nameForm").submit(function (e) { |         $("#nameForm").submit(function (e) { | ||||||
| @@ -78,8 +79,69 @@ | |||||||
|         $('#enqueueModal').on('shown.bs.modal', function (e) { |         $('#enqueueModal').on('shown.bs.modal', function (e) { | ||||||
|             $("#singerNameInput").focus(); |             $("#singerNameInput").focus(); | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     function getSuggestions(count) { | ||||||
|  |         $.getJSON("/api/songs/suggest", { count: count }, function (data) { | ||||||
|  |             console.log(data); | ||||||
|  |             if (data.length == 0) { | ||||||
|  |                 console.log("No suggestions"); | ||||||
|  |                 $("#suggestionExplainer").hide(); | ||||||
|  |             } else { | ||||||
|  |                 $("#suggestionExplainer").show(); | ||||||
|  |             } | ||||||
|  |             $.each(data, function (key, val) { | ||||||
|  |                 $("#songtable").append(constructResultRow(val)) | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function constructResultRow(data) { | ||||||
|  |         let itemRow = document.createElement("tr") | ||||||
|  |         let itemCell = document.createElement("td") | ||||||
|  |         itemCell.innerHTML = data["artist"] + ` - ` + data["title"] | ||||||
|  |         itemRow.appendChild(itemCell) | ||||||
|  |         let infoCell = document.createElement("td") | ||||||
|  |         let duoindicator = document.createElement("i") | ||||||
|  |         duoindicator.classList.add("fas") | ||||||
|  |         if (data["duo"] == 0) { | ||||||
|  |             duoindicator.classList.add("fa-user") | ||||||
|  |  | ||||||
|  |         } | ||||||
|  |         if (data["duo"] == 1) { | ||||||
|  |             duoindicator.classList.add("fa-user-friends") | ||||||
|  |         } | ||||||
|  |         duoindicator.classList.add("ml-1") | ||||||
|  |         duoindicator.classList.add("list-indicator") | ||||||
|  |         infoCell.appendChild(duoindicator) | ||||||
|  |         if (data["explicit"] == 1) { | ||||||
|  |             let explicitindicator = document.createElement("i") | ||||||
|  |             explicitindicator.classList.add("fas") | ||||||
|  |             explicitindicator.classList.add("fa-e") | ||||||
|  |             explicitindicator.classList.add("ml-1") | ||||||
|  |             infoCell.appendChild(explicitindicator) | ||||||
|  |         } | ||||||
|  |         itemRow.appendChild(infoCell) | ||||||
|  |         let buttonCell = document.createElement("td") | ||||||
|  |         let button = document.createElement("button") | ||||||
|  |         button.classList.add("btn") | ||||||
|  |         button.classList.add("btn-primary") | ||||||
|  |         button.classList.add("justify-content-center") | ||||||
|  |         button.classList.add("align-content-between") | ||||||
|  |         button.classList.add("enqueueButton") | ||||||
|  |         button.setAttribute("type", "button") | ||||||
|  |         button.setAttribute("data-toggle", "modal") | ||||||
|  |         button.setAttribute("data-target", "#enqueueModal") | ||||||
|  |         button.setAttribute("onclick", "setSelectedId(" + data["karafun_id"] + ")") | ||||||
|  |         let buttonIcon = document.createElement("i") | ||||||
|  |         buttonIcon.classList.add("fas") | ||||||
|  |         buttonIcon.classList.add("fa-plus") | ||||||
|  |         button.appendChild(buttonIcon) | ||||||
|  |         buttonCell.appendChild(button) | ||||||
|  |         itemRow.appendChild(buttonCell) | ||||||
|  |         return itemRow | ||||||
|  |     } | ||||||
|  |  | ||||||
|     function enqueue(client_id, id, name, success_callback, blocked_callback) { |     function enqueue(client_id, id, name, success_callback, blocked_callback) { | ||||||
|         var data = { |         var data = { | ||||||
| @@ -216,53 +278,12 @@ | |||||||
|     function songSearch() { |     function songSearch() { | ||||||
|         let value = $("#filter").val() |         let value = $("#filter").val() | ||||||
|         if (value.length >= 1) { |         if (value.length >= 1) { | ||||||
|  |             $("#suggestionExplainer").hide(); | ||||||
|             $.getJSON("/api/songs/search", { q: value }, function (data) { |             $.getJSON("/api/songs/search", { q: value }, function (data) { | ||||||
|                 var items = []; |                 var items = []; | ||||||
|                 $("#songtable").html("") |                 $("#songtable").html("") | ||||||
|                 $.each(data, function (key, val) { |                 $.each(data, function (key, val) { | ||||||
|                     let itemRow = document.createElement("tr") |                     $("#songtable").append(constructResultRow(val)) | ||||||
|                     let itemCell = document.createElement("td") |  | ||||||
|                     itemCell.innerHTML = val["artist"] + ` - ` + val["title"] |  | ||||||
|                     itemRow.appendChild(itemCell) |  | ||||||
|                     let infoCell = document.createElement("td") |  | ||||||
|                     let duoindicator = document.createElement("i") |  | ||||||
|                     duoindicator.classList.add("fas") |  | ||||||
|                     if (val["duo"] == 0) { |  | ||||||
|                         duoindicator.classList.add("fa-user") |  | ||||||
|  |  | ||||||
|                     } |  | ||||||
|                     if (val["duo"] == 1) { |  | ||||||
|                         duoindicator.classList.add("fa-user-friends") |  | ||||||
|                     } |  | ||||||
|                     duoindicator.classList.add("ml-1") |  | ||||||
|                     duoindicator.classList.add("list-indicator") |  | ||||||
|                     infoCell.appendChild(duoindicator) |  | ||||||
|                     if (val["explicit"] == 1) { |  | ||||||
|                         let explicitindicator = document.createElement("i") |  | ||||||
|                         explicitindicator.classList.add("fas") |  | ||||||
|                         explicitindicator.classList.add("fa-e") |  | ||||||
|                         explicitindicator.classList.add("ml-1") |  | ||||||
|                         infoCell.appendChild(explicitindicator) |  | ||||||
|                     } |  | ||||||
|                     itemRow.appendChild(infoCell) |  | ||||||
|                     let buttonCell = document.createElement("td") |  | ||||||
|                     let button = document.createElement("button") |  | ||||||
|                     button.classList.add("btn") |  | ||||||
|                     button.classList.add("btn-primary") |  | ||||||
|                     button.classList.add("justify-content-center") |  | ||||||
|                     button.classList.add("align-content-between") |  | ||||||
|                     button.classList.add("enqueueButton") |  | ||||||
|                     button.setAttribute("type", "button") |  | ||||||
|                     button.setAttribute("data-toggle", "modal") |  | ||||||
|                     button.setAttribute("data-target", "#enqueueModal") |  | ||||||
|                     button.setAttribute("onclick", "setSelectedId(" + val["karafun_id"] + ")") |  | ||||||
|                     let buttonIcon = document.createElement("i") |  | ||||||
|                     buttonIcon.classList.add("fas") |  | ||||||
|                     buttonIcon.classList.add("fa-plus") |  | ||||||
|                     button.appendChild(buttonIcon) |  | ||||||
|                     buttonCell.appendChild(button) |  | ||||||
|                     itemRow.appendChild(buttonCell) |  | ||||||
|                     $("#songtable").append(itemRow) |  | ||||||
|                 }); |                 }); | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -271,6 +292,7 @@ | |||||||
|             }); |             }); | ||||||
|         } else { |         } else { | ||||||
|             $("#songtable").html("") |             $("#songtable").html("") | ||||||
|  |             getSuggestions(10); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user