nefunguje mi tam ten icon picker

This commit is contained in:
Tomas Dvorak
2025-05-30 12:57:43 +02:00
parent e8ac93bdfb
commit 166edce67f
2 changed files with 98 additions and 419 deletions
+88 -108
View File
@@ -1214,10 +1214,25 @@
</div> </div>
</div> </div>
<!-- Include icon picker component --> <!-- Icon Picker Modal -->
<script src="components/icon-picker.js" defer></script> <div id="iconPickerModal" class="fixed inset-0 z-50 hidden">
<div id="iconPickerContainer" class="bg-white rounded-xl shadow-2xl overflow-hidden flex flex-col">
<div id="iconPickerHeader" class="bg-white border-b border-gray-200 px-6 py-4">
<h3 class="text-xl font-semibold text-gray-900">Vyberte ikonu</h3>
<button id="closeIconPicker" class="text-gray-400 hover:text-gray-500">
<i class="fas fa-times"></i>
</button>
</div>
<div id="iconSearchContainer" class="px-6 py-4 border-b border-gray-200">
<input type="text" id="iconSearch" class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" placeholder="Hledat ikony..." autocomplete="off">
</div>
<div id="iconListContainer" class="flex-1 overflow-y-auto">
<div id="iconList" class="p-6">
<!-- Icons will be populated by JavaScript -->
</div>
</div>
</div>
</div>
<script> <script>
// Get token and check authentication // Get token and check authentication
@@ -2202,7 +2217,37 @@ function selectIcon(iconClass) {
// Update the visible input with a friendly name // Update the visible input with a friendly name
const iconName = iconClass.replace('fa-', '').replace(/-/g, ' '); const iconName = iconClass.replace('fa-', '').replace(/-/g, ' ');
iconInput.value = iconName.split(' ') iconInput.value = iconName.split(' ')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
// Highlight the selected icon
const selectedOption = document.querySelector(`.icon-option[data-icon="${iconClass}"]`);
if (selectedOption) {
// Remove active class from all icons
document.querySelectorAll('.icon-option').forEach(option => {
option.classList.remove('active');
});
// Add active class to selected icon
selectedOption.classList.add('active');
}
// Close the modal
isModalOpen = false;
document.body.style.overflow = '';
iconPickerModal.style.display = 'none';
// Update the preview
if (iconPreview) {
iconPreview.className = 'w-12 h-12 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center mr-3';
}
}
}
// Reset icon selection // Reset icon selection
const appIcon = document.getElementById('appIcon');
const customIconInput = document.getElementById('customIconInput');
const customIconPreview = document.getElementById('customIconPreview');
const selectedIcon = document.getElementById('selectedIcon');
if (appIcon) appIcon.value = ''; if (appIcon) appIcon.value = '';
if (customIconInput) customIconInput.value = ''; if (customIconInput) customIconInput.value = '';
if (customIconPreview) { if (customIconPreview) {
@@ -2224,6 +2269,12 @@ function selectIcon(iconClass) {
const selectedIcons = document.querySelectorAll('.icon-option.selected'); const selectedIcons = document.querySelectorAll('.icon-option.selected');
selectedIcons.forEach(icon => icon.classList.remove('selected')); selectedIcons.forEach(icon => icon.classList.remove('selected'));
// Show the modal
const modal = document.getElementById('appModal');
if (modal) {
modal.classList.remove('hidden');
}
// Set focus to the first input field // Set focus to the first input field
const firstInput = form?.querySelector('input, textarea, select'); const firstInput = form?.querySelector('input, textarea, select');
if (firstInput) firstInput.focus(); if (firstInput) firstInput.focus();
@@ -2298,9 +2349,6 @@ document.addEventListener('DOMContentLoaded', function() {
// Initialize file input for custom icon upload // Initialize file input for custom icon upload
setupFileInput(); setupFileInput();
// Initialize icon picker
window.iconPicker = IconPicker.getInstance();
// Add click handler for custom icon button // Add click handler for custom icon button
const customIconBtn = document.getElementById('customIconBtn'); const customIconBtn = document.getElementById('customIconBtn');
const customIconInput = document.getElementById('customIconInput'); const customIconInput = document.getElementById('customIconInput');
@@ -2311,18 +2359,6 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
} }
// Add click handler for icon picker
const appIcon = document.getElementById('appIcon');
if (appIcon) {
appIcon.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
if (window.iconPicker) {
window.iconPicker.open();
}
});
}
const appColor = document.getElementById('appColor'); const appColor = document.getElementById('appColor');
const appColorText = document.getElementById('appColorText'); const appColorText = document.getElementById('appColorText');
@@ -2409,18 +2445,16 @@ function initIconPicker() {
let isModalOpen = false; let isModalOpen = false;
// Improved modal toggle with cleanup // Simple show/hide functions
const toggleModal = () => { const toggleModal = () => {
isModalOpen = !isModalOpen; isModalOpen = !isModalOpen;
document.body.style.overflow = isModalOpen ? 'hidden' : ''; document.body.style.overflow = isModalOpen ? 'hidden' : '';
iconPickerModal.style.display = isModalOpen ? 'flex' : 'none'; iconPickerModal.style.display = isModalOpen ? 'block' : 'none';
if (isModalOpen) { if (isModalOpen) {
// Focus search input only after modal is visible // Focus search input only after modal is visible
requestAnimationFrame(() => { requestAnimationFrame(() => {
if (iconSearch) { if (iconSearch) {
iconSearch.value = '';
renderIcons('');
iconSearch.focus(); iconSearch.focus();
} }
}); });
@@ -2436,13 +2470,19 @@ function initIconPicker() {
// Close modal handlers // Close modal handlers
if (closeButton) { if (closeButton) {
closeButton.addEventListener('click', toggleModal); closeButton.addEventListener('click', () => {
isModalOpen = false;
document.body.style.overflow = '';
iconPickerModal.style.display = 'none';
});
} }
// Close when clicking outside the modal content // Close when clicking outside the modal content
iconPickerModal.addEventListener('click', (e) => { iconPickerModal.addEventListener('click', (e) => {
if (e.target === iconPickerModal && isModalOpen) { if (e.target === iconPickerModal) {
toggleModal(); isModalOpen = false;
document.body.style.overflow = '';
iconPickerModal.style.display = 'none';
} }
}); });
@@ -2450,24 +2490,20 @@ function initIconPicker() {
if (iconList) { if (iconList) {
iconList.addEventListener('click', (e) => { iconList.addEventListener('click', (e) => {
const iconOption = e.target.closest('.icon-option'); const iconOption = e.target.closest('.icon-option');
if (iconOption && isModalOpen) { if (iconOption) {
const iconClass = iconOption.getAttribute('data-icon'); const iconClass = iconOption.getAttribute('data-icon');
if (iconClass) { selectIcon(iconClass);
selectIcon(iconClass);
toggleModal(); // Close modal after selection
}
} }
}); });
} }
// Handle search with improved debounce // Handle search
if (iconSearch) { if (iconSearch) {
let searchTimeout; iconSearch.addEventListener('input', () => {
iconSearch.addEventListener('input', (e) => { // Debounce search to prevent excessive re-renders
const searchTerm = e.target.value.toLowerCase(); clearTimeout(iconSearch.dataset.searchTimeout);
if (searchTimeout) clearTimeout(searchTimeout); iconSearch.dataset.searchTimeout = setTimeout(() => {
searchTimeout = setTimeout(() => { renderIcons(iconSearch.value.toLowerCase());
renderIcons(searchTerm);
}, 200); }, 200);
}); });
} }
@@ -2478,15 +2514,9 @@ function initIconPicker() {
// Close on Escape // Close on Escape
document.addEventListener('keydown', (e) => { document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && isModalOpen) { if (e.key === 'Escape' && isModalOpen) {
toggleModal(); isModalOpen = false;
} document.body.style.overflow = '';
}); iconPickerModal.style.display = 'none';
// Prevent scrolling when modal is open
document.addEventListener('scroll', (e) => {
if (isModalOpen) {
e.preventDefault();
e.stopPropagation();
} }
}); });
} }
@@ -2496,11 +2526,6 @@ function renderIcons(searchTerm) {
const iconList = document.getElementById('iconList'); const iconList = document.getElementById('iconList');
if (!iconList) return; if (!iconList) return;
// Clear existing event listeners
iconList.querySelectorAll('.icon-option').forEach(option => {
option.removeEventListener('click', handleIconClick);
});
let iconsHtml = ''; let iconsHtml = '';
let hasVisibleIcons = false; let hasVisibleIcons = false;
@@ -2523,8 +2548,8 @@ function renderIcons(searchTerm) {
<div class="icon-option" <div class="icon-option"
data-icon="${iconClass}" data-icon="${iconClass}"
title="${displayName}"> title="${displayName}">
<i class="fas ${iconClass} text-xl hover:text-blue-500 transition-colors duration-200"></i> <i class="fas ${iconClass}"></i>
<span class="icon-name text-sm text-gray-600">${displayName}</span> <span class="icon-name">${displayName}</span>
</div>`; </div>`;
}); });
} }
@@ -2540,24 +2565,15 @@ function renderIcons(searchTerm) {
iconList.innerHTML = iconsHtml; iconList.innerHTML = iconsHtml;
// Add click handlers to new icon options // Add click handlers to icon options
iconList.querySelectorAll('.icon-option').forEach(option => { document.querySelectorAll('.icon-option').forEach(option => {
option.addEventListener('click', handleIconClick); option.addEventListener('click', function() {
const iconClass = this.getAttribute('data-icon');
selectIcon(iconClass);
});
}); });
} }
// Handle icon click
function handleIconClick(e) {
const iconClass = this.getAttribute('data-icon');
if (iconClass) {
selectIcon(iconClass);
const modal = document.getElementById('iconPickerModal');
if (modal) {
modal.style.display = 'none';
}
}
}
// Filter icons based on search input (now handled in renderIcons) // Filter icons based on search input (now handled in renderIcons)
// Select an icon // Select an icon
@@ -2578,24 +2594,17 @@ function selectIcon(iconClass) {
} }
// Set the app icon value to the selected icon class // Set the app icon value to the selected icon class
if (appIcon) { if (appIcon) appIcon.value = iconClass;
appIcon.value = iconClass;
appIcon.dispatchEvent(new Event('change'));
}
// Reset file input // Reset file input
const customIconInput = document.getElementById('customIconInput'); const customIconInput = document.getElementById('customIconInput');
if (customIconInput) { if (customIconInput) customIconInput.value = '';
customIconInput.value = '';
customIconInput.dispatchEvent(new Event('change'));
}
// Update preview with random color // Update preview with random color
const colors = ['blue', 'green', 'red', 'yellow', 'indigo', 'purple', 'pink', 'gray']; const colors = ['blue', 'green', 'red', 'yellow', 'indigo', 'purple', 'pink', 'gray'];
const randomColor = colors[Math.floor(Math.random() * colors.length)]; const randomColor = colors[Math.floor(Math.random() * colors.length)];
if (iconPreview) { if (iconPreview) {
iconPreview.className = `mt-2 flex items-center justify-center w-16 h-16 bg-${randomColor}-100 rounded-md overflow-hidden`; iconPreview.className = `mt-2 flex items-center justify-center w-16 h-16 bg-${randomColor}-100 rounded-md overflow-hidden`;
iconPreview.innerHTML = `<i class="fas ${iconClass} text-2xl text-gray-400"></i>`;
} }
} }
@@ -4110,39 +4119,10 @@ function applyTemplate(templateId) {
if (bannerPreview) { if (bannerPreview) {
bannerPreview.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); bannerPreview.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
} }
}}} }
// Load apps when the page loads // Load apps when the page loads
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Initialize icon picker
window.iconPicker = IconPicker.getInstance();
// Initialize file input for custom icon upload
setupFileInput();
// Add click handler for custom icon button
const customIconBtn = document.getElementById('customIconBtn');
const customIconInput = document.getElementById('customIconInput');
if (customIconBtn && customIconInput) {
customIconBtn.addEventListener('click', function(e) {
e.preventDefault();
customIconInput.click();
});
}
// Add click handler for icon picker
const appIcon = document.getElementById('appIcon');
if (appIcon) {
appIcon.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
if (window.iconPicker) {
window.iconPicker.open();
}
});
}
// Load apps
loadApps(); loadApps();
}); });
</script> </script>
-301
View File
@@ -1,301 +0,0 @@
class IconPicker {
constructor() {
this.modal = null;
this.container = null;
this.searchInput = null;
this.iconList = null;
this.closeButton = null;
this.isInitialized = false;
this.isModalOpen = false;
this.icons = [];
this.currentSearch = '';
this.eventListeners = [];
}
static getInstance() {
if (!window._iconPickerInstance) {
window._iconPickerInstance = new IconPicker();
}
return window._iconPickerInstance;
}
init() {
if (this.isInitialized) return;
// Check if DOM is ready
if (!document.body) {
console.error('DOM not ready - cannot initialize icon picker');
return;
}
// Create modal structure
this.modal = document.createElement('div');
this.modal.className = 'icon-picker-modal fixed inset-0 bg-black bg-opacity-50 z-50 hidden';
this.modal.innerHTML = `
<div class="fixed inset-0 flex items-center justify-center p-4">
<div class="bg-white rounded-xl shadow-2xl max-w-4xl w-full max-h-[90vh] overflow-hidden">
<div class="flex flex-col h-full">
<div class="border-b border-gray-200">
<div class="flex justify-between items-center p-6">
<h3 class="text-xl font-semibold text-gray-900">Vyberte ikonu</h3>
<button class="icon-picker-close text-gray-400 hover:text-gray-500 p-2">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<div class="border-b border-gray-200">
<div class="p-6">
<input
type="text"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 icon-picker-search"
placeholder="Hledat ikony..."
autocomplete="off"
>
</div>
</div>
<div class="flex-1 overflow-y-auto">
<div class="p-6 grid grid-cols-6 gap-4 icon-picker-list">
<!-- Icons will be populated here -->
</div>
</div>
</div>
</div>
</div>
`;
// Get references to elements
this.container = this.modal.querySelector('.icon-picker-modal > div');
this.searchInput = this.modal.querySelector('.icon-picker-search');
this.iconList = this.modal.querySelector('.icon-picker-list');
this.closeButton = this.modal.querySelector('.icon-picker-close');
if (!this.container || !this.searchInput || !this.iconList || !this.closeButton) {
console.error('Failed to initialize icon picker - missing elements');
return;
}
// Add to body
document.body.appendChild(this.modal);
// Initialize icons
this.initializeIcons();
// Setup event listeners
this.setupEventListeners();
this.isInitialized = true;
}
initializeIcons() {
// Load icons from categories
const categories = {
'Doprava': ['car', 'car-side', 'truck', 'bus', 'bicycle', 'motorcycle', 'plane', 'plane-departure', 'ship', 'subway', 'train', 'train-subway', 'walking', 'gas-pump', 'map-marker-alt', 'route'],
'Jídlo a nápoje': ['utensils', 'hamburger', 'pizza-slice', 'ice-cream', 'coffee', 'mug-hot', 'beer', 'wine-glass', 'wine-bottle', 'wine-glass-alt', 'wine-bottle-alt', 'apple-alt', 'bread-slice', 'cheese', 'drumstick-bite', 'egg', 'fish', 'hotdog', 'ice-cream', 'lemon', 'pepper-hot', 'shrimp', 'stroopwafel'],
'Nástroje': ['tools', 'wrench', 'screwdriver', 'hammer', 'toolbox', 'ruler', 'ruler-combined', 'ruler-horizontal', 'ruler-vertical', 'screwdriver-wrench', 'screwdriver', 'hammer', 'paint-roller', 'paint-brush', 'pencil-ruler', 'ruler', 'screwdriver', 'toolbox', 'wrench'],
'Kancelář': ['briefcase', 'folder', 'folder-open', 'file', 'file-alt', 'file-archive', 'file-audio', 'file-code', 'file-excel', 'file-image', 'file-pdf', 'file-word', 'file-powerpoint', 'file-signature', 'file-upload', 'file-download', 'file-export', 'file-import', 'file-invoice', 'file-invoice-dollar', 'file-medical', 'file-prescription']
};
// Convert to array format
this.icons = Object.entries(categories).map(([category, icons]) => ({
category,
icons: icons.map(icon => ({
name: icon,
displayName: icon.replace(/-/g, ' '),
className: `fa-${icon}`
}))
}));
}
renderIcons(searchTerm = '') {
searchTerm = searchTerm.toLowerCase();
this.currentSearch = searchTerm;
// Clear existing content
this.iconList.innerHTML = '';
// Add icons
this.icons.forEach(category => {
const filteredIcons = category.icons.filter(icon =>
icon.name.toLowerCase().includes(searchTerm) ||
category.category.toLowerCase().includes(searchTerm)
);
if (filteredIcons.length > 0) {
// Add category header
const categoryHeader = document.createElement('div');
categoryHeader.className = 'icon-category';
categoryHeader.textContent = category.category;
this.iconList.appendChild(categoryHeader);
// Add icons
filteredIcons.forEach(icon => {
const iconElement = document.createElement('div');
iconElement.className = 'icon-option';
iconElement.setAttribute('data-icon', icon.className);
iconElement.title = icon.displayName;
iconElement.innerHTML = `
<i class="fas ${icon.className} text-xl hover:text-blue-500 transition-colors duration-200"></i>
<span class="icon-name text-sm text-gray-600">${icon.displayName}</span>
`;
this.iconList.appendChild(iconElement);
});
}
});
// Add no results message if needed
if (this.iconList.children.length === 0) {
const noResults = document.createElement('div');
noResults.className = 'col-span-full text-center py-8 text-gray-500';
noResults.innerHTML = `
<i class="fas fa-search mb-2 text-2xl"></i>
<p>Žádné ikony nenalezeny</p>
`;
this.iconList.appendChild(noResults);
}
}
setupEventListeners() {
// Close button
const closeHandler = () => this.close();
this.closeButton.addEventListener('click', closeHandler);
this.eventListeners.push({ element: this.closeButton, event: 'click', handler: closeHandler });
// Search input
const searchHandler = (e) => {
const searchTerm = e.target.value.toLowerCase();
this.renderIcons(searchTerm);
};
this.searchInput.addEventListener('input', searchHandler);
this.eventListeners.push({ element: this.searchInput, event: 'input', handler: searchHandler });
// Document click - close when clicking outside
const clickHandler = (e) => {
if (this.isModalOpen && !this.container.contains(e.target)) {
this.close();
}
};
document.addEventListener('click', clickHandler);
this.eventListeners.push({ element: document, event: 'click', handler: clickHandler });
// Escape key
const keydownHandler = (e) => {
if (e.key === 'Escape' && this.isModalOpen) {
this.close();
}
};
document.addEventListener('keydown', keydownHandler);
this.eventListeners.push({ element: document, event: 'keydown', handler: keydownHandler });
// Icon selection
const iconClickHandler = (e) => {
const iconOption = e.target.closest('.icon-option');
if (iconOption && iconOption.dataset.icon) {
this.selectIcon(iconOption.dataset.icon);
}
};
this.iconList.addEventListener('click', iconClickHandler);
this.eventListeners.push({ element: this.iconList, event: 'click', handler: iconClickHandler });
}
cleanupEventListeners() {
this.eventListeners.forEach(listener => {
const { element, event, handler } = listener;
element.removeEventListener(event, handler);
});
this.eventListeners = [];
}
close() {
if (!this.isModalOpen) return;
this.isModalOpen = false;
this.modal.style.display = 'none';
document.body.style.overflow = '';
// Clear search
this.searchInput.value = '';
this.renderIcons('');
// Cleanup event listeners
this.cleanupEventListeners();
}
open() {
if (!this.isInitialized) this.init();
if (this.isModalOpen) return;
this.isModalOpen = true;
this.modal.style.display = 'flex';
document.body.style.overflow = 'hidden';
// Focus search input
requestAnimationFrame(() => {
this.searchInput.focus();
this.searchInput.value = '';
this.renderIcons('');
});
}
close() {
if (!this.isModalOpen) return;
this.isModalOpen = false;
this.modal.style.display = 'none';
document.body.style.overflow = '';
// Clear search
this.searchInput.value = '';
this.renderIcons('');
}
selectIcon(iconClass) {
// Get target elements
const selectedIcon = document.getElementById('selectedIcon');
const customIconPreview = document.getElementById('customIconPreview');
const iconPreview = document.getElementById('iconPreview');
const appIcon = document.getElementById('appIcon');
const customIconInput = document.getElementById('customIconInput');
// Update selected icon
if (selectedIcon) {
selectedIcon.className = `fas ${iconClass} text-2xl text-gray-400`;
selectedIcon.classList.remove('hidden');
}
// Hide custom icon preview
if (customIconPreview) {
customIconPreview.src = '';
customIconPreview.classList.add('hidden');
}
// Update app icon
if (appIcon) {
appIcon.value = iconClass;
appIcon.dispatchEvent(new Event('change'));
}
// Reset custom icon input
if (customIconInput) {
customIconInput.value = '';
customIconInput.dispatchEvent(new Event('change'));
}
// Update preview
if (iconPreview) {
const colors = ['blue', 'green', 'red', 'yellow', 'indigo', 'purple', 'pink', 'gray'];
const randomColor = colors[Math.floor(Math.random() * colors.length)];
iconPreview.className = `mt-2 flex items-center justify-center w-16 h-16 bg-${randomColor}-100 rounded-md overflow-hidden`;
iconPreview.innerHTML = `<i class="fas ${iconClass} text-2xl text-gray-400"></i>`;
}
// Close modal
this.close();
}
static getInstance() {
if (!window.iconPickerInstance) {
window.iconPickerInstance = new IconPicker();
}
return window.iconPickerInstance;
}
}