/** * Services search module for homelab services * Extends the base search functionality with service-specific features */ import { initializeBaseSearch } from './baseSearch.js'; /** * Initialize search functionality for homelab services * @returns {Object} Alpine.js data object with search functionality */ export function initializeServicesSearch() { // Create base search with service-specific configuration const baseSearch = initializeBaseSearch('.app-card', { nameAttribute: 'data-app-name', tagsAttribute: 'data-app-tags', categoryAttribute: 'data-app-category', noResultsMessage: 'No services found', allItemsMessage: 'Showing all services', resultCountMessage: (count) => `Found ${count} services`, itemLabel: 'services' }); // Extend with service-specific functionality return { ...baseSearch, // View mode properties iconSizeValue: 2, // Slider value: 1=small, 2=medium, 3=large iconSize: 'medium', // small, medium, large viewMode: 'grid', // grid or list displayMode: 'both', // both, image, or name debounceTimeout: null, // For debouncing slider changes init() { // Call the base init method baseSearch.init.call(this); // Apply initial icon size, view mode, and display mode this.applyIconSize(); this.applyViewMode(); this.applyDisplayMode(); // Save preferences to localStorage if available this.loadPreferences(); // Listen for window resize events to optimize layout this.setupResizeListener(); }, // Load user preferences from localStorage loadPreferences() { if (typeof localStorage !== 'undefined') { try { // Load icon size const savedIconSize = localStorage.getItem('services-icon-size'); if (savedIconSize) { this.setIconSize(parseFloat(savedIconSize)); } // Load view mode const savedViewMode = localStorage.getItem('services-view-mode'); if (savedViewMode) { this.setViewMode(savedViewMode); } // Load display mode const savedDisplayMode = localStorage.getItem('services-display-mode'); if (savedDisplayMode) { this.setDisplayMode(savedDisplayMode); } } catch (e) { console.error('Error loading preferences:', e); } } }, // Save user preferences to localStorage savePreferences() { if (typeof localStorage !== 'undefined') { try { localStorage.setItem('services-icon-size', this.iconSizeValue.toString()); localStorage.setItem('services-view-mode', this.viewMode); localStorage.setItem('services-display-mode', this.displayMode); } catch (e) { console.error('Error saving preferences:', e); } } }, // Setup listener for window resize events setupResizeListener() { const handleResize = () => { // Switch to list view on small screens if not explicitly set by user const userHasSetViewMode = localStorage.getItem('services-view-mode') !== null; if (!userHasSetViewMode) { const smallScreen = window.innerWidth < 640; // sm breakpoint if (smallScreen && this.viewMode !== 'list') { this.setViewMode('list', false); // Don't save to preferences } else if (!smallScreen && this.viewMode !== 'grid') { this.setViewMode('grid', false); // Don't save to preferences } } }; // Initial check handleResize(); // Add resize listener with debounce let resizeTimeout; window.addEventListener('resize', () => { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(handleResize, 250); }); }, // Icon size methods setIconSize(size, savePreference = true) { if (typeof size === 'string') { // Handle legacy string values (small, medium, large) this.iconSize = size; this.iconSizeValue = size === 'small' ? 1 : size === 'medium' ? 2 : 3; } else { // Handle slider numeric values this.iconSizeValue = parseFloat(size); // Map slider value to size name if (this.iconSizeValue <= 1.33) { this.iconSize = 'small'; } else if (this.iconSizeValue <= 2.33) { this.iconSize = 'medium'; } else { this.iconSize = 'large'; } } this.applyIconSize(); // Save preference if requested if (savePreference) { this.savePreferences(); } }, // Handle slider input with debounce handleSliderChange(event) { const value = event.target.value; // Clear any existing timeout if (this.debounceTimeout) { clearTimeout(this.debounceTimeout); } // Set a new timeout this.debounceTimeout = setTimeout(() => { this.setIconSize(value); }, 50); // 50ms debounce }, applyIconSize() { const appList = document.getElementById('app-list'); if (!appList) return; // Remove existing size classes appList.classList.remove('icon-size-small', 'icon-size-medium', 'icon-size-large'); // Add the new size class appList.classList.add(`icon-size-${this.iconSize}`); // Apply custom CSS variable for fine-grained control appList.style.setProperty('--icon-scale', this.iconSizeValue); }, // View mode methods toggleViewMode() { this.viewMode = this.viewMode === 'grid' ? 'list' : 'grid'; this.applyViewMode(); this.savePreferences(); }, setViewMode(mode, savePreference = true) { this.viewMode = mode; this.applyViewMode(); // Save preference if requested if (savePreference) { this.savePreferences(); } }, applyViewMode() { const appList = document.getElementById('app-list'); if (!appList) return; // Remove existing view mode classes appList.classList.remove('view-mode-grid', 'view-mode-list'); // Add the new view mode class appList.classList.add(`view-mode-${this.viewMode}`); // Update all category sections document.querySelectorAll('.category-section').forEach(section => { const gridContainer = section.querySelector('.grid'); if (gridContainer) { // Update grid classes based on view mode if (this.viewMode === 'grid') { gridContainer.classList.remove('grid-cols-1'); gridContainer.classList.add('grid-cols-2', 'sm:grid-cols-3', 'lg:grid-cols-4'); } else { gridContainer.classList.remove('grid-cols-2', 'sm:grid-cols-3', 'lg:grid-cols-4'); gridContainer.classList.add('grid-cols-1'); } } }); }, // Display mode methods setDisplayMode(mode) { this.displayMode = mode; this.applyDisplayMode(); this.savePreferences(); }, applyDisplayMode() { const appList = document.getElementById('app-list'); if (!appList) return; // Remove existing display mode classes appList.classList.remove('display-both', 'display-image-only', 'display-name-only'); // Add the new display mode class if (this.displayMode === 'image') { appList.classList.add('display-image-only'); } else if (this.displayMode === 'name') { appList.classList.add('display-name-only'); } else { appList.classList.add('display-both'); } } }; }