mirror of
https://github.com/PhoenixTwoFive/karaoqueue.git
synced 2025-05-20 19:41:49 +02:00
Use bootstrap-table for fancy ajax tables.
This commit is contained in:
parent
41b2aa2ed1
commit
3ed8146b6f
34
.vscode/launch.json
vendored
34
.vscode/launch.json
vendored
@ -6,6 +6,7 @@
|
||||
"configurations": [
|
||||
|
||||
|
||||
|
||||
{"name":"Python: Flask","type":"python","request":"launch","module":"flask","env":{"FLASK_APP":"app/main.py","FLASK_ENV":"development","FLASK_DEBUG":"1"},"args":["run","--no-debugger","--no-reload"],"jinja":true},
|
||||
{"name":"Python: Flask (with reload)","type":"python","request":"launch","module":"flask","env":{"FLASK_APP":"app/main.py","FLASK_ENV":"development","FLASK_DEBUG":"1"},"args":["run","--no-debugger"],"jinja":true},
|
||||
{
|
||||
@ -25,6 +26,24 @@
|
||||
],
|
||||
"jinja": true
|
||||
},
|
||||
{
|
||||
"name": "Python: Flask (externally reachable)",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"module": "flask",
|
||||
"env": {
|
||||
"FLASK_APP": "app/main.py",
|
||||
"FLASK_ENV": "development",
|
||||
"FLASK_DEBUG": "1"
|
||||
},
|
||||
"args": [
|
||||
"run",
|
||||
"--no-debugger",
|
||||
"--no-reload",
|
||||
"--host='0.0.0.0'"
|
||||
],
|
||||
"jinja": true
|
||||
},
|
||||
{
|
||||
"name": "Python: Current File (Integrated Terminal)",
|
||||
"type": "python",
|
||||
@ -65,21 +84,6 @@
|
||||
],
|
||||
"django": true
|
||||
},
|
||||
{
|
||||
"name": "Python: Flask",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"module": "flask",
|
||||
"env": {
|
||||
"FLASK_APP": "app.py"
|
||||
},
|
||||
"args": [
|
||||
"run",
|
||||
"--no-debugger",
|
||||
"--no-reload"
|
||||
],
|
||||
"jinja": true
|
||||
},
|
||||
{
|
||||
"name": "Python: Current File (External Terminal)",
|
||||
"type": "python",
|
||||
|
8
app/data_adapters.py
Normal file
8
app/data_adapters.py
Normal file
@ -0,0 +1,8 @@
|
||||
def dict_from_row(row):
|
||||
return dict(zip(row.keys(), row))
|
||||
|
||||
def dict_from_rows(rows):
|
||||
outlist=[]
|
||||
for row in rows:
|
||||
outlist.append(dict_from_row(row))
|
||||
return outlist
|
@ -74,6 +74,7 @@ def create_done_song_view():
|
||||
|
||||
def get_list():
|
||||
conn = open_db()
|
||||
conn.row_factory = sqlite3.Row
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT * FROM Liste")
|
||||
return cur.fetchall()
|
||||
@ -141,6 +142,20 @@ def delete_entry(id):
|
||||
return True
|
||||
|
||||
|
||||
def delete_entries(ids):
|
||||
idlist = []
|
||||
for x in ids:
|
||||
idlist.append( (x,) )
|
||||
try:
|
||||
conn = open_db()
|
||||
cur = conn.cursor()
|
||||
cur.executemany("DELETE FROM entries WHERE id=?", idlist)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return cur.rowcount
|
||||
except sqlite3.Error as error:
|
||||
return -1
|
||||
|
||||
def delete_all_entries():
|
||||
conn = open_db()
|
||||
cur = conn.cursor()
|
||||
|
24
app/main.py
24
app/main.py
@ -1,6 +1,7 @@
|
||||
from flask import Flask, render_template, Response, abort, request, redirect
|
||||
import helpers
|
||||
import database
|
||||
import app.helpers as helpers
|
||||
import app.database as database
|
||||
import app.data_adapters as data_adapters
|
||||
import os, errno
|
||||
import json
|
||||
from flask_basicauth import BasicAuth
|
||||
@ -29,6 +30,11 @@ def enqueue():
|
||||
def songlist():
|
||||
return render_template('songlist.html', list=database.get_song_list(), auth=basic_auth.authenticate())
|
||||
|
||||
@app.route("/api/queue")
|
||||
def queue_json():
|
||||
list = data_adapters.dict_from_rows(database.get_list())
|
||||
return Response(json.dumps(list, ensure_ascii=False).encode('utf-8'), mimetype='text/json')
|
||||
|
||||
@app.route("/plays")
|
||||
@basic_auth.required
|
||||
def played_list():
|
||||
@ -69,6 +75,20 @@ def delete_entry(entry_id):
|
||||
return Response('{"status": "FAIL"}', mimetype='text/json')
|
||||
|
||||
|
||||
@app.route("/api/entries/delete", methods=['POST'])
|
||||
@basic_auth.required
|
||||
def delete_entries():
|
||||
if not request.json:
|
||||
print(request.data)
|
||||
abort(400)
|
||||
return
|
||||
updates = database.delete_entries(request.json)
|
||||
if updates >= 0:
|
||||
return Response('{"status": "OK", "updates": '+str(updates)+'}', mimetype='text/json')
|
||||
else:
|
||||
return Response('{"status": "FAIL"}', mimetype='text/json', status=400)
|
||||
|
||||
|
||||
@app.route("/api/entries/mark_sung/<entry_id>")
|
||||
@basic_auth.required
|
||||
def mark_sung(entry_id):
|
||||
|
@ -26,15 +26,11 @@ main {
|
||||
|
||||
.topbutton {
|
||||
width: 100%;
|
||||
margin-right: auto;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.topbutton {
|
||||
width: auto;
|
||||
margin-right: 1rem;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,9 @@
|
||||
|
||||
<title>{% block title %}{% endblock %} - KaraoQueue</title>
|
||||
|
||||
<!-- Bootstrap-Tables -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/bootstrap-table@1.15.3/dist/bootstrap-table.min.css">
|
||||
|
||||
<!-- Bootstrap core CSS -->
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
|
||||
integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
|
||||
@ -21,6 +24,7 @@
|
||||
<!-- Fontawesome Icons -->
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css"
|
||||
integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay" crossorigin="anonymous">
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@ -88,6 +92,7 @@
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/4.4.0/bootbox.min.js"
|
||||
integrity="sha256-4F7e4JsAJyLUdpP7Q8Sah866jCOhv72zU5E8lIRER4w=" crossorigin="anonymous">
|
||||
</script>
|
||||
<script src="https://unpkg.com/bootstrap-table@1.15.3/dist/bootstrap-table.min.js"></script>
|
||||
{% block extrajs %}{% endblock %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
|
@ -3,28 +3,19 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}Warteliste{% endblock %}
|
||||
{% block content %}
|
||||
<a role="button" class="btn btn-primary btn-lg btn-block" href="/list">Eintragen</a>
|
||||
<table class="table">
|
||||
<a role="button" class="btn btn-primary btn-lg btn-block mb-2" href="/list">Eintragen</a>
|
||||
<table class="table"
|
||||
data-toggle="table"
|
||||
data-url="/api/queue"
|
||||
data-pagination="true"
|
||||
data-classes="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Song</th>
|
||||
<th scope="col">Künstler</th>
|
||||
<th scope="col" data-field="Name">Name</th>
|
||||
<th scope="col" data-field="Title">Song</th>
|
||||
<th scope="col" data-field="Artist">Künstler</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for entry in list: %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ entry[0] }}
|
||||
</td>
|
||||
<td>
|
||||
{{ entry[1] }}
|
||||
</td>
|
||||
<td>
|
||||
{{ entry[2] }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<a name="end"></a>
|
||||
{% endblock %}
|
@ -4,54 +4,38 @@
|
||||
{% block title %}Warteliste-Admin{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="card" style="width: 100%">
|
||||
<div class="card-body">
|
||||
<button type="button" class="topbutton btn btn-danger"
|
||||
onclick="confirmDeleteAllEntries()"><i class="fas fa-trash mr-2"></i>Alle Einträge löschen</button>
|
||||
<button type="button" class="topbutton btn btn-danger"
|
||||
onclick="confirmUpdateSongDatabase()"><i class="fas fa-file-import mr-2"></i>Song-Datenbank
|
||||
<div id="toolbar">
|
||||
<button type="button" class="topbutton btn btn-danger" onclick="confirmDeleteSelectedEntries()"><i
|
||||
class="fas fa-trash mr-2"></i>Gewählte Einträge löschen</button>
|
||||
<button type="button" class="topbutton btn btn-danger" onclick="confirmUpdateSongDatabase()"><i
|
||||
class="fas fa-file-import mr-2"></i>Song-Datenbank
|
||||
aktualisieren</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<table class="table">
|
||||
<table class="table"
|
||||
id="entrytable"
|
||||
data-toggle="table"
|
||||
data-search="true"
|
||||
data-show-columns="true"
|
||||
data-show-toggle="true"
|
||||
data-multiple-select-row="true"
|
||||
data-click-to-select="true"
|
||||
data-toolbar="#toolbar"
|
||||
data-pagination="true"
|
||||
data-show-extended-pagination="true"
|
||||
data-classes="table table-hover"
|
||||
data-url="/api/queue">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Song</th>
|
||||
<th scope="col">Künstler</th>
|
||||
<th scope="col">Aktionen</th>
|
||||
<th data-field="state" data-checkbox="true"></th>
|
||||
<th scope="col" data-field="Name">Name</th>
|
||||
<th scope="col" data-field="Title">Song</th>
|
||||
<th scope="col" data-field="Artist">Künstler</th>
|
||||
<th scope="col" data-formatter="TableActions">Aktionen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for entry in list: %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ entry[0] }}
|
||||
</td>
|
||||
<td>
|
||||
{{ entry[1] }}
|
||||
</td>
|
||||
<td>
|
||||
{{ entry[2] }}
|
||||
</td>
|
||||
<td>
|
||||
<button type='button' class='btn btn-success'
|
||||
data-toggle="tooltip" data-placement="top" title="Als gesungen markieren"
|
||||
onclick='markEntryAsSung({{ entry[3] }})'><i
|
||||
class="fas fa-check"></i></button>
|
||||
<button type='button' class='btn btn-danger'
|
||||
data-toggle="tooltip" data-placement="top" title="Eintrag löschen"
|
||||
onclick='confirmDeleteEntry("{{ entry[0] }}",{{ entry[3] }})'><i
|
||||
class="fas fa-trash"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<a name="end"></a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block extrajs %}
|
||||
<script>
|
||||
@ -65,9 +49,9 @@
|
||||
}
|
||||
})
|
||||
}
|
||||
function confirmDeleteAllEntries() {
|
||||
function confirmDeleteSelectedEntries() {
|
||||
bootbox.confirm({
|
||||
message: "Wirklich alle Eintragungen löschen?",
|
||||
message: "Wirklich gewählte Eintragungen löschen?",
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: 'Ja',
|
||||
@ -80,7 +64,7 @@
|
||||
},
|
||||
callback: function(result){
|
||||
if (result) {
|
||||
deleteAllEntries()
|
||||
DeleteSelectedEntries(getIdSelections())
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -118,7 +102,7 @@
|
||||
dataType: 'json',
|
||||
async: false
|
||||
});
|
||||
location.reload();
|
||||
$("#entrytable").bootstrapTable('refresh')
|
||||
}
|
||||
function markEntryAsSung(entry_id) {
|
||||
$.ajax({
|
||||
@ -128,17 +112,24 @@
|
||||
dataType: 'json',
|
||||
async: false
|
||||
});
|
||||
location.reload();
|
||||
$("#entrytable").bootstrapTable('refresh')
|
||||
}
|
||||
function deleteAllEntries() {
|
||||
function DeleteSelectedEntries(ids) {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: '/api/entries/delete_all',
|
||||
type: 'POST',
|
||||
url: '/api/entries/delete',
|
||||
data: JSON.stringify(ids), // or JSON.stringify ({name: 'jonas'}),
|
||||
error: function() {
|
||||
bootbox.alert({
|
||||
message: "Fehler beim Löschen der Eintragungen.",
|
||||
})
|
||||
},
|
||||
success: function() {
|
||||
$("#entrytable").bootstrapTable('refresh')
|
||||
},
|
||||
contentType: "application/json",
|
||||
dataType: 'json',
|
||||
async: false
|
||||
dataType: 'json'
|
||||
});
|
||||
location.reload();
|
||||
}
|
||||
function updateSongDatabase(wait_dialog) {
|
||||
$.ajax({
|
||||
@ -150,10 +141,18 @@
|
||||
wait_dialog.modal('hide')
|
||||
bootbox.alert({
|
||||
message: data["status"],
|
||||
callback: function() {location.reload()}
|
||||
callback: function() {$("#entrytable").bootstrapTable('refresh')}
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
function TableActions (value, row, index) {
|
||||
return "<button type='button' class='btn btn-success' data-toggle=\"tooltip\" data-placement=\"top\" title=\"Als gesungen markieren\" onclick='markEntryAsSung("+row.ID+")'><i class=\"fas fa-check\"></i></button> <button type='button' class='btn btn-danger' data-toggle=\"tooltip\" data-placement=\"top\" title=\"Eintrag löschen\" onclick='confirmDeleteEntry(\""+row.Name+"\","+row.ID+")'><i class=\"fas fa-trash\"></i></button>";
|
||||
}
|
||||
function getIdSelections() {
|
||||
return $.map($("#entrytable").bootstrapTable('getSelections'), function (row) {
|
||||
return row.ID
|
||||
})
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
@ -1,15 +1,23 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}Abspielliste{% endblock %}
|
||||
{% block content %}
|
||||
<div class="card admincontrols" style="width: 100%">
|
||||
<div class="card-body">
|
||||
<div id="toolbar">
|
||||
<button type="button" class="topbutton btn btn-danger" onclick="confirmDeleteAllEntries()"><i
|
||||
class="fas fa-trash mr-2"></i>Abspielliste löschen</button>
|
||||
<button type="button" class="topbutton btn btn-primary" onclick="exportPDF()"><i class="fas fa-file-pdf mr-2"></i>Als PDF herunterladen</button>
|
||||
<button type="button" class="topbutton btn btn-secondary" onclick="printPDF()"><i class="fas fa-print mr-2"></i>Drucken</button>
|
||||
<button type="button" class="topbutton btn btn-primary" onclick="exportPDF()"><i
|
||||
class="fas fa-file-pdf mr-2"></i>Als PDF herunterladen</button>
|
||||
<button type="button" class="topbutton btn btn-secondary" onclick="printPDF()"><i
|
||||
class="fas fa-print mr-2"></i>Drucken</button>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table" id="table">
|
||||
<table class="table"
|
||||
id="table"
|
||||
data-toggle="table"
|
||||
data-search="true"
|
||||
data-show-columns="true"
|
||||
data-toolbar="#toolbar"
|
||||
data-pagination="true"
|
||||
data-classes="table table-bordered table-striped"
|
||||
data-show-extended-pagination="true">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Song</th>
|
||||
@ -67,7 +75,8 @@
|
||||
function exportPDF() {
|
||||
var doc = new jsPDF();
|
||||
doc.autoTable({
|
||||
html: '#table',
|
||||
head: [["Song","Wiedergaben"]],
|
||||
body: createTableArray(),
|
||||
theme: 'grid'
|
||||
});
|
||||
doc.save('Abspielliste.pdf');
|
||||
@ -76,11 +85,18 @@
|
||||
function printPDF() {
|
||||
var doc = new jsPDF();
|
||||
doc.autoTable({
|
||||
html: '#table',
|
||||
head: [["Song","Wiedergaben"]],
|
||||
body: createTableArray(),
|
||||
theme: 'grid'
|
||||
});
|
||||
doc.autoPrint();
|
||||
doc.output('dataurlnewwindow');
|
||||
}
|
||||
|
||||
function createTableArray() {
|
||||
var data = $("#table").bootstrapTable('getData')
|
||||
out = data.map(x => [x["0"],x["1"]])
|
||||
return out;
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
@ -4,16 +4,6 @@
|
||||
<input class="form-control" id="filter" type="text" placeholder="Suchen...">
|
||||
<table class="table">
|
||||
<tbody id="songtable">
|
||||
<!--{% for entry in (): %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ entry[0] }}
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-primary"><i class="material-icons">queue_music</i></button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}-->
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="modal fade" id="enqueueModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
|
||||
|
Loading…
x
Reference in New Issue
Block a user