Die Admin-Ansicht hat der Checkbox das Ergebnis von is_item_active() uebergeben. Ausserhalb des Zeitfensters erschien ein zeitgesteuerter Eintrag dadurch als deaktiviert, und beim naechsten Speichern der Reihenfolge schrieb der Client diesen Anzeigezustand als enabled=false zurueck - der Eintrag war danach dauerhaft stillgelegt. Die Checkbox spiegelt jetzt das gespeicherte enabled-Flag. Der Live-Zustand steckt in einem neuen Feld active und faerbt nur noch das Zeit-Badge (grau, wenn das Fenster gerade zu ist). Der Player filtert entsprechend ueber beide Felder. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
696 lines
28 KiB
HTML
696 lines
28 KiB
HTML
<!doctype html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||
<title>CANCOM Simple Signage – Priority Playlist</title>
|
||
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||
|
||
<script>
|
||
(() => {
|
||
const stored = window.localStorage.getItem("signage-theme");
|
||
const theme = stored === "dark" || stored === "light" ? stored : "light";
|
||
document.documentElement.setAttribute("data-bs-theme", theme);
|
||
})();
|
||
</script>
|
||
|
||
<link rel="stylesheet"
|
||
href="https://unpkg.com/@tabler/core@1.0.0-beta20/dist/css/tabler.min.css">
|
||
<link rel="stylesheet"
|
||
href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css">
|
||
<script defer
|
||
src="https://unpkg.com/@tabler/core@1.0.0-beta20/dist/js/tabler.min.js"></script>
|
||
<script defer
|
||
src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
|
||
|
||
{% include "_styles.html" %}
|
||
|
||
<style>
|
||
.playlist-row {
|
||
display: grid;
|
||
grid-template-columns:
|
||
28px 1fr 90px 80px 50px 40px 32px;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
}
|
||
|
||
.playlist-name {
|
||
font-weight: 600;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.playlist-size {
|
||
color: #6c757d;
|
||
font-size: 0.9em;
|
||
}
|
||
|
||
.drag-handle {
|
||
cursor: grab;
|
||
font-size: 1.2em;
|
||
text-align: center;
|
||
user-select: none;
|
||
}
|
||
.drag-handle:active {
|
||
cursor: grabbing;
|
||
}
|
||
|
||
.card-body .nav-tabs {
|
||
margin-left: -1rem;
|
||
margin-right: -1rem;
|
||
padding: 0 1rem;
|
||
border-bottom-color: var(--ccm-border);
|
||
}
|
||
.card-body .nav-tabs .nav-link {
|
||
background: transparent;
|
||
color: var(--ccm-muted);
|
||
}
|
||
.card-body .nav-tabs .nav-link.active {
|
||
background: var(--ccm-surface);
|
||
border-color: var(--ccm-border) var(--ccm-border) var(--ccm-surface);
|
||
color: var(--ccm-text);
|
||
}
|
||
.card-body .tab-content {
|
||
background: var(--ccm-surface);
|
||
padding: 1rem;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="page">
|
||
|
||
{% set brand_subtitle = "Priority Playlist" %}
|
||
{% set site_dropdown_url_prefix = "/admin/" %}
|
||
{% include "_header.html" %}
|
||
|
||
<!-- Second Nav -->
|
||
<div class="navbar-expand-md">
|
||
<div class="collapse navbar-collapse show">
|
||
<div class="navbar navbar-dark nav-surface">
|
||
<div class="container-xl">
|
||
<div class="d-flex flex-wrap gap-2 py-2">
|
||
<a class="btn btn-white" href="/admin/{{ current_site }}"><i class="ti ti-layout-dashboard me-1"></i>Übersicht</a>
|
||
<a class="btn btn-white active" href="/admin/{{ current_site }}/priority"><i class="ti ti-alert-triangle me-1"></i>Priority Playlist</a>
|
||
<a class="btn btn-white" href="/willkommen?site={{ current_site }}"><i class="ti ti-file-plus me-1"></i>Willkommensseite</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Content -->
|
||
<div class="page-wrapper">
|
||
<div class="container-xl mt-4">
|
||
|
||
<!-- ========================= -->
|
||
<!-- PRIORITY / GLOBAL PLAYLIST -->
|
||
<!-- ========================= -->
|
||
<div class="card mb-6">
|
||
<div class="card-header d-flex justify-content-between align-items-center">
|
||
<span>
|
||
<div class="page-title" style="color:#DA002D;">⚠ PRIORITY Playlist</div>
|
||
<div class="page-subtitle">wirkt auf alle Player</div>
|
||
</span>
|
||
</div>
|
||
|
||
<div class="card-body">
|
||
|
||
<ul class="nav nav-tabs" data-bs-toggle="tabs" role="tablist">
|
||
<li class="nav-item" role="presentation">
|
||
<a href="#tab-prio-playlist" class="nav-link active" data-bs-toggle="tab" role="tab">
|
||
<i class="ti ti-list me-1"></i>Playlist
|
||
</a>
|
||
</li>
|
||
<li class="nav-item" role="presentation">
|
||
<a href="#tab-prio-media" class="nav-link" data-bs-toggle="tab" role="tab">
|
||
<i class="ti ti-upload me-1"></i>Medien
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
|
||
<div class="tab-content">
|
||
|
||
<!-- Tab: Playlist -->
|
||
<div class="tab-pane active" id="tab-prio-playlist" role="tabpanel">
|
||
<ul class="list-group mb-3" id="playlist-priority">
|
||
{% for file in priority_files %}
|
||
<li class="list-group-item playlist-row"
|
||
data-file="{{ file.name }}">
|
||
|
||
<input class="form-check-input playlist-enabled"
|
||
type="checkbox"
|
||
{% if file.enabled %}checked{% endif %}
|
||
title="Element aktivieren">
|
||
|
||
<span class="playlist-name"
|
||
{% if file.type == "image" %}
|
||
data-bs-toggle="tooltip"
|
||
data-bs-html="true"
|
||
title="<img src='/media/{{ current_site }}/priority/{{ file.name }}' style='max-width: 200px; max-height: 150px; border: none; background: white; padding: 2px;' alt='Vorschau'>"
|
||
{% elif file.type == "url" %}
|
||
data-bs-toggle="tooltip"
|
||
title="URL: {{ file.name }}{% if file.zoom %} (Zoom: {{ file.zoom }}x){% endif %}"
|
||
{% endif %}>
|
||
{{ file.name }}{% if file.type == "url" and file.zoom != 1.0 %} <strong style="color: #DA002D;">[{{ file.zoom }}x]</strong>{% endif %}{% if file.type == "url" %}{% if file.get('passthrough', False) %} <i class="ti ti-link" style="color:#6c757d;" title="Query-Parameter werden übergeben"></i>{% else %} <i class="ti ti-link-off" style="color:#adb5bd;" title="Query-Parameter werden nicht übergeben"></i>{% endif %}{% endif %}
|
||
{% if file.schedule %}<span class="badge ms-1 {% if file.active %}bg-info text-white{% else %}bg-secondary-lt{% endif %}" title="{{ file.schedule | schedule_text }}{% if not file.active %} — laeuft gerade nicht{% endif %}">Zeit</span>{% endif %}
|
||
</span>
|
||
|
||
{% if file.type == "image" %}
|
||
<span class="badge bg-purple text-purple-fg">Bild</span>
|
||
{% elif file.type == "url" or file.type == "html" %}
|
||
<span class="badge bg-blue text-blue-fg">URL</span>
|
||
{% else %}
|
||
<span class="badge bg-orange text-orange-fg">Video</span>
|
||
{% endif %}
|
||
|
||
<span class="playlist-size">
|
||
{% if file.type == "url" %}
|
||
{{ file.size }}
|
||
{% else %}
|
||
{{ file.size }} KB
|
||
{% endif %}
|
||
</span>
|
||
|
||
<!-- Delete -->
|
||
<form action="{{ url_for('delete_file', site=current_site, screen='priority', filename=file.name) }}"
|
||
method="post"
|
||
class="d-inline"
|
||
onmousedown="event.stopPropagation()">
|
||
<button type="submit"
|
||
class="btn btn-outline-danger btn-sm"
|
||
onclick="return confirm('Datei wirklich löschen?')">
|
||
Löschen
|
||
</button>
|
||
</form>
|
||
|
||
<!-- Bearbeiten -->
|
||
<button type="button"
|
||
class="btn btn-outline-secondary btn-sm edit-url-btn"
|
||
title="Bearbeiten"
|
||
data-name="{{ file.name | e }}"
|
||
data-type="{{ file.type }}"
|
||
data-zoom="{{ file.zoom | default(1.0) }}"
|
||
data-site="{{ current_site }}"
|
||
data-screen="priority"
|
||
data-oldname="{{ file.name | e }}"
|
||
data-passthrough="{{ 'true' if file.get('passthrough', False) else 'false' }}"
|
||
data-schedule='{{ file.schedule | tojson }}'>
|
||
Edit
|
||
</button>
|
||
|
||
<!-- Drag -->
|
||
<span class="drag-handle" title="Reihenfolge ändern">⇅</span>
|
||
</li>
|
||
{% endfor %}
|
||
</ul>
|
||
|
||
<button class="btn btn-primary"
|
||
onclick="savePlaylist('{{ current_site }}', 'priority')">
|
||
Priority-Playlist speichern
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Tab: Medien -->
|
||
<div class="tab-pane" id="tab-prio-media" role="tabpanel">
|
||
<h3>Medien hochladen</h3>
|
||
<form action="/admin/{{ current_site }}/upload/priority"
|
||
method="post" enctype="multipart/form-data">
|
||
<div class="input-group mb-3">
|
||
<input type="file" name="file" class="form-control" required>
|
||
<button class="btn btn-primary" type="submit">Upload</button>
|
||
</div>
|
||
</form>
|
||
|
||
<hr>
|
||
<h3>URL hinzufügen</h3>
|
||
<form action="/admin/{{ current_site }}/add-url/priority" method="post" class="mb-3">
|
||
<div class="mb-2">
|
||
<label class="form-label">URL</label>
|
||
<input type="url" name="url" class="form-control"
|
||
placeholder="https://example.com" required>
|
||
</div>
|
||
<div class="mb-2">
|
||
<label class="form-label">Zoom Faktor (z.B. 1.0, 1.5, 2.0)</label>
|
||
<input type="number" name="zoom" class="form-control" step="0.1" value="1.0" min="0.1">
|
||
</div>
|
||
|
||
<!-- Zeitsteuerung -->
|
||
<hr class="mt-4 mb-3">
|
||
<h3>Zeitsteuerung</h3>
|
||
|
||
<div class="mb-2 form-check">
|
||
<input class="form-check-input" type="checkbox" id="schedule-enabled-priority" name="schedule_enabled">
|
||
<label class="form-check-label" for="schedule-enabled-priority">Zeitgesteuerte Wiedergabe</label>
|
||
</div>
|
||
|
||
<div class="mb-3" id="schedule-options-priority" style="display: none;">
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label">Wiederholung</label>
|
||
<select class="form-select" name="schedule_mode">
|
||
<option value="once">Einmalig</option>
|
||
<option value="daily">Täglich</option>
|
||
<option value="weekly">Wöchentlich</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- Einmalig: Datum + Uhrzeit -->
|
||
<div class="row mb-3" id="schedule-datetime-priority">
|
||
<div class="col">
|
||
<label class="form-label">Start</label>
|
||
<input type="datetime-local" class="form-control" name="schedule_start">
|
||
</div>
|
||
<div class="col">
|
||
<label class="form-label">Ende</label>
|
||
<input type="datetime-local" class="form-control" name="schedule_end">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Täglich / Wöchentlich: nur Uhrzeit -->
|
||
<div class="row mb-3" id="schedule-time-priority" style="display: none;">
|
||
<div class="col">
|
||
<label class="form-label">Von</label>
|
||
<input type="time" class="form-control" name="schedule_start_time">
|
||
</div>
|
||
<div class="col">
|
||
<label class="form-label">Bis</label>
|
||
<input type="time" class="form-control" name="schedule_end_time">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mb-3" id="schedule-validity-priority" style="display: none;">
|
||
<label class="form-label">Gültig von / bis <span class="text-muted">(optional)</span></label>
|
||
<div class="row">
|
||
<div class="col">
|
||
<input type="date" class="form-control" name="schedule_valid_from">
|
||
</div>
|
||
<div class="col">
|
||
<input type="date" class="form-control" name="schedule_valid_until">
|
||
</div>
|
||
</div>
|
||
<small class="form-hint">Ohne Angabe läuft die Wiedergabe unbefristet.</small>
|
||
</div>
|
||
|
||
<div class="mb-3" id="schedule-days-priority" style="display: none;">
|
||
<label class="form-label">Wochentage</label>
|
||
<div class="d-flex gap-2">
|
||
{% for nr, tag in [(0, 'Mo'), (1, 'Di'), (2, 'Mi'), (3, 'Do'), (4, 'Fr'), (5, 'Sa'), (6, 'So')] %}
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" name="schedule_days" value="{{ nr }}" id="prio-day-{{ nr }}">
|
||
<label class="form-check-label" for="prio-day-{{ nr }}">{{ tag }}</label>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<button class="btn btn-primary" type="submit">URL hinzufügen</button>
|
||
</form>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Footer -->
|
||
{% include "_footer.html" %}
|
||
|
||
</div>
|
||
|
||
<script>
|
||
/* ─── Global: editState ─── */
|
||
var editState = { site: "", screen: "", oldName: "", type: "url" };
|
||
|
||
/* Uhrzeit aus "HH:MM" oder einem vollen ISO-Zeitstempel herausziehen */
|
||
function scheduleClock(value) {
|
||
if (!value) return "";
|
||
var text = String(value).replace(" ", "T");
|
||
var idx = text.indexOf("T");
|
||
if (idx !== -1) text = text.substring(idx + 1);
|
||
return text.substring(0, 5);
|
||
}
|
||
|
||
/* Sichtbarkeit der Zeitsteuerungs-Felder im Modal an den Modus koppeln.
|
||
Muss auf Top-Level stehen - der Edit-Handler unten läuft ausserhalb
|
||
von DOMContentLoaded. */
|
||
function updateScheduleDaysVisibility() {
|
||
var modeEl = document.getElementById("edit-schedule-mode");
|
||
if (!modeEl) return;
|
||
var wiederkehrend = modeEl.value === "daily" || modeEl.value === "weekly";
|
||
|
||
var daysEl = document.getElementById("edit-schedule-days");
|
||
var validityEl = document.getElementById("edit-schedule-validity");
|
||
var datetimeEl = document.getElementById("edit-schedule-datetime");
|
||
var timeEl = document.getElementById("edit-schedule-time");
|
||
|
||
// Wochentage nur bei "wöchentlich" - bei "täglich" gilt ohnehin jeder Tag
|
||
if (daysEl) daysEl.style.display = modeEl.value === "weekly" ? "" : "none";
|
||
// Gültigkeitszeitraum ergänzt die Tageszeit; bei "einmalig" steckt das
|
||
// Datum bereits in Start und Ende.
|
||
if (validityEl) validityEl.style.display = wiederkehrend ? "" : "none";
|
||
if (datetimeEl) datetimeEl.style.display = wiederkehrend ? "none" : "";
|
||
if (timeEl) timeEl.style.display = wiederkehrend ? "" : "none";
|
||
}
|
||
|
||
/* Event-Delegation für Edit-Buttons (global, sofort aktiv) */
|
||
document.addEventListener("click", function (e) {
|
||
var btn = e.target.closest(".edit-url-btn");
|
||
if (!btn) return;
|
||
e.preventDefault();
|
||
editState.site = btn.dataset.site;
|
||
editState.screen = btn.dataset.screen;
|
||
editState.oldName = btn.dataset.oldname;
|
||
editState.type = btn.dataset.type || "url";
|
||
|
||
var isUrl = editState.type === "url";
|
||
document.getElementById("edit-url-input").value = btn.dataset.oldname;
|
||
document.getElementById("edit-zoom-input").value = btn.dataset.zoom;
|
||
document.getElementById("edit-passthrough-input").checked = btn.dataset.passthrough === "true";
|
||
|
||
/* Bei Dateien gibt es keine URL, keinen Zoom und kein Passthrough -
|
||
dann bleibt nur die Zeitsteuerung übrig. */
|
||
document.getElementById("edit-url-row").style.display = isUrl ? "" : "none";
|
||
document.getElementById("edit-zoom-row").style.display = isUrl ? "" : "none";
|
||
document.getElementById("edit-passthrough-row").style.display = isUrl ? "" : "none";
|
||
document.getElementById("edit-url-modal-title").textContent =
|
||
isUrl ? "URL bearbeiten" : "Zeitsteuerung: " + btn.dataset.oldname;
|
||
|
||
var schedule = JSON.parse(btn.dataset.schedule || "null");
|
||
var enabledEl = document.getElementById("edit-schedule-enabled");
|
||
var optionsEl = document.getElementById("edit-schedule-options");
|
||
|
||
if (schedule) {
|
||
enabledEl.checked = true;
|
||
optionsEl.style.display = "";
|
||
document.getElementById("edit-schedule-mode").value = schedule.mode || "once";
|
||
|
||
var wiederkehrend = (schedule.mode === "daily" || schedule.mode === "weekly");
|
||
document.getElementById("edit-schedule-start").value = wiederkehrend ? "" : (schedule.start || "");
|
||
document.getElementById("edit-schedule-end").value = wiederkehrend ? "" : (schedule.end || "");
|
||
document.getElementById("edit-schedule-start-time").value = scheduleClock(schedule.start);
|
||
document.getElementById("edit-schedule-end-time").value = scheduleClock(schedule.end);
|
||
document.getElementById("edit-schedule-valid-from").value = schedule.valid_from || "";
|
||
document.getElementById("edit-schedule-valid-until").value = schedule.valid_until || "";
|
||
|
||
var days = schedule.days || [];
|
||
for (var i = 0; i < 7; i++) {
|
||
document.getElementById("edit-prio-day-" + i).checked = days.indexOf(String(i)) !== -1;
|
||
}
|
||
} else {
|
||
enabledEl.checked = false;
|
||
optionsEl.style.display = "none";
|
||
document.getElementById("edit-schedule-start").value = "";
|
||
document.getElementById("edit-schedule-end").value = "";
|
||
document.getElementById("edit-schedule-start-time").value = "";
|
||
document.getElementById("edit-schedule-end-time").value = "";
|
||
document.getElementById("edit-schedule-valid-from").value = "";
|
||
document.getElementById("edit-schedule-valid-until").value = "";
|
||
for (var j = 0; j < 7; j++) {
|
||
document.getElementById("edit-prio-day-" + j).checked = false;
|
||
}
|
||
}
|
||
updateScheduleDaysVisibility();
|
||
|
||
document.getElementById("edit-url-error").style.display = "none";
|
||
new bootstrap.Modal(document.getElementById("edit-url-modal")).show();
|
||
});
|
||
|
||
document.addEventListener("DOMContentLoaded", function () {
|
||
|
||
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"
|
||
});
|
||
}
|
||
|
||
initSortable("priority");
|
||
|
||
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 ─── */
|
||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||
return new bootstrap.Tooltip(tooltipTriggerEl);
|
||
});
|
||
|
||
/* ─── Zeitsteuerung: Änderungs-Events im Modal ─── */
|
||
document.getElementById("edit-schedule-enabled").addEventListener("change", function () {
|
||
document.getElementById("edit-schedule-options").style.display = this.checked ? "" : "none";
|
||
});
|
||
|
||
document.getElementById("edit-schedule-mode").addEventListener("change", updateScheduleDaysVisibility);
|
||
updateScheduleDaysVisibility();
|
||
|
||
/* ─── "URL hinzufügen"-Formular: Zeitsteuerungs-Felder ein-/ausblenden ─── */
|
||
(function () {
|
||
var cb = document.getElementById("schedule-enabled-priority");
|
||
if (!cb) return;
|
||
var optionsEl = document.getElementById("schedule-options-priority");
|
||
var daysEl = document.getElementById("schedule-days-priority");
|
||
var validityEl = document.getElementById("schedule-validity-priority");
|
||
var datetimeEl = document.getElementById("schedule-datetime-priority");
|
||
var timeEl = document.getElementById("schedule-time-priority");
|
||
var modeEl = cb.closest("form").querySelector('[name="schedule_mode"]');
|
||
|
||
function sync() {
|
||
var mode = modeEl ? modeEl.value : "once";
|
||
var wiederkehrend = (mode === "daily" || mode === "weekly");
|
||
if (optionsEl) optionsEl.style.display = cb.checked ? "" : "none";
|
||
if (daysEl) daysEl.style.display = (cb.checked && mode === "weekly") ? "" : "none";
|
||
if (validityEl) validityEl.style.display = (cb.checked && wiederkehrend) ? "" : "none";
|
||
if (datetimeEl) datetimeEl.style.display = wiederkehrend ? "none" : "";
|
||
if (timeEl) timeEl.style.display = wiederkehrend ? "" : "none";
|
||
}
|
||
|
||
cb.addEventListener("change", sync);
|
||
if (modeEl) modeEl.addEventListener("change", sync);
|
||
sync();
|
||
})();
|
||
|
||
/* ─── Edit-URL-Modal: Speichern-Button ─── */
|
||
document.getElementById("edit-url-save").addEventListener("click", function () {
|
||
const isUrl = editState.type === "url";
|
||
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 (isUrl && !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";
|
||
|
||
// Zeitsteuerung sammeln
|
||
const scheduleEnabled = document.getElementById("edit-schedule-enabled").checked;
|
||
let schedule = null;
|
||
|
||
if (scheduleEnabled) {
|
||
const mode = document.getElementById("edit-schedule-mode").value;
|
||
const wiederkehrend = (mode === "daily" || mode === "weekly");
|
||
|
||
// Einmalig arbeitet mit Datum + Uhrzeit, täglich/wöchentlich nur mit Uhrzeit
|
||
const start = wiederkehrend
|
||
? document.getElementById("edit-schedule-start-time").value
|
||
: document.getElementById("edit-schedule-start").value;
|
||
const end = wiederkehrend
|
||
? document.getElementById("edit-schedule-end-time").value
|
||
: document.getElementById("edit-schedule-end").value;
|
||
|
||
if (!start || !end) {
|
||
errorEl.textContent = wiederkehrend
|
||
? "Von- und Bis-Uhrzeit müssen angegeben werden"
|
||
: "Start und Ende müssen angegeben werden";
|
||
errorEl.style.display = "block";
|
||
return;
|
||
}
|
||
|
||
schedule = { mode: mode, start: start, end: end };
|
||
|
||
if (wiederkehrend) {
|
||
const validFrom = document.getElementById("edit-schedule-valid-from").value;
|
||
const validUntil = document.getElementById("edit-schedule-valid-until").value;
|
||
if (validFrom && validUntil && validFrom > validUntil) {
|
||
errorEl.textContent = "\"Gültig von\" liegt nach \"Gültig bis\"";
|
||
errorEl.style.display = "block";
|
||
return;
|
||
}
|
||
if (validFrom) schedule.valid_from = validFrom;
|
||
if (validUntil) schedule.valid_until = validUntil;
|
||
}
|
||
|
||
if (mode === "weekly") {
|
||
const days = [];
|
||
for (var i = 0; i < 7; i++) {
|
||
if (document.getElementById("edit-prio-day-" + i).checked) days.push(String(i));
|
||
}
|
||
schedule.days = days;
|
||
}
|
||
}
|
||
|
||
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: isUrl ? newUrl : null,
|
||
zoom: zoom,
|
||
passthrough: isUrl ? document.getElementById("edit-passthrough-input").checked : null,
|
||
schedule: schedule || {}
|
||
})
|
||
}).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" id="edit-url-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" id="edit-url-row">
|
||
<label class="form-label">URL</label>
|
||
<input type="url" class="form-control" id="edit-url-input">
|
||
</div>
|
||
<div class="mb-3" id="edit-zoom-row">
|
||
<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="mb-3 form-check" id="edit-passthrough-row">
|
||
<input type="checkbox" class="form-check-input" id="edit-passthrough-input">
|
||
<label class="form-check-label" for="edit-passthrough-input">Query-Parameter an diese URL übergeben</label>
|
||
</div>
|
||
|
||
<!-- Zeitsteuerung -->
|
||
<div class="mb-3 form-check">
|
||
<input class="form-check-input" type="checkbox" id="edit-schedule-enabled">
|
||
<label class="form-check-label" for="edit-schedule-enabled">Zeitgesteuerte Wiedergabe</label>
|
||
</div>
|
||
|
||
<div id="edit-schedule-options" style="display: none;">
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label">Wiederholung</label>
|
||
<select class="form-select" id="edit-schedule-mode">
|
||
<option value="once">Einmalig</option>
|
||
<option value="daily">Täglich</option>
|
||
<option value="weekly">Wöchentlich</option>
|
||
</select>
|
||
</div>
|
||
|
||
<!-- Einmalig: Datum + Uhrzeit -->
|
||
<div class="row mb-3" id="edit-schedule-datetime">
|
||
<div class="col">
|
||
<label class="form-label">Start</label>
|
||
<input type="datetime-local" class="form-control" id="edit-schedule-start">
|
||
</div>
|
||
<div class="col">
|
||
<label class="form-label">Ende</label>
|
||
<input type="datetime-local" class="form-control" id="edit-schedule-end">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Täglich / Wöchentlich: nur Uhrzeit -->
|
||
<div class="row mb-3" id="edit-schedule-time" style="display: none;">
|
||
<div class="col">
|
||
<label class="form-label">Von</label>
|
||
<input type="time" class="form-control" id="edit-schedule-start-time">
|
||
</div>
|
||
<div class="col">
|
||
<label class="form-label">Bis</label>
|
||
<input type="time" class="form-control" id="edit-schedule-end-time">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mb-3" id="edit-schedule-validity" style="display: none;">
|
||
<label class="form-label">Gültig von / bis <span class="text-muted">(optional)</span></label>
|
||
<div class="row">
|
||
<div class="col">
|
||
<input type="date" class="form-control" id="edit-schedule-valid-from">
|
||
</div>
|
||
<div class="col">
|
||
<input type="date" class="form-control" id="edit-schedule-valid-until">
|
||
</div>
|
||
</div>
|
||
<small class="form-hint">Ohne Angabe läuft die Wiedergabe unbefristet.</small>
|
||
</div>
|
||
|
||
<div class="mb-3" id="edit-schedule-days" style="display: none;">
|
||
<label class="form-label">Wochentage</label>
|
||
<div class="d-flex gap-2">
|
||
{% for nr, tag in [(0, 'Mo'), (1, 'Di'), (2, 'Mi'), (3, 'Do'), (4, 'Fr'), (5, 'Sa'), (6, 'So')] %}
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" value="{{ nr }}" id="edit-prio-day-{{ nr }}">
|
||
<label class="form-check-label" for="edit-prio-day-{{ nr }}">{{ tag }}</label>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</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>
|