244 lines
7.6 KiB
JavaScript
244 lines
7.6 KiB
JavaScript
|
/**
|
||
|
* 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');
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
}
|