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
|
||||
|
||||
|
||||
# -------------------------------------------------
|
||||
# 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)
|
||||
# -------------------------------------------------
|
||||
|
||||
@@ -43,11 +43,11 @@
|
||||
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 {
|
||||
display: grid;
|
||||
grid-template-columns:
|
||||
28px 1fr 90px 80px 90px 32px;
|
||||
28px 1fr 90px 80px 40px 50px 32px;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
@@ -271,6 +271,18 @@
|
||||
{% endif %}
|
||||
</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 -->
|
||||
<form action="{{ url_for('delete_file', site=current_site, screen=screen, filename=file.name) }}"
|
||||
method="post">
|
||||
@@ -577,8 +589,138 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
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>
|
||||
|
||||
<!-- 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>
|
||||
</html>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
.playlist-row {
|
||||
display: grid;
|
||||
grid-template-columns:
|
||||
28px 1fr 90px 80px 90px 32px;
|
||||
28px 1fr 90px 80px 40px 50px 32px;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
@@ -171,6 +171,18 @@
|
||||
{% endif %}
|
||||
</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 -->
|
||||
<form action="{{ url_for('delete_file', site=current_site, screen='priority', filename=file.name) }}"
|
||||
method="post"
|
||||
@@ -278,14 +290,91 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
|
||||
window.savePlaylist = savePlaylist;
|
||||
|
||||
// Initialize tooltips
|
||||
/* ─── Bootstrap Tooltips ─── */
|
||||
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";
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
/* ─── 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>
|
||||
|
||||
<!-- 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>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user