justin.deal/src/components/SearchScript.astro

730 lines
26 KiB
Plaintext

---
/**
* SearchScript component
* Imports the modular search functionality
*/
---
<!-- Register Alpine.js components -->
<script is:inline>
document.addEventListener('alpine:init', () => {
if (typeof Alpine !== 'undefined') {
// Register the styleControls component
Alpine.data('styleControls', () => ({
iconSize: 'medium',
iconSizeValue: 2, // Slider value: 1=small, 2=medium, 3=large
viewMode: 'grid',
displayMode: 'both',
init() {
// Load preferences from localStorage
this.loadPreferences();
// 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;
}
});
},
loadPreferences() {
if (typeof localStorage !== 'undefined') {
try {
// Load icon size
const savedIconSize = localStorage.getItem('services-icon-size');
if (savedIconSize) {
this.setIconSize(savedIconSize, false); // Don't update slider yet
}
// 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;
}
// Update slider value based on loaded icon size
this.updateSliderFromIconSize();
} 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);
}
}
},
// 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);
this.iconSize = size;
// Update slider value if requested
if (updateSlider) {
this.updateSliderFromIconSize();
}
this.savePreferences();
// Apply the size directly
this.applyIconSizeDirectly(size);
},
toggleViewMode() {
this.viewMode = this.viewMode === 'grid' ? 'list' : 'grid';
this.savePreferences();
// 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');
}
}
});
}
},
setDisplayMode(mode) {
this.displayMode = mode;
this.savePreferences();
// 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');
}
}
}
}));
// 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
init() {
// Initialize the visible count
this.visibleCount = document.querySelectorAll('.app-card').length;
this.setupWatchers();
this.setupKeyboardShortcuts();
// 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();
// Listen for events from styleControls component
this.setupStyleControlsListeners();
// Set loading to false after initialization
setTimeout(() => {
this.loading = false;
}, 300);
},
// 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 }
}));
}
});
},
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);
}
}
},
// 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);
}
}
},
// 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) {
this.iconSize = size;
this.applyIconSize();
this.savePreferences();
},
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}`);
},
// 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');
}
},
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;
}
}));
// Register searchArticles component
Alpine.data('searchArticles', () => ({
searchQuery: '',
hasResults: true,
visibleCount: 0,
loading: false,
focusedItemIndex: -1,
debounceTimeout: null,
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);
},
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;
}
});
},
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++;
} else {
item.style.display = 'none';
}
});
this.hasResults = query === '' || anyResults;
this.visibleCount = visibleCount;
}
}));
// Register searchProjects component
Alpine.data('searchProjects', () => ({
searchQuery: '',
hasResults: true,
visibleCount: 0,
loading: false,
focusedItemIndex: -1,
debounceTimeout: null,
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);
},
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;
}
});
},
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++;
} else {
item.style.display = 'none';
}
});
this.hasResults = query === '' || anyResults;
this.visibleCount = visibleCount;
}
}));
}
});
</script>