2025-05-03 01:35:48 -07:00
|
|
|
---
|
|
|
|
/**
|
2025-05-03 18:22:25 -07:00
|
|
|
* SearchScript component
|
|
|
|
* Imports the modular search functionality
|
2025-05-03 01:35:48 -07:00
|
|
|
*/
|
2025-05-03 18:22:25 -07:00
|
|
|
---
|
2025-05-03 01:35:48 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
<!-- Register Alpine.js components -->
|
|
|
|
<script is:inline>
|
|
|
|
document.addEventListener('alpine:init', () => {
|
|
|
|
if (typeof Alpine !== 'undefined') {
|
|
|
|
// Register the styleControls component
|
|
|
|
Alpine.data('styleControls', () => ({
|
|
|
|
iconSize: 'medium',
|
2025-05-03 19:05:21 -07:00
|
|
|
iconSizeValue: 2, // Slider value: 1=small, 2=medium, 3=large
|
2025-05-03 18:22:25 -07:00
|
|
|
viewMode: 'grid',
|
|
|
|
displayMode: 'both',
|
2025-05-03 01:35:48 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
init() {
|
|
|
|
// Load preferences from localStorage
|
|
|
|
this.loadPreferences();
|
2025-05-03 19:05:21 -07:00
|
|
|
|
|
|
|
// Set initial iconSizeValue based on iconSize
|
|
|
|
this.updateSliderFromIconSize();
|
|
|
|
|
|
|
|
// Listen for events from searchServices component
|
|
|
|
window.addEventListener('styleControls:updateIconSize', (e) => {
|
|
|
|
if (e.detail && e.detail.size) {
|
|
|
|
this.iconSize = e.detail.size;
|
|
|
|
this.updateSliderFromIconSize();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
window.addEventListener('styleControls:updateViewMode', (e) => {
|
|
|
|
if (e.detail && e.detail.mode) {
|
|
|
|
this.viewMode = e.detail.mode;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
window.addEventListener('styleControls:updateDisplayMode', (e) => {
|
|
|
|
if (e.detail && e.detail.mode) {
|
|
|
|
this.displayMode = e.detail.mode;
|
|
|
|
}
|
|
|
|
});
|
2025-05-03 18:22:25 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
loadPreferences() {
|
|
|
|
if (typeof localStorage !== 'undefined') {
|
|
|
|
try {
|
|
|
|
// Load icon size
|
|
|
|
const savedIconSize = localStorage.getItem('services-icon-size');
|
|
|
|
if (savedIconSize) {
|
2025-05-03 19:05:21 -07:00
|
|
|
this.setIconSize(savedIconSize, false); // Don't update slider yet
|
2025-05-03 18:22:25 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Load view mode
|
|
|
|
const savedViewMode = localStorage.getItem('services-view-mode');
|
|
|
|
if (savedViewMode) {
|
|
|
|
this.viewMode = savedViewMode;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load display mode
|
|
|
|
const savedDisplayMode = localStorage.getItem('services-display-mode');
|
|
|
|
if (savedDisplayMode) {
|
|
|
|
this.displayMode = savedDisplayMode;
|
|
|
|
}
|
2025-05-03 19:05:21 -07:00
|
|
|
|
|
|
|
// Update slider value based on loaded icon size
|
|
|
|
this.updateSliderFromIconSize();
|
2025-05-03 18:22:25 -07:00
|
|
|
} catch (e) {
|
|
|
|
console.error('Error loading preferences:', e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
savePreferences() {
|
|
|
|
if (typeof localStorage !== 'undefined') {
|
|
|
|
try {
|
|
|
|
localStorage.setItem('services-icon-size', this.iconSize);
|
|
|
|
localStorage.setItem('services-view-mode', this.viewMode);
|
|
|
|
localStorage.setItem('services-display-mode', this.displayMode);
|
|
|
|
} catch (e) {
|
|
|
|
console.error('Error saving preferences:', e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2025-05-03 19:05:21 -07:00
|
|
|
// Convert between iconSize string and iconSizeValue number
|
|
|
|
updateSliderFromIconSize() {
|
|
|
|
if (this.iconSize === 'small') {
|
|
|
|
this.iconSizeValue = 1;
|
|
|
|
} else if (this.iconSize === 'medium') {
|
|
|
|
this.iconSizeValue = 2;
|
|
|
|
} else if (this.iconSize === 'large') {
|
|
|
|
this.iconSizeValue = 3;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
// Update iconSize based on slider value
|
|
|
|
updateIconSizeFromSlider() {
|
|
|
|
console.log('Slider value changed:', this.iconSizeValue);
|
|
|
|
const value = parseInt(this.iconSizeValue);
|
|
|
|
let size;
|
|
|
|
|
|
|
|
if (value === 1) {
|
|
|
|
size = 'small';
|
|
|
|
} else if (value === 2) {
|
|
|
|
size = 'medium';
|
|
|
|
} else if (value === 3) {
|
|
|
|
size = 'large';
|
|
|
|
} else {
|
|
|
|
size = 'medium'; // Default fallback
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log('Setting icon size to:', size);
|
|
|
|
this.setIconSize(size);
|
|
|
|
|
|
|
|
// Apply the size directly to app-list if it exists
|
|
|
|
this.applyIconSizeDirectly(size);
|
|
|
|
},
|
|
|
|
|
|
|
|
// Apply icon size directly to elements
|
|
|
|
applyIconSizeDirectly(size) {
|
|
|
|
// Try to find app-list element
|
|
|
|
const appList = document.getElementById('app-list');
|
|
|
|
if (appList) {
|
|
|
|
console.log('Found app-list, applying size:', size);
|
|
|
|
// 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-${size}`);
|
|
|
|
} else {
|
|
|
|
console.log('app-list element not found');
|
|
|
|
|
|
|
|
// Try to apply to all service cards directly
|
|
|
|
const cards = document.querySelectorAll('.app-card, .service-card');
|
|
|
|
if (cards.length > 0) {
|
|
|
|
console.log('Found', cards.length, 'cards, applying size:', size);
|
|
|
|
cards.forEach(card => {
|
|
|
|
// Remove existing size classes
|
|
|
|
card.classList.remove('icon-size-small', 'icon-size-medium', 'icon-size-large');
|
|
|
|
// Add the new size class
|
|
|
|
card.classList.add(`icon-size-${size}`);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
console.log('No service cards found');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Dispatch event to notify searchServices component
|
|
|
|
window.dispatchEvent(new CustomEvent('searchServices:setIconSize', {
|
|
|
|
detail: { size }
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
|
|
|
|
setIconSize(size, updateSlider = true) {
|
|
|
|
console.log('setIconSize called with:', size);
|
2025-05-03 18:22:25 -07:00
|
|
|
this.iconSize = size;
|
2025-05-03 19:05:21 -07:00
|
|
|
|
|
|
|
// Update slider value if requested
|
|
|
|
if (updateSlider) {
|
|
|
|
this.updateSliderFromIconSize();
|
|
|
|
}
|
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
this.savePreferences();
|
2025-05-03 19:05:21 -07:00
|
|
|
|
|
|
|
// Apply the size directly
|
|
|
|
this.applyIconSizeDirectly(size);
|
2025-05-03 18:22:25 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
toggleViewMode() {
|
|
|
|
this.viewMode = this.viewMode === 'grid' ? 'list' : 'grid';
|
|
|
|
this.savePreferences();
|
2025-05-03 19:05:21 -07:00
|
|
|
|
|
|
|
// Apply view mode directly
|
|
|
|
this.applyViewModeDirectly(this.viewMode);
|
|
|
|
|
|
|
|
// Dispatch event to notify searchServices component
|
|
|
|
window.dispatchEvent(new CustomEvent('searchServices:setViewMode', {
|
|
|
|
detail: { mode: this.viewMode }
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
|
|
|
|
applyViewModeDirectly(mode) {
|
|
|
|
console.log('Applying view mode directly:', mode);
|
|
|
|
const appList = document.getElementById('app-list');
|
|
|
|
if (appList) {
|
|
|
|
// 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-${mode}`);
|
|
|
|
|
|
|
|
// 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 (mode === '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');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2025-05-03 18:22:25 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
setDisplayMode(mode) {
|
|
|
|
this.displayMode = mode;
|
|
|
|
this.savePreferences();
|
2025-05-03 19:05:21 -07:00
|
|
|
|
|
|
|
// Apply display mode directly
|
|
|
|
this.applyDisplayModeDirectly(mode);
|
|
|
|
|
|
|
|
// Dispatch event to notify searchServices component
|
|
|
|
window.dispatchEvent(new CustomEvent('searchServices:setDisplayMode', {
|
|
|
|
detail: { mode }
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
|
|
|
|
applyDisplayModeDirectly(mode) {
|
|
|
|
console.log('Applying display mode directly:', mode);
|
|
|
|
const appList = document.getElementById('app-list');
|
|
|
|
if (appList) {
|
|
|
|
// Remove existing display mode classes
|
|
|
|
appList.classList.remove('display-both', 'display-image-only', 'display-name-only');
|
|
|
|
|
|
|
|
// Add the new display mode class
|
|
|
|
if (mode === 'image') {
|
|
|
|
appList.classList.add('display-image-only');
|
|
|
|
} else if (mode === 'name') {
|
|
|
|
appList.classList.add('display-name-only');
|
|
|
|
} else {
|
|
|
|
appList.classList.add('display-both');
|
|
|
|
}
|
|
|
|
}
|
2025-05-03 01:35:48 -07:00
|
|
|
}
|
2025-05-03 18:22:25 -07:00
|
|
|
}));
|
|
|
|
|
|
|
|
// Register searchServices component
|
|
|
|
Alpine.data('searchServices', () => ({
|
|
|
|
searchQuery: '',
|
|
|
|
hasResults: true,
|
|
|
|
visibleCount: 0,
|
|
|
|
loading: false,
|
|
|
|
focusedItemIndex: -1,
|
|
|
|
debounceTimeout: null,
|
|
|
|
|
|
|
|
// 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
|
2025-05-03 01:35:48 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
init() {
|
|
|
|
// Initialize the visible count
|
|
|
|
this.visibleCount = document.querySelectorAll('.app-card').length;
|
|
|
|
this.setupWatchers();
|
|
|
|
this.setupKeyboardShortcuts();
|
2025-05-03 01:35:48 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
// Apply initial icon size, view mode, and display mode
|
|
|
|
this.applyIconSize();
|
|
|
|
this.applyViewMode();
|
|
|
|
this.applyDisplayMode();
|
2025-05-03 01:35:48 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
// Save preferences to localStorage if available
|
|
|
|
this.loadPreferences();
|
2025-05-03 01:35:48 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
// Listen for window resize events to optimize layout
|
|
|
|
this.setupResizeListener();
|
2025-05-03 01:35:48 -07:00
|
|
|
|
2025-05-03 19:05:21 -07:00
|
|
|
// Listen for events from styleControls component
|
|
|
|
this.setupStyleControlsListeners();
|
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
// Set loading to false after initialization
|
|
|
|
setTimeout(() => {
|
|
|
|
this.loading = false;
|
|
|
|
}, 300);
|
|
|
|
},
|
|
|
|
|
2025-05-03 19:05:21 -07:00
|
|
|
// Setup listeners for styleControls events
|
|
|
|
setupStyleControlsListeners() {
|
|
|
|
// Listen for icon size changes
|
|
|
|
window.addEventListener('searchServices:setIconSize', (e) => {
|
|
|
|
if (e.detail && e.detail.size) {
|
|
|
|
console.log('searchServices received icon size event:', e.detail.size);
|
|
|
|
this.setIconSize(e.detail.size);
|
|
|
|
|
|
|
|
// Notify styleControls component of the change
|
|
|
|
window.dispatchEvent(new CustomEvent('styleControls:updateIconSize', {
|
|
|
|
detail: { size: e.detail.size }
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Listen for view mode changes
|
|
|
|
window.addEventListener('searchServices:setViewMode', (e) => {
|
|
|
|
if (e.detail && e.detail.mode) {
|
|
|
|
console.log('searchServices received view mode event:', e.detail.mode);
|
|
|
|
this.setViewMode(e.detail.mode);
|
|
|
|
|
|
|
|
// Notify styleControls component of the change
|
|
|
|
window.dispatchEvent(new CustomEvent('styleControls:updateViewMode', {
|
|
|
|
detail: { mode: e.detail.mode }
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Listen for display mode changes
|
|
|
|
window.addEventListener('searchServices:setDisplayMode', (e) => {
|
|
|
|
if (e.detail && e.detail.mode) {
|
|
|
|
console.log('searchServices received display mode event:', e.detail.mode);
|
|
|
|
this.setDisplayMode(e.detail.mode);
|
|
|
|
|
|
|
|
// Notify styleControls component of the change
|
|
|
|
window.dispatchEvent(new CustomEvent('styleControls:updateDisplayMode', {
|
|
|
|
detail: { mode: e.detail.mode }
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
setupWatchers() {
|
|
|
|
this.$watch('searchQuery', (query) => {
|
|
|
|
// Debounce search for better performance
|
|
|
|
if (this.debounceTimeout) {
|
|
|
|
clearTimeout(this.debounceTimeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.debounceTimeout = setTimeout(() => {
|
|
|
|
this.filterContent(query);
|
|
|
|
}, 150);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
setupKeyboardShortcuts() {
|
|
|
|
document.addEventListener('keydown', (e) => {
|
|
|
|
// '/' key focuses the search input
|
|
|
|
if (e.key === '/' && document.activeElement.id !== 'app-search') {
|
|
|
|
e.preventDefault();
|
|
|
|
document.getElementById('app-search').focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Escape key clears the search
|
|
|
|
if (e.key === 'Escape' && this.searchQuery !== '') {
|
|
|
|
this.searchQuery = '';
|
|
|
|
document.getElementById('app-search').focus();
|
|
|
|
this.focusedItemIndex = -1;
|
|
|
|
this.clearItemFocus();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
// 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(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);
|
|
|
|
}
|
2025-05-03 01:35:48 -07:00
|
|
|
}
|
2025-05-03 18:22:25 -07:00
|
|
|
},
|
2025-05-03 01:35:48 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
// Save user preferences to localStorage
|
|
|
|
savePreferences() {
|
|
|
|
if (typeof localStorage !== 'undefined') {
|
|
|
|
try {
|
|
|
|
localStorage.setItem('services-icon-size', this.iconSize);
|
|
|
|
localStorage.setItem('services-view-mode', this.viewMode);
|
|
|
|
localStorage.setItem('services-display-mode', this.displayMode);
|
|
|
|
} catch (e) {
|
|
|
|
console.error('Error saving preferences:', e);
|
|
|
|
}
|
2025-05-03 01:35:48 -07:00
|
|
|
}
|
2025-05-03 18:22:25 -07:00
|
|
|
},
|
2025-05-03 01:35:48 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
// 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);
|
|
|
|
});
|
|
|
|
},
|
2025-05-03 01:35:48 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
// Icon size methods
|
|
|
|
setIconSize(size) {
|
|
|
|
this.iconSize = size;
|
|
|
|
this.applyIconSize();
|
|
|
|
this.savePreferences();
|
|
|
|
},
|
2025-05-03 01:35:48 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
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}`);
|
|
|
|
},
|
2025-05-03 01:35:48 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
// View mode methods
|
|
|
|
toggleViewMode() {
|
|
|
|
this.viewMode = this.viewMode === 'grid' ? 'list' : 'grid';
|
|
|
|
this.applyViewMode();
|
|
|
|
this.savePreferences();
|
|
|
|
},
|
2025-05-03 01:35:48 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
setViewMode(mode, savePreference = true) {
|
|
|
|
this.viewMode = mode;
|
|
|
|
this.applyViewMode();
|
2025-05-03 01:35:48 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
// Save preference if requested
|
|
|
|
if (savePreference) {
|
|
|
|
this.savePreferences();
|
2025-05-03 01:35:48 -07:00
|
|
|
}
|
2025-05-03 18:22:25 -07:00
|
|
|
},
|
2025-05-03 13:19:10 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
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');
|
2025-05-03 13:19:10 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
// 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');
|
2025-05-03 13:19:10 -07:00
|
|
|
} else {
|
2025-05-03 18:22:25 -07:00
|
|
|
appList.classList.add('display-both');
|
2025-05-03 13:19:10 -07:00
|
|
|
}
|
2025-05-03 18:22:25 -07:00
|
|
|
},
|
2025-05-03 13:19:10 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
filterContent(query) {
|
|
|
|
query = query.toLowerCase().trim();
|
|
|
|
let anyResults = false;
|
|
|
|
let visibleCount = 0;
|
|
|
|
|
|
|
|
// Process all content items
|
|
|
|
document.querySelectorAll('.app-card').forEach((item) => {
|
|
|
|
const name = (item.getAttribute('data-app-name') || '').toLowerCase();
|
|
|
|
const tags = (item.getAttribute('data-app-tags') || '').toLowerCase();
|
|
|
|
const category = (item.getAttribute('data-app-category') || '').toLowerCase();
|
|
|
|
|
|
|
|
const isMatch = query === '' ||
|
|
|
|
name.includes(query) ||
|
|
|
|
tags.includes(query) ||
|
|
|
|
category.includes(query);
|
|
|
|
|
|
|
|
if (isMatch) {
|
|
|
|
item.style.display = '';
|
|
|
|
anyResults = true;
|
|
|
|
visibleCount++;
|
|
|
|
} else {
|
|
|
|
item.style.display = 'none';
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Update category visibility
|
|
|
|
document.querySelectorAll('.category-section').forEach((categorySection) => {
|
|
|
|
const items = categorySection.querySelectorAll('.app-card');
|
|
|
|
|
|
|
|
// Count visible items in this category
|
|
|
|
const visibleItems = Array.from(items).filter(item =>
|
|
|
|
item.style.display !== 'none'
|
|
|
|
).length;
|
|
|
|
|
|
|
|
// If no visible items and we're searching, hide the category
|
|
|
|
if (query !== '' && visibleItems === 0) {
|
|
|
|
categorySection.style.display = 'none';
|
|
|
|
} else {
|
|
|
|
categorySection.style.display = '';
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// Update results status
|
|
|
|
this.hasResults = query === '' || anyResults;
|
|
|
|
this.visibleCount = visibleCount;
|
2025-05-03 13:19:10 -07:00
|
|
|
}
|
2025-05-03 18:22:25 -07:00
|
|
|
}));
|
2025-05-03 13:19:10 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
// Register searchArticles component
|
|
|
|
Alpine.data('searchArticles', () => ({
|
|
|
|
searchQuery: '',
|
|
|
|
hasResults: true,
|
|
|
|
visibleCount: 0,
|
|
|
|
loading: false,
|
|
|
|
focusedItemIndex: -1,
|
|
|
|
debounceTimeout: null,
|
2025-05-03 13:19:10 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
init() {
|
|
|
|
// Initialize with loading state
|
|
|
|
this.loading = true;
|
|
|
|
|
|
|
|
// Initialize the visible count
|
|
|
|
this.visibleCount = document.querySelectorAll('.article-item').length;
|
|
|
|
|
|
|
|
// Setup watchers
|
|
|
|
this.$watch('searchQuery', (query) => {
|
|
|
|
// Debounce search for better performance
|
|
|
|
if (this.debounceTimeout) {
|
|
|
|
clearTimeout(this.debounceTimeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.debounceTimeout = setTimeout(() => {
|
|
|
|
this.filterContent(query);
|
|
|
|
}, 150);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Setup keyboard shortcuts
|
|
|
|
this.setupKeyboardShortcuts();
|
|
|
|
|
|
|
|
// Set loading to false after initialization
|
|
|
|
setTimeout(() => {
|
|
|
|
this.loading = false;
|
|
|
|
}, 300);
|
|
|
|
},
|
2025-05-03 13:19:10 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
setupKeyboardShortcuts() {
|
|
|
|
document.addEventListener('keydown', (e) => {
|
|
|
|
// '/' key focuses the search input
|
|
|
|
if (e.key === '/' && document.activeElement.id !== 'app-search') {
|
|
|
|
e.preventDefault();
|
|
|
|
document.getElementById('app-search').focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Escape key clears the search
|
|
|
|
if (e.key === 'Escape' && this.searchQuery !== '') {
|
|
|
|
this.searchQuery = '';
|
|
|
|
document.getElementById('app-search').focus();
|
|
|
|
this.focusedItemIndex = -1;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
2025-05-03 13:19:10 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
filterContent(query) {
|
|
|
|
query = query.toLowerCase().trim();
|
|
|
|
let anyResults = false;
|
|
|
|
let visibleCount = 0;
|
|
|
|
|
|
|
|
document.querySelectorAll('.article-item').forEach((item) => {
|
|
|
|
const title = (item.getAttribute('data-title') || '').toLowerCase();
|
|
|
|
const tags = (item.getAttribute('data-tags') || '').toLowerCase();
|
|
|
|
const description = (item.getAttribute('data-description') || '').toLowerCase();
|
|
|
|
|
|
|
|
const isMatch = query === '' ||
|
|
|
|
title.includes(query) ||
|
|
|
|
tags.includes(query) ||
|
|
|
|
description.includes(query);
|
|
|
|
|
|
|
|
if (isMatch) {
|
|
|
|
item.style.display = '';
|
|
|
|
anyResults = true;
|
|
|
|
visibleCount++;
|
2025-05-03 13:35:55 -07:00
|
|
|
} else {
|
2025-05-03 18:22:25 -07:00
|
|
|
item.style.display = 'none';
|
2025-05-03 13:35:55 -07:00
|
|
|
}
|
2025-05-03 18:22:25 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
this.hasResults = query === '' || anyResults;
|
|
|
|
this.visibleCount = visibleCount;
|
|
|
|
}
|
|
|
|
}));
|
2025-05-03 13:35:55 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
// Register searchProjects component
|
|
|
|
Alpine.data('searchProjects', () => ({
|
|
|
|
searchQuery: '',
|
|
|
|
hasResults: true,
|
|
|
|
visibleCount: 0,
|
|
|
|
loading: false,
|
|
|
|
focusedItemIndex: -1,
|
|
|
|
debounceTimeout: null,
|
2025-05-03 13:35:55 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
init() {
|
|
|
|
// Initialize with loading state
|
|
|
|
this.loading = true;
|
|
|
|
|
|
|
|
// Initialize the visible count
|
|
|
|
this.visibleCount = document.querySelectorAll('.project-item').length;
|
|
|
|
|
|
|
|
// Setup watchers
|
|
|
|
this.$watch('searchQuery', (query) => {
|
|
|
|
// Debounce search for better performance
|
|
|
|
if (this.debounceTimeout) {
|
|
|
|
clearTimeout(this.debounceTimeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.debounceTimeout = setTimeout(() => {
|
|
|
|
this.filterContent(query);
|
|
|
|
}, 150);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Setup keyboard shortcuts
|
|
|
|
this.setupKeyboardShortcuts();
|
|
|
|
|
|
|
|
// Set loading to false after initialization
|
|
|
|
setTimeout(() => {
|
|
|
|
this.loading = false;
|
|
|
|
}, 300);
|
|
|
|
},
|
2025-05-03 13:35:55 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
setupKeyboardShortcuts() {
|
|
|
|
document.addEventListener('keydown', (e) => {
|
|
|
|
// '/' key focuses the search input
|
|
|
|
if (e.key === '/' && document.activeElement.id !== 'app-search') {
|
|
|
|
e.preventDefault();
|
|
|
|
document.getElementById('app-search').focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Escape key clears the search
|
|
|
|
if (e.key === 'Escape' && this.searchQuery !== '') {
|
|
|
|
this.searchQuery = '';
|
|
|
|
document.getElementById('app-search').focus();
|
|
|
|
this.focusedItemIndex = -1;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
2025-05-03 13:35:55 -07:00
|
|
|
|
2025-05-03 18:22:25 -07:00
|
|
|
filterContent(query) {
|
|
|
|
query = query.toLowerCase().trim();
|
|
|
|
let anyResults = false;
|
|
|
|
let visibleCount = 0;
|
|
|
|
|
|
|
|
document.querySelectorAll('.project-item').forEach((item) => {
|
|
|
|
const title = (item.getAttribute('data-title') || '').toLowerCase();
|
|
|
|
const tags = (item.getAttribute('data-tags') || '').toLowerCase();
|
|
|
|
const description = (item.getAttribute('data-description') || '').toLowerCase();
|
|
|
|
const github = (item.getAttribute('data-github') || '').toLowerCase();
|
|
|
|
const live = (item.getAttribute('data-live') || '').toLowerCase();
|
|
|
|
|
|
|
|
const isMatch = query === '' ||
|
|
|
|
title.includes(query) ||
|
|
|
|
tags.includes(query) ||
|
|
|
|
description.includes(query) ||
|
|
|
|
github.includes(query) ||
|
|
|
|
live.includes(query);
|
|
|
|
|
|
|
|
if (isMatch) {
|
|
|
|
item.style.display = '';
|
|
|
|
anyResults = true;
|
|
|
|
visibleCount++;
|
2025-05-03 13:19:10 -07:00
|
|
|
} else {
|
2025-05-03 18:22:25 -07:00
|
|
|
item.style.display = 'none';
|
2025-05-03 13:19:10 -07:00
|
|
|
}
|
2025-05-03 18:22:25 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
this.hasResults = query === '' || anyResults;
|
|
|
|
this.visibleCount = visibleCount;
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
}
|
2025-05-03 01:35:48 -07:00
|
|
|
});
|
|
|
|
</script>
|