diff --git a/resources/css/styles.css b/resources/css/styles.css index 23dd645..a50587a 100644 --- a/resources/css/styles.css +++ b/resources/css/styles.css @@ -165,6 +165,60 @@ body, html { transform: translateY(-2px); } +/* Match button styles with theme */ +.btn-group .btn { + transition: all 0.3s ease; +} + +.btn-group .btn:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); +} + +/* Search input styling */ +.input-group .form-control-sm { + background: rgba(30, 30, 50, 0.9); + border: 1px solid rgba(74, 71, 163, 0.3); + color: #e0e0e0; +} + +.input-group .form-control-sm:focus { + background: rgba(40, 40, 60, 0.9); + border-color: #4a47a3; + box-shadow: 0 0 0 0.25rem rgba(74, 71, 163, 0.25); +} + +/* Search count badge */ +.search-count { + background: rgba(74, 71, 163, 0.2); + color: #e0e0e0; + border: 1px solid rgba(74, 71, 163, 0.3); + font-size: 0.85rem; + padding: 0.2em 0.6em; + border-radius: 0.25rem; + margin-left: 0.5rem; +} + +/* Smooth hide/show transitions */ +.col-12, .draggable-catalog, .draggable-language { + transition: opacity 0.2s ease; +} + +.d-none { + opacity: 0; + pointer-events: none; +} + +/* Hover state for checkboxes */ +.form-check:hover { + background: rgba(74, 71, 163, 0.3); +} + +/* Item highlight during search */ +.search-highlight { + background: rgba(74, 71, 163, 0.4); + border-left: 3px solid #4a47a3; +} /* MDBList Selected Lists Section */ #selected-lists .list-group-item { @@ -1110,7 +1164,7 @@ small.text-muted, } .excluded-file { - background-color: rgba(255,255,255,0.05); + background-color: rgba(255, 255, 255, 0.05); padding: 0.5rem; } @@ -1280,6 +1334,15 @@ small.text-muted, padding: 10px; } + .btn-group-sm > .btn, .btn-sm { + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + } + + .input-group .form-control-sm { + font-size: 0.85rem; + } + h4 { font-size: 1.2rem; } @@ -1296,6 +1359,7 @@ small.text-muted, .fallback-url-container button { font-size: 0.8rem; } + } @media (min-width: 769px) and (max-width: 1200px) { diff --git a/resources/js/config_script.js b/resources/js/config_script.js index 87b8a58..b504482 100644 --- a/resources/js/config_script.js +++ b/resources/js/config_script.js @@ -664,6 +664,153 @@ async function setupKodiAddon(kodiCode) { } } } +/** + * Add select/deselect buttons and search input for a container + * + * @param {string} containerId - ID of the container element + * @param {string} sectionName - Name of the section for labels and placeholders + * @param {string} checkboxSelector - CSS selector for checkboxes in the container + */ +function addSelectDeselectSearch(containerId, sectionName, checkboxSelector) { + const container = document.getElementById(containerId); + if (!container) return; + + // Create a controls container + const controlsContainer = document.createElement('div'); + controlsContainer.className = 'mb-3'; + + // Create search input + const searchGroup = document.createElement('div'); + searchGroup.className = 'input-group mb-2'; + + const searchInput = document.createElement('input'); + searchInput.type = 'text'; + searchInput.className = 'form-control form-control-sm'; + searchInput.placeholder = `Search ${sectionName}...`; + searchInput.id = `${containerId}-search`; + searchInput.setAttribute('autocomplete', 'off'); + + const searchClearBtn = document.createElement('button'); + searchClearBtn.className = 'btn btn-outline-secondary btn-sm'; + searchClearBtn.type = 'button'; + searchClearBtn.innerHTML = ''; + searchClearBtn.setAttribute('data-bs-toggle', 'tooltip'); + searchClearBtn.setAttribute('data-bs-placement', 'top'); + searchClearBtn.setAttribute('title', 'Clear Search'); + + searchGroup.appendChild(searchInput); + searchGroup.appendChild(searchClearBtn); + + // Create button group + const buttonGroup = document.createElement('div'); + buttonGroup.className = 'btn-group btn-group-sm w-100'; + + const selectAllBtn = document.createElement('button'); + selectAllBtn.type = 'button'; + selectAllBtn.className = 'btn btn-outline-primary'; + selectAllBtn.innerHTML = ' Select All'; + + const deselectAllBtn = document.createElement('button'); + deselectAllBtn.type = 'button'; + deselectAllBtn.className = 'btn btn-outline-secondary'; + deselectAllBtn.innerHTML = ' Deselect All'; + + buttonGroup.appendChild(selectAllBtn); + buttonGroup.appendChild(deselectAllBtn); + + // Add elements to controls container + controlsContainer.appendChild(searchGroup); + controlsContainer.appendChild(buttonGroup); + + // Insert controls at the beginning of the container + container.parentNode.insertBefore(controlsContainer, container); + + // Add event listeners + selectAllBtn.addEventListener('click', function() { + const visibleCheckboxes = getVisibleCheckboxes(container, checkboxSelector); + visibleCheckboxes.forEach(checkbox => { + checkbox.checked = true; + }); + }); + + deselectAllBtn.addEventListener('click', function() { + const visibleCheckboxes = getVisibleCheckboxes(container, checkboxSelector); + visibleCheckboxes.forEach(checkbox => { + checkbox.checked = false; + }); + }); + + searchInput.addEventListener('input', function() { + filterItems(container, this.value.toLowerCase()); + }); + + searchClearBtn.addEventListener('click', function() { + searchInput.value = ''; + filterItems(container, ''); + }); +} + +/** + * Get all visible checkboxes in a container + * + * @param {HTMLElement} container - Container element + * @param {string} checkboxSelector - CSS selector for checkboxes + * @returns {HTMLElement[]} Array of visible checkbox elements + */ +function getVisibleCheckboxes(container, checkboxSelector) { + const allCheckboxes = container.querySelectorAll(checkboxSelector); + return Array.from(allCheckboxes).filter(checkbox => { + const item = getItemContainer(checkbox); + return item && !item.classList.contains('d-none'); + }); +} + +/** + * Get the container element of a checkbox + * + * @param {HTMLElement} checkbox - Checkbox element + * @returns {HTMLElement|null} Container element or null + */ +function getItemContainer(checkbox) { + // Navigate up to find the draggable container or column + let parent = checkbox.parentElement; + while (parent && !parent.classList.contains('draggable-catalog') && + !parent.classList.contains('draggable-language') && + !parent.classList.contains('col-12') && + !parent.classList.contains('col-md-6') && + !parent.classList.contains('col-lg-4')) { + parent = parent.parentElement; + } + return parent; +} + +/** + * Filter items in a container based on search text + * + * @param {HTMLElement} container - Container element + * @param {string} searchText - Text to search for + */ +function filterItems(container, searchText) { + // Find all items (columns or containers) + const items = container.querySelectorAll('.draggable-catalog, .draggable-language, .col-12.col-md-6.col-lg-4'); + + if (searchText === '') { + // Show all items if search is empty + items.forEach(item => { + item.classList.remove('d-none'); + }); + } else { + // Show/hide items based on label text + items.forEach(item => { + const label = item.querySelector('label'); + if (label && label.textContent.toLowerCase().includes(searchText)) { + item.classList.remove('d-none'); + } else { + item.classList.add('d-none'); + } + }); + } +} // ---- Event Listeners ---- @@ -910,6 +1057,12 @@ document.addEventListener('DOMContentLoaded', function () { document.getElementById('languageSortSection').style.display = 'block'; } + // Add UI elements for catalogs + addSelectDeselectSearch('catalogs', 'Catalogs', 'input[name="selected_catalogs"]'); + + // Add UI elements for languages + addSelectDeselectSearch('languageSortOrder', 'Languages', 'input[name="selected_languages"]'); + // Initialize the parental guide checkboxes const parentalGuideCheckboxes = document.querySelectorAll('.parental-guide-checkbox');