URLs in Playlist inline bearbeiten (Edit-Button + Modal)
- Neue Route POST /admin/<site>/update-playlist-item/<screen> - Bearbeiten-Button (✎) für URL-Items in admin.html + priority.html - Modal mit URL + Zoom-Feld, Validierung und Speichern - Grid um Edit-Spalte erweitert (28px|1fr|90px|80px|40px|50px|32px)
This commit is contained in:
39
app.py
39
app.py
@@ -1575,6 +1575,45 @@ def save_playlist(site, screen):
|
|||||||
return "", 204
|
return "", 204
|
||||||
|
|
||||||
|
|
||||||
|
# -------------------------------------------------
|
||||||
|
# Admin: Playlist-Item bearbeiten (URL + Zoom)
|
||||||
|
# -------------------------------------------------
|
||||||
|
@app.route("/admin/<site>/update-playlist-item/<screen>", methods=["POST"])
|
||||||
|
@site_access_required
|
||||||
|
def update_playlist_item(site, screen):
|
||||||
|
"""Aktualisiert URL und Zoom eines einzelnen Playlist-Items."""
|
||||||
|
data = request.get_json()
|
||||||
|
old_name = data.get("old_name")
|
||||||
|
new_url = data.get("new_url", "").strip()
|
||||||
|
zoom = data.get("zoom", 1.0)
|
||||||
|
|
||||||
|
if not is_url(new_url):
|
||||||
|
return jsonify({"error": "Ungültige URL"}), 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
zoom = float(zoom)
|
||||||
|
if zoom <= 0:
|
||||||
|
zoom = 1.0
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
zoom = 1.0
|
||||||
|
|
||||||
|
config = load_config()
|
||||||
|
|
||||||
|
if screen == "priority":
|
||||||
|
playlist = config.setdefault("priority", {}).setdefault("playlist", [])
|
||||||
|
else:
|
||||||
|
playlist = config["sites"][site]["screens"][screen].setdefault("playlist", [])
|
||||||
|
|
||||||
|
for i, item in enumerate(playlist):
|
||||||
|
name = playlist_item_name(item)
|
||||||
|
if name == old_name:
|
||||||
|
playlist[i] = {"url": new_url, "zoom": zoom}
|
||||||
|
save_config(config)
|
||||||
|
return jsonify({"success": True})
|
||||||
|
|
||||||
|
return jsonify({"error": "Item nicht gefunden"}), 404
|
||||||
|
|
||||||
|
|
||||||
# -------------------------------------------------
|
# -------------------------------------------------
|
||||||
# Change-Password (für First-Login / Reset)
|
# Change-Password (für First-Login / Reset)
|
||||||
# -------------------------------------------------
|
# -------------------------------------------------
|
||||||
|
|||||||
@@ -43,11 +43,11 @@
|
|||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ─── Playlist-Zeile als Grid (Checkbox | Name | Typ | Größe | Löschen | Drag) ─── */
|
/* ─── Playlist-Zeile als Grid (Checkbox | Name | Typ | Größe | Edit | Löschen | Drag) ─── */
|
||||||
.playlist-row {
|
.playlist-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns:
|
grid-template-columns:
|
||||||
28px 1fr 90px 80px 90px 32px;
|
28px 1fr 90px 80px 40px 50px 32px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
@@ -271,6 +271,18 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<!-- Bearbeiten (nur URLs) -->
|
||||||
|
{% if file.type == "url" %}
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-ghost-secondary btn-sm edit-url-btn"
|
||||||
|
title="URL bearbeiten"
|
||||||
|
data-name="{{ file.name | e }}"
|
||||||
|
data-zoom="{{ file.zoom | default(1.0) }}"
|
||||||
|
onclick="editPlaylistItem('{{ current_site }}', '{{ screen }}', this.dataset.name, this.dataset.zoom)">
|
||||||
|
✎
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- Löschen -->
|
<!-- Löschen -->
|
||||||
<form action="{{ url_for('delete_file', site=current_site, screen=screen, filename=file.name) }}"
|
<form action="{{ url_for('delete_file', site=current_site, screen=screen, filename=file.name) }}"
|
||||||
method="post">
|
method="post">
|
||||||
@@ -577,8 +589,138 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
return new bootstrap.Tooltip(tooltipTriggerEl);
|
return new bootstrap.Tooltip(tooltipTriggerEl);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* ─── Playlist-Item bearbeiten (URL + Zoom) ─── */
|
||||||
|
var editState = { site: "", screen: "", oldName: "" };
|
||||||
|
|
||||||
|
function editPlaylistItem(site, screen, oldName, zoom) {
|
||||||
|
editState.site = site;
|
||||||
|
editState.screen = screen;
|
||||||
|
editState.oldName = oldName;
|
||||||
|
document.getElementById("edit-url-input").value = oldName;
|
||||||
|
document.getElementById("edit-zoom-input").value = zoom;
|
||||||
|
document.getElementById("edit-url-error").style.display = "none";
|
||||||
|
new bootstrap.Modal(document.getElementById("edit-url-modal")).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
|
||||||
|
/* ─── SortableJS Drag & Drop initialisieren ─── */
|
||||||
|
function initSortable(name) {
|
||||||
|
const el = document.getElementById("playlist-" + name);
|
||||||
|
if (!el || typeof Sortable === "undefined") return;
|
||||||
|
|
||||||
|
new Sortable(el, {
|
||||||
|
animation: 150,
|
||||||
|
handle: ".drag-handle",
|
||||||
|
ghostClass: "bg-light",
|
||||||
|
chosenClass: "bg-light"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Für jeden Screen eine Sortable-Instanz
|
||||||
|
{% for screen in screens.keys() %}
|
||||||
|
initSortable("{{ screen }}");
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
/* ─── Playlist-Reihenfolge speichern ─── */
|
||||||
|
function savePlaylist(site, screen) {
|
||||||
|
const items = document.querySelectorAll(
|
||||||
|
"#playlist-" + screen + " li"
|
||||||
|
);
|
||||||
|
const playlist = [];
|
||||||
|
|
||||||
|
const enabled = {};
|
||||||
|
items.forEach((item) => {
|
||||||
|
const checkbox = item.querySelector(".playlist-enabled");
|
||||||
|
const fileName = item.dataset.file;
|
||||||
|
enabled[fileName] = !!(checkbox && checkbox.checked);
|
||||||
|
playlist.push(fileName);
|
||||||
|
});
|
||||||
|
|
||||||
|
fetch("/admin/" + site + "/playlist/" + screen, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
playlist: playlist,
|
||||||
|
enabled: enabled
|
||||||
|
})
|
||||||
|
}).then(() => location.reload());
|
||||||
|
}
|
||||||
|
|
||||||
|
window.savePlaylist = savePlaylist;
|
||||||
|
|
||||||
|
/* ─── Bootstrap Tooltips initialisieren ─── */
|
||||||
|
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||||||
|
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||||
|
return new bootstrap.Tooltip(tooltipTriggerEl);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ─── Edit-URL-Modal: Speichern-Button ─── */
|
||||||
|
document.getElementById("edit-url-save").addEventListener("click", function () {
|
||||||
|
const newUrl = document.getElementById("edit-url-input").value.trim();
|
||||||
|
const zoom = parseFloat(document.getElementById("edit-zoom-input").value) || 1.0;
|
||||||
|
const errorEl = document.getElementById("edit-url-error");
|
||||||
|
|
||||||
|
if (!newUrl.startsWith("http://") && !newUrl.startsWith("https://")) {
|
||||||
|
errorEl.textContent = "Ungültige URL (muss mit http:// oder https:// beginnen)";
|
||||||
|
errorEl.style.display = "block";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorEl.style.display = "none";
|
||||||
|
|
||||||
|
fetch("/admin/" + editState.site + "/update-playlist-item/" + editState.screen, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
old_name: editState.oldName,
|
||||||
|
new_url: newUrl,
|
||||||
|
zoom: zoom
|
||||||
|
})
|
||||||
|
}).then(function (r) {
|
||||||
|
if (r.ok) {
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
return r.json().then(function (d) {
|
||||||
|
errorEl.textContent = d.error || "Fehler beim Speichern";
|
||||||
|
errorEl.style.display = "block";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch(function () {
|
||||||
|
errorEl.textContent = "Netzwerkfehler";
|
||||||
|
errorEl.style.display = "block";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- Modal: URL bearbeiten -->
|
||||||
|
<div class="modal fade" id="edit-url-modal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">URL bearbeiten</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">URL</label>
|
||||||
|
<input type="url" class="form-control" id="edit-url-input">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Zoom Faktor (z.B. 1.0, 1.5, 2.0)</label>
|
||||||
|
<input type="number" class="form-control" id="edit-zoom-input" step="0.1" min="0.1" value="1.0">
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-danger py-2" id="edit-url-error" style="display:none"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="edit-url-save">Speichern</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
.playlist-row {
|
.playlist-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns:
|
grid-template-columns:
|
||||||
28px 1fr 90px 80px 90px 32px;
|
28px 1fr 90px 80px 40px 50px 32px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
@@ -171,6 +171,18 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<!-- Bearbeiten (nur URLs) -->
|
||||||
|
{% if file.type == "url" %}
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-ghost-secondary btn-sm edit-url-btn"
|
||||||
|
title="URL bearbeiten"
|
||||||
|
data-name="{{ file.name | e }}"
|
||||||
|
data-zoom="{{ file.zoom | default(1.0) }}"
|
||||||
|
onclick="editPlaylistItem('{{ current_site }}', 'priority', this.dataset.name, this.dataset.zoom)">
|
||||||
|
✎
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- Delete -->
|
<!-- Delete -->
|
||||||
<form action="{{ url_for('delete_file', site=current_site, screen='priority', filename=file.name) }}"
|
<form action="{{ url_for('delete_file', site=current_site, screen='priority', filename=file.name) }}"
|
||||||
method="post"
|
method="post"
|
||||||
@@ -278,14 +290,91 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
|
|
||||||
window.savePlaylist = savePlaylist;
|
window.savePlaylist = savePlaylist;
|
||||||
|
|
||||||
// Initialize tooltips
|
/* ─── Bootstrap Tooltips ─── */
|
||||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||||||
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||||
return new bootstrap.Tooltip(tooltipTriggerEl);
|
return new bootstrap.Tooltip(tooltipTriggerEl);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* ─── Edit-URL-Modal: Speichern-Button ─── */
|
||||||
|
document.getElementById("edit-url-save").addEventListener("click", function () {
|
||||||
|
const newUrl = document.getElementById("edit-url-input").value.trim();
|
||||||
|
const zoom = parseFloat(document.getElementById("edit-zoom-input").value) || 1.0;
|
||||||
|
const errorEl = document.getElementById("edit-url-error");
|
||||||
|
|
||||||
|
if (!newUrl.startsWith("http://") && !newUrl.startsWith("https://")) {
|
||||||
|
errorEl.textContent = "Ungültige URL (muss mit http:// oder https:// beginnen)";
|
||||||
|
errorEl.style.display = "block";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorEl.style.display = "none";
|
||||||
|
|
||||||
|
fetch("/admin/" + editState.site + "/update-playlist-item/" + editState.screen, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
old_name: editState.oldName,
|
||||||
|
new_url: newUrl,
|
||||||
|
zoom: zoom
|
||||||
|
})
|
||||||
|
}).then(function (r) {
|
||||||
|
if (r.ok) {
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
return r.json().then(function (d) {
|
||||||
|
errorEl.textContent = d.error || "Fehler beim Speichern";
|
||||||
|
errorEl.style.display = "block";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch(function () {
|
||||||
|
errorEl.textContent = "Netzwerkfehler";
|
||||||
|
errorEl.style.display = "block";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* ─── Global: editState + editPlaylistItem ─── */
|
||||||
|
var editState = { site: "", screen: "", oldName: "" };
|
||||||
|
|
||||||
|
function editPlaylistItem(site, screen, oldName, zoom) {
|
||||||
|
editState.site = site;
|
||||||
|
editState.screen = screen;
|
||||||
|
editState.oldName = oldName;
|
||||||
|
document.getElementById("edit-url-input").value = oldName;
|
||||||
|
document.getElementById("edit-zoom-input").value = zoom;
|
||||||
|
document.getElementById("edit-url-error").style.display = "none";
|
||||||
|
new bootstrap.Modal(document.getElementById("edit-url-modal")).show();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- Modal: URL bearbeiten -->
|
||||||
|
<div class="modal fade" id="edit-url-modal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">URL bearbeiten</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">URL</label>
|
||||||
|
<input type="url" class="form-control" id="edit-url-input">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Zoom Faktor (z.B. 1.0, 1.5, 2.0)</label>
|
||||||
|
<input type="number" class="form-control" id="edit-zoom-input" step="0.1" min="0.1" value="1.0">
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-danger py-2" id="edit-url-error" style="display:none"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="edit-url-save">Speichern</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user