diff --git a/locales/en.json b/locales/en.json index 2605fc5..2dc5931 100644 --- a/locales/en.json +++ b/locales/en.json @@ -43,6 +43,8 @@ "personal_epg_label": "EPG URL (XMLTV) *", "personal_epg_m3u_label": "M3U URL", "personal_epg_optional": "optional", + "personal_epg_m3u_enabled": "This instance allows a personal M3U playlist.", + "personal_epg_m3u_disabled": "The administrator of this instance has disabled personal M3U playlists.", "cancel": "Cancel", "apply": "Apply", "setup_already_done": "Configuration already done. Use your admin key to access setup again.", @@ -52,4 +54,4 @@ "fav_remove": "Remove from favorites", "update_available": "↑ Update available:", "update_title": "Click to download the latest version" -} \ No newline at end of file +} diff --git a/locales/es.json b/locales/es.json index e4331cb..3f84350 100644 --- a/locales/es.json +++ b/locales/es.json @@ -43,6 +43,8 @@ "personal_epg_label": "URL EPG (XMLTV) *", "personal_epg_m3u_label": "URL M3U", "personal_epg_optional": "opcional", + "personal_epg_m3u_enabled": "Esta instancia permite una lista M3U personal.", + "personal_epg_m3u_disabled": "El administrador de esta instancia ha desactivado las listas M3U personales.", "cancel": "Cancelar", "apply": "Aplicar", "setup_already_done": "Configuración ya realizada. Usa tu clave de administrador para volver al setup.", @@ -52,4 +54,4 @@ "fav_remove": "Quitar de favoritos", "update_available": "↑ Actualizacion disponible:", "update_title": "Haz clic para descargar la ultima version" -} \ No newline at end of file +} diff --git a/locales/fr.json b/locales/fr.json index 46cb8fe..0094539 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -43,6 +43,8 @@ "personal_epg_label": "URL EPG (XMLTV) *", "personal_epg_m3u_label": "URL M3U", "personal_epg_optional": "optionnel", + "personal_epg_m3u_enabled": "Cette instance autorise l'ajout d'une playlist M3U personnelle.", + "personal_epg_m3u_disabled": "L'administrateur de cette instance a désactivé l'ajout d'une playlist M3U personnelle.", "cancel": "Annuler", "apply": "Appliquer", "setup_already_done": "Configuration déjà effectuée. Utilisez votre clé admin pour accéder à nouveau au setup.", @@ -52,4 +54,4 @@ "fav_remove": "Retirer des favoris", "update_available": "↑ Mise a jour disponible :", "update_title": "Cliquez pour telecharger la derniere version" -} \ No newline at end of file +} diff --git a/setup.php b/setup.php index ec21324..bb88005 100644 --- a/setup.php +++ b/setup.php @@ -69,6 +69,7 @@ if ($mode === 'setup' && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST[' } else { $group_name = trim($_POST['group_name'] ?? ''); $allow_personal_epg = isset($_POST['allow_personal_epg']); + $allow_personal_m3u = $allow_personal_epg && isset($_POST['allow_personal_m3u']); $names = $_POST['source_name'] ?? []; $epg_urls = $_POST['source_epg_url'] ?? []; @@ -103,6 +104,7 @@ if ($mode === 'setup' && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST[' 'group_name' => $group_name, 'epg_sources' => $epg_sources, 'allow_personal_epg' => $allow_personal_epg, + 'allow_personal_m3u' => $allow_personal_m3u, 'admin_key' => $new_key, ]; $written = file_put_contents($config_path, json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); @@ -119,6 +121,9 @@ if ($mode === 'setup' && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST[' $prefill_group = $_POST['group_name'] ?? $existing['group_name'] ?? ''; $prefill_sources = $existing['epg_sources'] ?? []; $prefill_epg = !empty($existing['allow_personal_epg']); +$prefill_personal_m3u = array_key_exists('allow_personal_m3u', $existing) + ? !empty($existing['allow_personal_m3u']) + : $prefill_epg; $submitted_key = trim($_POST['admin_key_input'] ?? $_POST['admin_key_hidden'] ?? ''); ?> @@ -289,6 +294,17 @@ $submitted_key = trim($_POST['admin_key_input'] ?? $_POST['admin_key_hidden'] +
+
+ Allow personal M3U + Lets visitors add their own stream playlist in the Personal EPG modal. +
+ +
+ @@ -324,6 +340,15 @@ function addSource() { function removeSource(btn) { btn.closest('.source-block').remove(); renumberSources(); updateRemoveButtons(); } function renumberSources() { document.querySelectorAll('.source-block').forEach((b,i) => b.querySelector('.source-num').textContent = `SOURCE ${i+1}`); } function updateRemoveButtons() { const b = document.querySelectorAll('.source-block'); b.forEach(bl => bl.querySelector('.source-remove').style.display = b.length > 1 ? '' : 'none'); } +const allowPersonalEpgToggle = document.getElementById('allowPersonalEpg'); +const allowPersonalM3uToggle = document.getElementById('allowPersonalM3u'); +function syncPersonalM3uToggle() { + if (!allowPersonalEpgToggle || !allowPersonalM3uToggle) return; + allowPersonalM3uToggle.disabled = !allowPersonalEpgToggle.checked; + if (!allowPersonalEpgToggle.checked) allowPersonalM3uToggle.checked = false; +} +allowPersonalEpgToggle?.addEventListener('change', syncPersonalM3uToggle); +syncPersonalM3uToggle(); diff --git a/src/config.php b/src/config.php index e0c67f8..379fcaf 100644 --- a/src/config.php +++ b/src/config.php @@ -29,6 +29,9 @@ if (isset($config['epg_url']) && !isset($config['epg_sources'])) { $group_name = htmlspecialchars($config['group_name'] ?? 'GridTV'); $epg_sources = $config['epg_sources'] ?? []; $allow_personal_epg = !empty($config['allow_personal_epg']); +$allow_personal_m3u = array_key_exists('allow_personal_m3u', $config) + ? !empty($config['allow_personal_m3u']) + : $allow_personal_epg; // Default to the first configured source to keep the rest of the app compatible. $first_source = $epg_sources[0] ?? []; diff --git a/src/css/modals.css b/src/css/modals.css index ae82e04..f67416f 100644 --- a/src/css/modals.css +++ b/src/css/modals.css @@ -25,6 +25,13 @@ } .modal-field input:focus { border-color: var(--accent); } .modal-field input::placeholder { color: var(--text-dim); opacity: 0.4; } + .modal-field-note { + margin-top: 6px; font-size: 11px; line-height: 1.45; opacity: 0.82; + color: var(--text-dim); + } + .modal-field-note.is-disabled { + color: #ffb0a5; opacity: 1; + } .modal-actions { display: flex; gap: 10px; margin-top: 20px; justify-content: flex-end; } .modal-btn-cancel { background: none; border: 1px solid var(--border-bright); color: var(--text-dim); diff --git a/src/js/config.js b/src/js/config.js index 8d8188d..2c3fe6d 100644 --- a/src/js/config.js +++ b/src/js/config.js @@ -1,6 +1,7 @@ const EPG_SOURCES = ; const L = ; const ALLOW_PERSONAL_EPG = ; +const ALLOW_PERSONAL_M3U = ; // Index of the currently selected source. let activeSourceIndex = 0; diff --git a/src/js/live.js b/src/js/live.js index 39e2e7d..0bc2d8b 100644 --- a/src/js/live.js +++ b/src/js/live.js @@ -9,7 +9,7 @@ window.addEventListener('load', () => { const savedSource = localStorage.getItem('gridtv-active-source'); if (savedSource === 'personal') { const epg = localStorage.getItem('gridtv-personal-epg'); - const m3u = localStorage.getItem('gridtv-personal-m3u') ?? ''; + const m3u = ALLOW_PERSONAL_M3U ? (localStorage.getItem('gridtv-personal-m3u') ?? '') : ''; if (epg) { currentEpgUrl = epg; currentM3uUrl = m3u; diff --git a/src/js/sources.js b/src/js/sources.js index 08904ee..0beda05 100644 --- a/src/js/sources.js +++ b/src/js/sources.js @@ -28,8 +28,20 @@ function openPersonalEpgModal() { const saved_epg = localStorage.getItem('gridtv-personal-epg') ?? ''; const saved_m3u = localStorage.getItem('gridtv-personal-m3u') ?? ''; const modal = document.getElementById('personalEpgModal'); + const m3uInput = document.getElementById('personalM3uInput'); + const m3uHint = document.getElementById('personalM3uHint'); document.getElementById('personalEpgInput').value = saved_epg; - document.getElementById('personalM3uInput').value = saved_m3u; + if (m3uInput) { + m3uInput.value = ALLOW_PERSONAL_M3U ? saved_m3u : ''; + m3uInput.disabled = !ALLOW_PERSONAL_M3U; + } + if (m3uHint) { + m3uHint.textContent = ALLOW_PERSONAL_M3U + ? (m3uHint.dataset.enabledText ?? '') + : (m3uHint.dataset.disabledText ?? ''); + m3uHint.classList.toggle('is-disabled', !ALLOW_PERSONAL_M3U); + m3uHint.style.display = ''; + } modal.classList.add('visible'); } @@ -44,10 +56,14 @@ function closePersonalEpgModal(applied) { function applyPersonalEpg() { const epg = document.getElementById('personalEpgInput').value.trim(); - const m3u = document.getElementById('personalM3uInput').value.trim(); + const m3u = ALLOW_PERSONAL_M3U ? document.getElementById('personalM3uInput').value.trim() : ''; if (!epg) { alert('URL EPG obligatoire'); return; } localStorage.setItem('gridtv-personal-epg', epg); - localStorage.setItem('gridtv-personal-m3u', m3u); + if (ALLOW_PERSONAL_M3U) { + localStorage.setItem('gridtv-personal-m3u', m3u); + } else { + localStorage.removeItem('gridtv-personal-m3u'); + } localStorage.setItem('gridtv-active-source', 'personal'); updateSourceDropdownLabel('✏ Personal EPG'); currentEpgUrl = epg; diff --git a/src/tpl/modals.php b/src/tpl/modals.php index 85b5f95..a29e186 100644 --- a/src/tpl/modals.php +++ b/src/tpl/modals.php @@ -29,6 +29,7 @@