From 3d4f6bb8036982326f38e1ee71588889e50ec3ac Mon Sep 17 00:00:00 2001 From: simonpipe Date: Thu, 18 Dec 2025 17:15:46 +0100 Subject: [PATCH] Update index.js Code angepasst und auf verschiedene Dateien aufgeteilt. --- index.js | 505 ++++++++++++++++++------------------------------------- 1 file changed, 166 insertions(+), 339 deletions(-) diff --git a/index.js b/index.js index b551f8f..63721ad 100644 --- a/index.js +++ b/index.js @@ -1,214 +1,93 @@ -// Public Domain +// Public Domain - Hauptlogik des Radioplayers (Schaltzentrale) document.addEventListener('DOMContentLoaded', function () { + // --- UI ELEMENTE --- const presetContainer = document.getElementById('preset-container'); const stopButton = document.querySelector('.stop-button'); const currentStationName = document.getElementById('current-station-name'); const presetModal = document.getElementById('preset-modal'); const tableBody = document.querySelector('#sortable-tbody'); - const editPresetsButton = document.getElementById('edit-presets-button'); - const openSleepTimerModalButton = document.getElementById('open-sleep-timer-modal'); - const sleepTimerModal = document.getElementById('sleep-timer-modal'); - const closeSleepTimerModalButton = document.getElementById('close-sleep-timer-modal'); - const offSleepTimerModalButton = document.getElementById('off-sleep-timer-modal'); + let currentSound = null; - let presets = []; - let sleepTimerTimeout = null; - let sleepTimerEndTime = null; - // Benutzerdefiniertes Modal für Alerts und Bestätigungen - function showAlert(title, message) { - const modal = document.getElementById("custom-modal"); - const titleElement = document.getElementById("custom-modal-title"); - const messageElement = document.getElementById("custom-modal-message"); - const cancelButton = document.getElementById("custom-modal-cancel"); - - titleElement.textContent = title; - messageElement.textContent = message; - cancelButton.classList.add("hidden"); - - modal.classList.remove("hidden"); - - document.getElementById("custom-modal-ok").onclick = function() { - modal.classList.add("hidden"); - }; - } - - function showConfirm(title, message, callback) { - const modal = document.getElementById("custom-modal"); - const titleElement = document.getElementById("custom-modal-title"); - const messageElement = document.getElementById("custom-modal-message"); - const cancelButton = document.getElementById("custom-modal-cancel"); - - titleElement.textContent = title; - messageElement.textContent = message; - cancelButton.classList.remove("hidden"); - - modal.classList.remove("hidden"); - - document.getElementById("custom-modal-ok").onclick = function() { - modal.classList.add("hidden"); - callback(true); - }; - - cancelButton.onclick = function() { - modal.classList.add("hidden"); - callback(false); - }; - } - - initializeFooter(); - loadPresets(); + // --- INITIALISIERUNG --- + if (window.UI) window.UI.initializeFooter(); initializeEventListeners(); - function initializeFooter() { - const currentYear = new Date().getFullYear(); - document.getElementById('current-year').textContent = currentYear; - document.getElementById('server-name').textContent = window.location.hostname; + // Manager verknüpfen + if (window.SleepTimer) { + window.SleepTimer.init(() => stopPlayback()); } - function updateTitle(stationName) { - document.title = stationName ? `${stationName} - Radioplayer mit Presets` : "Radioplayer mit Presets"; + if (window.PresetManager) { + window.PresetManager.load((loadedPresets) => { + displayPresets(loadedPresets); + initSortable(); + }); } + // --- EVENT LISTENER --- function initializeEventListeners() { - stopButton.addEventListener('click', stopPlayback); - editPresetsButton.addEventListener('click', openModal); - } - - async function loadPresets() { - presets = JSON.parse(localStorage.getItem('presets')) || []; - if (presets.length === 0) { - try { - const response = await fetch('webradio_presets.json'); - if (!response.ok) throw new Error('Datei konnte nicht geladen werden'); - presets = await response.json(); - localStorage.setItem('presets', JSON.stringify(presets)); - } catch (error) { - showAlert("Fehler", 'Fehler beim Laden der Presets: ' + error.message); - } + // Stop Button + if (stopButton) { + stopButton.onclick = stopPlayback; + } + + // Preset Modal öffnen + const editBtn = document.getElementById('edit-presets-button'); + if (editBtn) { + editBtn.onclick = openPresetModal; } - displayPresets(presets); - renderPresets(); - initSortable(); - } - function displayPresets(presets) { - presetContainer.innerHTML = ''; - presets.forEach(preset => addNewPreset(preset.name, preset.url)); - } + // Sleep Timer Modal öffnen + const sleepBtn = document.getElementById('open-sleep-timer-modal'); + if (sleepBtn) { + sleepBtn.onclick = openSleepModal; + } + + // Sleep Timer Modal schließen (X-Button) + const closeSleepBtn = document.getElementById('close-sleep-timer-modal'); + if (closeSleepBtn) { + closeSleepBtn.onclick = () => { + const sm = document.getElementById('sleep-timer-modal'); + sm.classList.add('hidden'); + sm.style.display = 'none'; + }; + } - function addNewPreset(name, url) { - const presetItem = document.createElement('div'); - presetItem.classList.add('preset-item'); - presetItem.dataset.streamUrl = url; - presetItem.innerHTML = `

${name}

`; - presetItem.addEventListener('click', () => playStream(url, presetItem)); - presetContainer.appendChild(presetItem); - } - - function renderPresets() { - tableBody.innerHTML = ''; - presets.forEach((preset, index) => { - const row = document.createElement('tr'); - row.setAttribute('data-index', index); - row.innerHTML = ` - ☰ - - - - `; - tableBody.appendChild(row); + // Zeit-Optionen im Sleep Timer + document.querySelectorAll('.time-option').forEach(btn => { + btn.onclick = () => { + const minutes = parseInt(btn.getAttribute('data-time')) / 60000; + window.SleepTimer.start(minutes); + const sm = document.getElementById('sleep-timer-modal'); + sm.classList.add('hidden'); + sm.style.display = 'none'; + }; }); } - openSleepTimerModalButton.addEventListener('click', function () { - sleepTimerModal.classList.remove('hidden'); - sleepTimerModal.style.display = 'block'; - updateOffButtonVisibility(); - }); - - closeSleepTimerModalButton.addEventListener('click', function () { - sleepTimerModal.classList.add('hidden'); - sleepTimerModal.style.display = 'none'; - }); - - offSleepTimerModalButton.addEventListener('click', function () { - clearSleepTimer(); - sleepTimerModal.classList.add('hidden'); - sleepTimerModal.style.display = 'none'; - }); - - document.querySelectorAll('.time-option').forEach(button => { - button.addEventListener('click', function () { - const timeInMilliseconds = button.getAttribute('data-time'); - startSleepTimer(parseInt(timeInMilliseconds)); - sleepTimerModal.classList.add('hidden'); - sleepTimerModal.style.display = 'none'; - }); - }); - - function startSleepTimer(duration) { - if (sleepTimerTimeout) { - clearTimeout(sleepTimerTimeout); - } - sleepTimerEndTime = Date.now() + duration; - sleepTimerTimeout = setTimeout(() => { - stopPlayback(); - updateSleepTimerButton(); - updateOffButtonVisibility(); - }, duration); - updateSleepTimerButton(); - updateOffButtonVisibility(); - } - - function clearSleepTimer() { - if (sleepTimerTimeout) { - clearTimeout(sleepTimerTimeout); - sleepTimerTimeout = null; - sleepTimerEndTime = null; - openSleepTimerModalButton.textContent = 'Sleep Timer'; - updateOffButtonVisibility(); - } - } - - function updateSleepTimerButton() { - if (sleepTimerEndTime) { - const remainingTime = Math.max(0, Math.ceil((sleepTimerEndTime - Date.now()) / 60000)); - openSleepTimerModalButton.innerHTML = ` ${remainingTime} Minuten`; - if (remainingTime > 0) { - setTimeout(updateSleepTimerButton, 60000); - } else { - openSleepTimerModalButton.textContent = 'Sleep Timer'; - sleepTimerEndTime = null; - updateOffButtonVisibility(); - } - } - } - - function updateOffButtonVisibility() { - if (sleepTimerTimeout) { - offSleepTimerModalButton.style.display = 'inline-block'; - } else { - offSleepTimerModalButton.style.display = 'none'; - } - } - + // --- PLAYER LOGIK --- function playStream(url, item) { stopPlayback(); + if (window.MetaHandler) window.MetaHandler.stop(); + currentSound = new Howl({ src: [url], html5: true, volume: 0.5, onplay: () => { stopButton.classList.remove('hidden'); - updateTitle(item.querySelector('h3').textContent); - currentStationName.textContent = 'Es läuft: ' + item.querySelector('h3').textContent; + const stationName = item.querySelector('h3').textContent; + if (window.UI) window.UI.updateDocumentTitle(stationName); + currentStationName.textContent = 'Es läuft: ' + stationName; + if (window.MetaHandler) window.MetaHandler.start(url); }, onstop: () => { stopButton.classList.add('hidden'); currentStationName.textContent = 'Wähle einen Sender aus'; - updateTitle(''); + if (window.UI) window.UI.updateDocumentTitle(''); + if (window.MetaHandler) window.MetaHandler.stop(); } }); currentSound.play(); @@ -221,185 +100,133 @@ document.addEventListener('DOMContentLoaded', function () { } } - function openModal() { + // --- PRESET UI --- + function displayPresets(presets) { + presetContainer.innerHTML = ''; + presets.forEach(preset => { + const presetItem = document.createElement('div'); + presetItem.classList.add('preset-item'); + presetItem.innerHTML = `

${preset.name}

`; + presetItem.onclick = () => playStream(preset.url, presetItem); + presetContainer.appendChild(presetItem); + }); + } + + function renderPresetsTable() { + if (!tableBody) return; + tableBody.innerHTML = ''; + window.PresetManager.presets.forEach((preset, index) => { + const row = document.createElement('tr'); + row.setAttribute('data-index', index); + row.innerHTML = ` + ☰ + + + + `; + tableBody.appendChild(row); + }); + } + + // --- MODAL WRAPPER --- + function openPresetModal() { + if (!presetModal) return; presetModal.classList.remove('hidden'); presetModal.style.display = 'block'; - renderPresets(); - generateExportCode(); + renderPresetsTable(); + if (window.PresetManager) window.PresetManager.generateExportCode(); } - window.closeModal = function() { - presetModal.classList.add('hidden'); - presetModal.style.display = 'none'; + function openSleepModal() { + const sm = document.getElementById('sleep-timer-modal'); + if (sm) { + sm.classList.remove('hidden'); + sm.style.display = 'block'; + if (window.SleepTimer) window.SleepTimer.updateButtonUI(); + } } - window.addPreset = function() { - presets.push({ name: 'Neuer Sender', url: '' }); - renderPresets(); - generateExportCode(); - } + // --- GLOBALE HELFER (für HTML onclicks & Manager) --- + window.closeModal = () => { + if (presetModal) { + presetModal.classList.add('hidden'); + presetModal.style.display = 'none'; + } + }; + + window.addPreset = () => { + window.PresetManager.presets.push({ name: 'Neuer Sender', url: '' }); + renderPresetsTable(); + }; - window.changeName = function(index, newName) { - presets[index].name = newName; - generateExportCode(); - } - - window.changeURL = function(index, newURL) { - presets[index].url = newURL; - generateExportCode(); - } - - window.deletePreset = function(index) { - showConfirm("Löschen", "Möchtest du diesen Sender wirklich löschen?", function(confirmed) { - if (confirmed) { - presets.splice(index, 1); - renderPresets(); - generateExportCode(); + window.deletePreset = (index) => { + window.UI.showConfirm("Löschen", "Sender wirklich entfernen?", (conf) => { + if (conf) { + window.PresetManager.presets.splice(index, 1); + renderPresetsTable(); + window.PresetManager.generateExportCode(); } }); - } + }; - window.saveAndClose = function() { - savePresets(); - closeModal(); + window.saveAndClose = () => { + window.PresetManager.save(); location.reload(); - } + }; - function savePresets() { - localStorage.setItem('presets', JSON.stringify(presets)); - } + window.changeName = (i, v) => { + window.PresetManager.presets[i].name = v; + window.PresetManager.generateExportCode(); + }; + + window.changeURL = (i, v) => { + window.PresetManager.presets[i].url = v; + window.PresetManager.generateExportCode(); + }; + + window.exportPreset = () => window.PresetManager.exportToFile(); + + window.importPreset = () => { + window.UI.showConfirm("Vorsicht", "Alle Sender werden überschrieben!", (conf) => { + if (conf) { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = 'application/json'; + input.onchange = e => { + const reader = new FileReader(); + reader.onload = ev => { + try { + window.PresetManager.presets = JSON.parse(ev.target.result); + window.PresetManager.save(); + location.reload(); + } catch (err) { window.UI.showAlert("Fehler", "Ungültige Datei."); } + }; + reader.readAsText(e.target.files[0]); + }; + input.click(); + } + }); + }; + + window.importFromString = () => { + const field = document.getElementById('import-string-field'); + const code = field ? field.value.trim() : ""; + if (!code) return window.UI.showAlert("Fehler", "Bitte Code einfügen."); + window.UI.showConfirm("Vorsicht", "Alle Sender werden überschrieben!", (conf) => { + if (conf) window.PresetManager.importFromString(code); + }); + }; function initSortable() { - const sortable = new Sortable(tableBody, { + if (!tableBody) return; + new Sortable(tableBody, { handle: '.drag-handle', animation: 150, - onEnd: function (evt) { + onEnd: () => { const newOrder = Array.from(tableBody.children).map(row => parseInt(row.getAttribute('data-index'))); - presets = newOrder.map(index => presets[index]); - generateExportCode(); + window.PresetManager.presets = newOrder.map(index => window.PresetManager.presets[index]); + window.PresetManager.generateExportCode(); } }); } - - // --- NEUE EXPORT/IMPORT-FUNKTIONEN --- - window.generateExportCode = function() { - try { - const jsonString = JSON.stringify(presets); - const base64String = btoa(unescape(encodeURIComponent(jsonString))); - const exportField = document.getElementById('export-string-field'); - if (exportField) exportField.value = base64String; - } catch (e) { - console.error("Export-Fehler:", e); - showAlert("Fehler", "Export-Fehler: " + e.message); - } - } - - // Export String mit Kompression (Gzip) - window.generateExportCode = async function() { - try { - if (!presets || presets.length === 0) return; - - // 1. Struktur-Diät: Nur Werte speichern [["Name","URL"],["Name","URL"]] - const compactData = presets.map(p => [p.name, p.url]); - const jsonString = JSON.stringify(compactData); - - // 2. Kompression mittels Gzip - const stream = new Blob([jsonString]).stream(); - const compressedStream = stream.pipeThrough(new CompressionStream('gzip')); - const response = new Response(compressedStream); - const buffer = await response.arrayBuffer(); - - // 3. In Base64 umwandeln - const base64String = btoa(String.fromCharCode(...new Uint8Array(buffer))); - - const exportField = document.getElementById('export-string-field'); - if (exportField) exportField.value = base64String; - - console.log("Kompression erfolgreich. Länge:", base64String.length); - } catch (e) { - console.error("Export-Fehler:", e); - } - } - - // Export nach JSON-Datei - window.exportPreset = function() { - const presetsJSON = JSON.stringify(presets, null, 2); - const blob = new Blob([presetsJSON], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = 'radio_presets.json'; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - }; - - // Import String mit Dekomprimierung - window.importFromString = function() { - const field = document.getElementById('import-string-field'); - const code = field.value.trim(); - if (!code) return showAlert("Fehler", "Bitte Code einfügen."); - - showConfirm("Vorsicht", "Es werden alle bestehenden Sender überschrieben. Möchtest du fortfahren?", async function(confirmed) { - if (!confirmed) return; - - try { - // 1. Base64 zurück in Bytes - const binary = atob(code); - const bytes = Uint8Array.from(binary, c => c.charCodeAt(0)); - - // 2. Dekomprimieren (Gzip) - const stream = new Blob([bytes]).stream(); - const decompressedStream = stream.pipeThrough(new DecompressionStream('gzip')); - const response = new Response(decompressedStream); - const resultText = await response.text(); - - // 3. Struktur wiederherstellen (Array zu Objekten) - const importedArray = JSON.parse(resultText); - const restoredPresets = importedArray.map(item => ({ - name: item[0], - url: item[1] - })); - - if (Array.isArray(restoredPresets)) { - presets = restoredPresets; - savePresets(); - location.reload(); - } - } catch (e) { - console.error("Import-Fehler:", e); - showAlert("Fehler", "Code ungültig oder beschädigt!"); - } - }); - }; - - // Import aus JSON-Datei mit Warnung - window.importPreset = function() { - showConfirm("Vorsicht", "Es werden alle bestehenden Sender überschrieben. Möchtest du fortfahren?", function(confirmed) { - if (!confirmed) return; - - const input = document.createElement('input'); - input.type = 'file'; - input.accept = 'application/json'; - input.onchange = function(event) { - const file = event.target.files[0]; - if (file) { - const reader = new FileReader(); - reader.onload = function(e) { - try { - const importedPresets = JSON.parse(e.target.result); - presets = importedPresets; - savePresets(); - location.reload(); - } catch (error) { - showAlert("Fehler", 'Fehler beim Importieren der Presets: Ungültige Datei.'); - } - }; - reader.readAsText(file); - } - }; - input.click(); - }); - }; -}); +}); \ No newline at end of file