387 lines
13 KiB
HTML
387 lines
13 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 40px 50px 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/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 %}
|
||
</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>
|
||
|
||
<!-- Bearbeiten (nur URLs) -->
|
||
{% if file.type == "url" %}
|
||
<button type="button"
|
||
class="btn btn-outline-secondary btn-sm edit-url-btn"
|
||
title="URL bearbeiten"
|
||
data-name="{{ file.name | e }}"
|
||
data-zoom="{{ file.zoom | default(1.0) }}"
|
||
data-site="{{ current_site }}"
|
||
data-screen="priority"
|
||
data-oldname="{{ file.name | e }}">
|
||
Edit
|
||
</button>
|
||
{% endif %}
|
||
|
||
<!-- 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>
|
||
|
||
<!-- 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>
|
||
<button class="btn btn-primary" type="submit">URL hinzufügen</button>
|
||
</form>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Footer -->
|
||
{% include "_footer.html" %}
|
||
|
||
</div>
|
||
|
||
<script>
|
||
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);
|
||
});
|
||
|
||
/* ─── Edit-URL-Button: Modal öffnen (Event-Delegation) ─── */
|
||
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;
|
||
document.getElementById("edit-url-input").value = btn.dataset.oldname;
|
||
document.getElementById("edit-zoom-input").value = btn.dataset.zoom;
|
||
document.getElementById("edit-url-error").style.display = "none";
|
||
new bootstrap.Modal(document.getElementById("edit-url-modal")).show();
|
||
});
|
||
|
||
/* ─── 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 ─── */
|
||
var editState = { site: "", screen: "", oldName: "" };
|
||
</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>
|