334 lines
10 KiB
PHP
334 lines
10 KiB
PHP
<?php
|
|
require_once __DIR__ . '/../src/Services/LogoService.php';
|
|
?>
|
|
|
|
<?php if ($saved): ?>
|
|
<div class="notice">Sender gespeichert. EPG wird beim nächsten Laden neu aufgebaut.</div>
|
|
<?php endif; ?>
|
|
|
|
<form method="post" class="settings-form" id="channelsForm">
|
|
<div class="settings-header channels-sticky-header">
|
|
<div>
|
|
<h2>Senderverwaltung</h2>
|
|
<p class="settings-subtitle">Aktiviere Sender, markiere Favoriten und ändere die Reihenfolge.</p>
|
|
</div>
|
|
<button type="submit">Speichern</button>
|
|
</div>
|
|
|
|
<div class="channel-toolbar">
|
|
<input type="search" id="channelSearch" placeholder="Sender suchen...">
|
|
|
|
<?php
|
|
$groups = [];
|
|
|
|
foreach ($channelConfig as $channel) {
|
|
$group = $channel['group'] ?? 'Sonstige';
|
|
$groups[$group] = true;
|
|
}
|
|
|
|
ksort($groups, SORT_NATURAL | SORT_FLAG_CASE);
|
|
?>
|
|
|
|
<select id="groupFilter">
|
|
<option value="">Alle Gruppen</option>
|
|
|
|
<?php foreach (array_keys($groups) as $group): ?>
|
|
<option value="<?= htmlspecialchars($group) ?>">
|
|
<?= htmlspecialchars($group) ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
|
|
<div class="channel-filter-buttons">
|
|
<button type="button" data-filter="all" class="active">Alle</button>
|
|
<button type="button" data-filter="enabled">Aktiv</button>
|
|
<button type="button" data-filter="disabled">Deaktiviert</button>
|
|
<button type="button" data-filter="favorite">Favoriten</button>
|
|
<button type="button" data-filter="changed">Geändert</button>
|
|
</div>
|
|
|
|
<div class="channel-actions">
|
|
<button type="button" id="enableVisible">Sichtbare aktivieren</button>
|
|
<button type="button" id="disableVisible">Sichtbare deaktivieren</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="channel-count">
|
|
<span id="visibleCount">0</span> von <?= count($channelConfig) ?> Sendern sichtbar
|
|
<span id="duplicateWarning" class="duplicate-warning" hidden>⚠ Doppelte Sendernummern</span>
|
|
</div>
|
|
|
|
<div class="channel-settings-list">
|
|
<?php foreach ($channelConfig as $id => $channel): ?>
|
|
<?php
|
|
$enabled = !empty($channel['enabled']);
|
|
$favorite = !empty($channel['favorite']);
|
|
$name = $channel['name'] ?? $id;
|
|
$logoUrl = LogoService::getLogoUrl($id);
|
|
$number = (int)($channel['number'] ?? 999999);
|
|
$group = $channel['group'] ?? 'Unknown';
|
|
?>
|
|
|
|
<article
|
|
class="channel-settings-card"
|
|
data-name="<?= htmlspecialchars(strtolower($name . ' ' . $id)) ?>"
|
|
data-enabled="<?= $enabled ? '1' : '0' ?>"
|
|
data-favorite="<?= $favorite ? '1' : '0' ?>"
|
|
data-group="<?= htmlspecialchars($channel['group'] ?? 'Sonstige') ?>"
|
|
data-original-enabled="<?= $enabled ? '1' : '0' ?>"
|
|
data-original-favorite="<?= $favorite ? '1' : '0' ?>"
|
|
data-original-number="<?= $number ?>"
|
|
data-original-group="<?= htmlspecialchars($group) ?>"
|
|
data-changed="0"
|
|
>
|
|
<div class="channel-settings-main">
|
|
<div class="drag-handle" draggable="true" title="Zum Sortieren ziehen">☰</div>
|
|
<div class="channel-logo">
|
|
<img src="<?= htmlspecialchars($logoUrl) ?>" alt="">
|
|
</div>
|
|
<label class="switch-row">
|
|
<input class="enabled-checkbox" type="checkbox" name="enabled[<?= htmlspecialchars($id) ?>]" <?= $enabled ? 'checked' : '' ?>>
|
|
<span class="channel-settings-title"><?= htmlspecialchars($name) ?></span>
|
|
</label>
|
|
|
|
<div class="channel-id"><?= htmlspecialchars($id) ?></div>
|
|
</div>
|
|
|
|
<div class="channel-settings-fields">
|
|
<label>
|
|
Nummer
|
|
<input class="number-input" type="number" name="number[<?= htmlspecialchars($id) ?>]" value="<?= $number ?>">
|
|
</label>
|
|
|
|
<label>
|
|
Gruppe
|
|
<input class="group-input" type="text" name="group[<?= htmlspecialchars($id) ?>]" value="<?= htmlspecialchars($group) ?>">
|
|
</label>
|
|
|
|
<label class="favorite-toggle">
|
|
<input class="favorite-checkbox" type="checkbox" name="favorite[<?= htmlspecialchars($id) ?>]" <?= $favorite ? 'checked' : '' ?>>
|
|
⭐ Favorit
|
|
</label>
|
|
</div>
|
|
</article>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</form>
|
|
|
|
<script>
|
|
(function () {
|
|
const search = document.getElementById('channelSearch');
|
|
const list = document.querySelector('.channel-settings-list');
|
|
const cards = Array.from(document.querySelectorAll('.channel-settings-card'));
|
|
const visibleCount = document.getElementById('visibleCount');
|
|
const duplicateWarning = document.getElementById('duplicateWarning');
|
|
const filterButtons = Array.from(document.querySelectorAll('[data-filter]'));
|
|
const enableVisible = document.getElementById('enableVisible');
|
|
const disableVisible = document.getElementById('disableVisible');
|
|
const groupFilter = document.getElementById('groupFilter');
|
|
|
|
let currentFilter = 'all';
|
|
let draggedCard = null;
|
|
|
|
function refreshCardState(card) {
|
|
const enabled = card.querySelector('.enabled-checkbox').checked;
|
|
const favorite = card.querySelector('.favorite-checkbox').checked;
|
|
const number = card.querySelector('.number-input').value;
|
|
const group = card.querySelector('.group-input').value;
|
|
|
|
card.dataset.enabled = enabled ? '1' : '0';
|
|
card.dataset.favorite = favorite ? '1' : '0';
|
|
|
|
const changed =
|
|
card.dataset.enabled !== card.dataset.originalEnabled ||
|
|
card.dataset.favorite !== card.dataset.originalFavorite ||
|
|
String(number) !== String(card.dataset.originalNumber) ||
|
|
String(group) !== String(card.dataset.originalGroup);
|
|
|
|
card.dataset.changed = changed ? '1' : '0';
|
|
card.classList.toggle('is-changed', changed);
|
|
}
|
|
|
|
function markDuplicateNumbers() {
|
|
const numbers = new Map();
|
|
|
|
cards.forEach(card => {
|
|
const input = card.querySelector('.number-input');
|
|
input.classList.remove('has-duplicate');
|
|
|
|
const number = input.value.trim();
|
|
if (!number || number === '999999') return;
|
|
|
|
if (!numbers.has(number)) numbers.set(number, []);
|
|
numbers.get(number).push(input);
|
|
});
|
|
|
|
let hasDuplicates = false;
|
|
|
|
numbers.forEach(inputs => {
|
|
if (inputs.length > 1) {
|
|
hasDuplicates = true;
|
|
inputs.forEach(input => input.classList.add('has-duplicate'));
|
|
}
|
|
});
|
|
|
|
duplicateWarning.hidden = !hasDuplicates;
|
|
}
|
|
|
|
function applyFilter() {
|
|
const q = search.value.trim().toLowerCase();
|
|
const selectedGroup = groupFilter.value;
|
|
let count = 0;
|
|
|
|
cards.forEach(card => {
|
|
refreshCardState(card);
|
|
|
|
const matchesSearch = !q || card.dataset.name.includes(q);
|
|
|
|
const matchesFilter =
|
|
currentFilter === 'all' ||
|
|
(currentFilter === 'enabled' && card.dataset.enabled === '1') ||
|
|
(currentFilter === 'disabled' && card.dataset.enabled === '0') ||
|
|
(currentFilter === 'favorite' && card.dataset.favorite === '1') ||
|
|
(currentFilter === 'changed' && card.dataset.changed === '1');
|
|
|
|
const matchesGroup =
|
|
!selectedGroup ||
|
|
card.dataset.group === selectedGroup;
|
|
|
|
const visible = matchesSearch && matchesFilter && matchesGroup;
|
|
|
|
card.style.display = visible ? '' : 'none';
|
|
|
|
if (visible) count++;
|
|
});
|
|
|
|
visibleCount.textContent = count;
|
|
markDuplicateNumbers();
|
|
}
|
|
|
|
function getDragAfterElement(container, y) {
|
|
const visibleCards = Array.from(container.querySelectorAll('.channel-settings-card:not(.is-dragging)'))
|
|
.filter(card => card.style.display !== 'none');
|
|
|
|
let closest = null;
|
|
let closestOffset = Number.NEGATIVE_INFINITY;
|
|
|
|
visibleCards.forEach(card => {
|
|
const box = card.getBoundingClientRect();
|
|
const offset = y - box.top - box.height / 2;
|
|
|
|
if (offset < 0 && offset > closestOffset) {
|
|
closestOffset = offset;
|
|
closest = card;
|
|
}
|
|
});
|
|
|
|
return closest;
|
|
}
|
|
|
|
function renumberVisible() {
|
|
let number = 10;
|
|
|
|
Array.from(list.querySelectorAll('.channel-settings-card'))
|
|
.forEach(card => {
|
|
card.querySelector('.number-input').value = number;
|
|
number += 10;
|
|
refreshCardState(card);
|
|
});
|
|
|
|
markDuplicateNumbers();
|
|
}
|
|
|
|
cards.forEach(card => {
|
|
const handle = card.querySelector('.drag-handle');
|
|
|
|
handle.setAttribute('draggable', 'true');
|
|
|
|
handle.addEventListener('dragstart', event => {
|
|
draggedCard = card;
|
|
card.classList.add('is-dragging');
|
|
event.dataTransfer.effectAllowed = 'move';
|
|
event.dataTransfer.setData('text/plain', card.dataset.name || 'channel');
|
|
});
|
|
|
|
handle.addEventListener('dragend', () => {
|
|
if (draggedCard) {
|
|
draggedCard.classList.remove('is-dragging');
|
|
}
|
|
|
|
draggedCard = null;
|
|
renumberVisible();
|
|
applyFilter();
|
|
});
|
|
});
|
|
|
|
list.addEventListener('dragover', event => {
|
|
if (!draggedCard) return;
|
|
|
|
event.preventDefault();
|
|
event.dataTransfer.dropEffect = 'move';
|
|
|
|
const afterElement = getDragAfterElement(list, event.clientY);
|
|
|
|
if (afterElement == null) {
|
|
list.appendChild(draggedCard);
|
|
} else {
|
|
list.insertBefore(draggedCard, afterElement);
|
|
}
|
|
});
|
|
|
|
list.addEventListener('drop', event => {
|
|
event.preventDefault();
|
|
|
|
if (draggedCard) {
|
|
draggedCard.classList.remove('is-dragging');
|
|
}
|
|
|
|
draggedCard = null;
|
|
renumberVisible();
|
|
applyFilter();
|
|
});
|
|
|
|
filterButtons.forEach(button => {
|
|
button.addEventListener('click', () => {
|
|
filterButtons.forEach(b => b.classList.remove('active'));
|
|
button.classList.add('active');
|
|
currentFilter = button.dataset.filter;
|
|
applyFilter();
|
|
});
|
|
});
|
|
|
|
search.addEventListener('input', applyFilter);
|
|
|
|
groupFilter.addEventListener('change', applyFilter);
|
|
|
|
cards.forEach(card => {
|
|
card.querySelectorAll('input').forEach(input => {
|
|
input.addEventListener('input', applyFilter);
|
|
input.addEventListener('change', applyFilter);
|
|
});
|
|
});
|
|
|
|
enableVisible.addEventListener('click', () => {
|
|
cards.forEach(card => {
|
|
if (card.style.display !== 'none') {
|
|
card.querySelector('.enabled-checkbox').checked = true;
|
|
}
|
|
});
|
|
applyFilter();
|
|
});
|
|
|
|
disableVisible.addEventListener('click', () => {
|
|
cards.forEach(card => {
|
|
if (card.style.display !== 'none') {
|
|
card.querySelector('.enabled-checkbox').checked = false;
|
|
}
|
|
});
|
|
applyFilter();
|
|
});
|
|
|
|
applyFilter();
|
|
})();
|
|
</script>
|