// NOTE: The scripts is used for all iconography pages and for demo purposes, if you wish you may use any part of the code for your own project
// Global variables (add new ones)
let currentSvgWeight = 'sa-thin'; // Default weight for SVG icons
let isNoFill = false; // Default no-fill state for SVG icons
// Global variables
let allIcons = [];
let searchHistory = new Set();
let searchTimeout = null;
let currentToast = null;
let iconMappings = {};
let currentSearchTerm = '';
let currentIconSet = 'sa';
let isLoadingIconSet = false; // Flag to prevent multiple simultaneous loads
let selectedIcon = null; // Move selectedIcon to global scope so it's shared
let editingLayerIndex = null; // Move editingLayerIndex to global scope
let db; // IndexedDB setup and management
// Semantic matches
function findSemanticMatches(searchTerm) {
searchTerm = searchTerm.toLowerCase().replace(/\s+/g, '-');
if (iconMappings[searchTerm]) return iconMappings[searchTerm];
const partialMatches = Object.keys(iconMappings).filter(key => {
const normalizedKey = key.toLowerCase().replace(/\s+/g, '-').replace(/^-/, ''); // Strip leading hyphen
return normalizedKey.includes(searchTerm) || searchTerm.includes(normalizedKey);
});
if (partialMatches.length > 0) return partialMatches.flatMap(key => iconMappings[key]);
for (const category in iconMappings) {
const categoryMatches = Object.keys(iconMappings[category]).filter(key => {
const normalizedKey = key.toLowerCase().replace(/\s+/g, '-').replace(/^-/, ''); // Strip leading hyphen
return normalizedKey.includes(searchTerm) || searchTerm.includes(normalizedKey);
});
if (categoryMatches.length > 0) return categoryMatches.flatMap(key => iconMappings[category][key]);
}
return null;
}
// Suggestions with Levenshtein distance
function findSuggestions(searchTerm) {
if (!searchTerm || searchTerm.length < 2) return [];
searchTerm = searchTerm.replace(/\s+/g, '-').replace(/^-/, ''); // Strip leading hyphen
const semanticMatches = findSemanticMatches(searchTerm);
if (semanticMatches) {
return [...new Set(semanticMatches)].filter(icon =>
allIcons.includes(`-${icon}`) || allIcons.includes(icon)
).map(icon => icon.replace(/^-/, '')); // Clean icons in results
}
return allIcons
.map(icon => {
const iconName = icon.startsWith('-') ? icon.substring(1) : icon;
return {
name: iconName,
distance: levenshteinDistance(searchTerm.toLowerCase(), iconName.toLowerCase())
};
})
.filter(item => {
const maxDistance = Math.min(Math.floor(searchTerm.length * 0.4), 3);
return item.distance > 0 && item.distance <= maxDistance;
})
.sort((a, b) => a.distance - b.distance)
.slice(0, 3)
.map(item => item.name);
}
function levenshteinDistance(a, b) {
if (a.length === 0) return b.length;
if (b.length === 0) return a.length;
const matrix = Array(b.length + 1).fill(null).map(() => Array(a.length + 1).fill(null));
for (let i = 0; i <= b.length; i++) matrix[i][0] = i;
for (let j = 0; j <= a.length; j++) matrix[0][j] = j;
for (let i = 1; i <= b.length; i++) {
for (let j = 1; j <= a.length; j++) {
matrix[i][j] = b.charAt(i - 1) === a.charAt(j - 1) ?
matrix[i - 1][j - 1] :
Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
}
}
return matrix[b.length][a.length];
}
// Filter logic
function initializeFilter() {
const searchInput = document.getElementById('searchIcons');
searchInput.addEventListener('input', function () {
currentSearchTerm = this.value.trim().replace(/^-/, ''); // Strip leading hyphen
filterIcons();
});
}
function filterIcons() {
const searchTerms = currentSearchTerm.toLowerCase().split(/\s+/).filter(term => term.length > 0).map(term => term.replace(/^-/, '')); // Strip leading hyphens
document.querySelectorAll('#iconList li').forEach(item => {
const text = item.textContent.toLowerCase().replace(/^-/, ''); // Strip leading hyphen
const matches = searchTerms.every(term => text.includes(term));
item.classList.toggle('js-filter-hide', !matches);
});
updateVisibleCount();
if (searchTimeout) clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => updateSearchHistory(currentSearchTerm), 1000);
const visibleIcons = document.querySelectorAll('#iconList li:not(.js-filter-hide)').length;
const suggestionsContainer = document.getElementById('suggestions');
if (currentSearchTerm.length >= 2 && visibleIcons < 10) {
const suggestions = findSuggestions(currentSearchTerm);
suggestionsContainer.innerHTML = suggestions.length > 0 ?
`Did you mean? ${suggestions.map(s =>
`${s}`).join(' ')}` :
'';
} else {
suggestionsContainer.innerHTML = '';
}
}
// Load icon set
async function loadIconSet(iconSet = 'sa') {
// Prevent multiple simultaneous loads
if (isLoadingIconSet) {
console.log('Already loading an icon set, please wait...');
return;
}
isLoadingIconSet = true;
try {
// Show loading indicator
const iconList = document.getElementById('iconList');
iconList.innerHTML = '
';
// Clear search input and results
const searchInput = document.getElementById('searchIcons');
if (searchInput) {
searchInput.value = '';
currentSearchTerm = '';
}
document.getElementById('suggestions').innerHTML = '';
const iconSets = {
'sa': { icons: 'json/sa-icons.json', mappings: 'json/sa-mappings.json', prefix: 'sa' },
'base': { icons: 'json/sa-base.json', mappings: 'json/sa-mappings.json', prefix: 'sa' },
'svg': { icons: 'json/sa-svg-icons.json', mappings: 'json/sa-svg-mappings.json', prefix: 'svg' },
'fal': { icons: 'json/fa-icons.json', mappings: 'json/fa-mappings.json', prefix: 'fal' },
'fas': { icons: 'json/fa-icons.json', mappings: 'json/fa-mappings.json', prefix: 'fas' },
'far': { icons: 'json/fa-icons.json', mappings: 'json/fa-mappings.json', prefix: 'far' },
'fab': { icons: 'json/fa-brands.json', mappings: 'json/fa-mappings.json', prefix: 'fab' },
'fad': { icons: 'json/fa-duotone.json', mappings: 'json/fa-mappings.json', prefix: 'fad' },
'material': { icons: 'json/material-icons.json', mappings: 'json/material-mappings.json', prefix: 'material' }
};
const selectedSet = iconSets[iconSet] || iconSets['sa'];
// Always fetch new data when switching icon sets
try {
const [iconsResponse, mappingsResponse] = await Promise.all([
fetch(selectedSet.icons),
fetch(selectedSet.mappings)
]);
if (!iconsResponse.ok || !mappingsResponse.ok) {
throw new Error('Failed to load resources');
}
// Clear previous icons and load new ones
allIcons = (await iconsResponse.json()).map(icon => icon.replace(/^-/, '')); // Strip leading hyphen from all icons
iconMappings = await mappingsResponse.json();
// Update current icon set and stylesheet
currentIconSet = iconSet;
// Generate new icon list
generateIconList(allIcons, selectedSet.prefix);
// Initialize filter if not already done
if (!document.getElementById('searchIcons').hasAttribute('data-initialized')) {
initializeFilter();
document.getElementById('searchIcons').setAttribute('data-initialized', 'true');
}
console.log(`Successfully loaded icon set: ${iconSet}`);
} catch (error) {
console.error('Error loading icon set:', error);
iconList.innerHTML = `Failed to load icon set: ${error.message}
`;
}
} finally {
isLoadingIconSet = false;
}
// Re-apply filter if search term exists
if (currentSearchTerm) filterIcons();
updateVisibleCount();
}
// Generate icon list
function generateIconList(icons, iconPrefix) {
const iconList = document.getElementById('iconList');
iconList.innerHTML = icons.map(icon => {
const cleanIconName = icon.replace(/^-/, ''); // Ensure no leading hyphen
const iconClass = getIconClass(iconPrefix, cleanIconName);
const displayName = cleanIconName;
const isSvg = iconPrefix === 'svg';
return `
${displayName}
`;
}).join('');
updateVisibleCount();
addIconClickHandlers();
}
// Search history
function updateSearchHistory(term) {
if (term && term.length >= 2) {
searchHistory.add(term.replace(/^-/, '')); // Strip leading hyphen
if (searchHistory.size > 5) searchHistory.delete([...searchHistory][0]);
renderSearchHistory();
}
}
function renderSearchHistory() {
const historyContainer = document.getElementById('searchHistory');
historyContainer.innerHTML = [...searchHistory].map(term =>
`
${term}
`
).join('');
}
function removeFromHistory(term) {
searchHistory.delete(term);
renderSearchHistory();
}
function applySearch(term) {
const searchIcons = document.getElementById('searchIcons');
searchIcons.value = term.replace(/^-/, ''); // Strip leading hyphen
currentSearchTerm = term.replace(/^-/, ''); // Strip leading hyphen
filterIcons();
}
// Icon utilities
function updateVisibleCount() {
const visibleIcons = document.querySelectorAll('#iconList li:not(.js-filter-hide)').length;
const totalIcons = allIcons.length;
document.querySelector('.results-count').textContent =
`Showing ${visibleIcons} of ${totalIcons} icons`;
}
// Update getIconClass to handle SVG classes dynamically (add this at the end of the existing getIconClass function)
const getIconClass = (prefix, icon) => {
const cleanIcon = icon.replace(/^-/, ''); // Ensure no leading hyphen
switch (prefix) {
case 'svg':
const weightClass = currentSvgWeight; // Use the current weight from dropdown
const fillClass = isNoFill ? ' sa-nofill' : ''; // Add no-fill class if checkbox is checked
return ``;
case 'fal':
return ``;
case 'fas':
return ``;
case 'far':
return ``;
case 'fad':
return ``;
case 'fab':
return ``;
case 'material':
return `${cleanIcon}`;
case 'sa':
return ``; // SmartAdmin format: sa sa-iconname
case 'base':
return ``;
default:
return ``;
}
};
function addIconClickHandlers() {
// Get the select button from the modal
const selectButton = document.querySelector('#example-modal-backdrop-transparent .modal-footer .btn-primary');
// Disable the select button initially
if (selectButton) {
selectButton.disabled = true;
selectButton.classList.add('disabled');
}
// Remove any existing click handlers to avoid conflicts
document.querySelectorAll('.js-showcase-icon').forEach(iconElement => {
const newElement = iconElement.cloneNode(true);
iconElement.parentNode.replaceChild(newElement, iconElement);
});
// Add new click handlers for icon selection
document.querySelectorAll('.js-showcase-icon').forEach(iconElement => {
iconElement.addEventListener('click', function(event) {
event.preventDefault();
event.stopPropagation();
// Remove selection from all icons
document.querySelectorAll('.js-showcase-icon').forEach(icon => {
icon.classList.remove('selected-icon');
});
// Add selection to clicked icon
this.classList.add('selected-icon');
// Store the selected icon
const iconEl = this.querySelector('svg') || this.querySelector('i');
const iconContainer = this.querySelector('.icon-container');
if (iconEl && iconContainer) {
// Store the icon HTML for later use in the global variable
selectedIcon = iconContainer.innerHTML.trim();
console.log('Icon selected:', selectedIcon); // Debug log
// Enable the select button
if (selectButton) {
selectButton.disabled = false;
selectButton.classList.remove('disabled');
}
}
});
});
}
function copyToClipboard(text) {
let copyText = text;
if (text.includes('sprite.svg#')) {
// Keep the weight and fill classes when copying SVG markup
const weightClass = currentSvgWeight;
const fillClass = isNoFill ? ' sa-nofill' : '';
copyText = ``;
}
navigator.clipboard.writeText(copyText).then(() =>
console.log('Icon class/markup copied:', copyText)
).catch(err => console.error('Failed to copy:', err));
}
function escapeHTML(str) {
return str.replace(/&/g, '&').replace(//g, '>')
.replace(/"/g, '"').replace(/'/g, "'");
}
function showToast(message, type = 'primary') {
if (currentToast) {
currentToast.hide();
}
// Check if toast container exists, create if not
let toastContainer = document.querySelector('.toast-container');
if (!toastContainer) {
toastContainer = document.createElement('div');
toastContainer.className = 'toast-container position-fixed top-0 end-0 p-3';
document.body.appendChild(toastContainer);
}
// Create toast element
const toastId = 'toast-' + Date.now();
const toast = document.createElement('div');
toast.className = `toast hide align-items-center border-0 py-2 px-3 bg-${type} text-white`;
toast.id = toastId;
toast.setAttribute('role', 'alert');
toast.setAttribute('aria-live', 'assertive');
toast.setAttribute('aria-atomic', 'true');
toast.style.setProperty('--bs-toast-max-width', 'auto');
toast.innerHTML = `
`;
toastContainer.appendChild(toast);
// Initialize and show the toast
currentToast = new bootstrap.Toast(toast, {
autohide: true,
delay: 3000
});
// Remove toast after it's hidden
toast.addEventListener('hidden.bs.toast', function() {
currentToast = null;
toast.remove();
});
currentToast.show();
}
function updateSvgClasses() {
document.querySelectorAll('#iconList li').forEach(item => {
const svgElement = item.querySelector('svg');
if (svgElement) {
const weightClass = currentSvgWeight;
const fillClass = isNoFill ? ' sa-nofill' : '';
// Update classes using classList
svgElement.setAttribute('class', `sa-icon ${weightClass}${fillClass}`);
}
});
}
// Initialize when DOM is loaded
// document.addEventListener('DOMContentLoaded', function() {
// loadIconSet('sa')
// .then(() => console.log('Icon set loaded successfully'))
// .catch(error => console.error('Failed to load icon set:', error));
// });
/// zoom container
// Function to handle zoom-in (incrementing classes)
function adjustZoom(direction) {
var container = document.getElementById('stackgenerator-container');
var currentClass = container.className.match(/icon-zoom-(\d+)/); // Extract current zoom level
var currentLevel = currentClass ? parseInt(currentClass[1], 10) : 0; // Default to 0 if no zoom class
// Remove existing zoom class
if (currentLevel > 0) {
container.className = container.className.replace(/icon-zoom-\d+/, '');
}
// Calculate new level based on direction
var newLevel;
if (direction === 'in') {
newLevel = currentLevel < 15 ? currentLevel + 1 : 15; // Zoom in, cap at 10
} else if (direction === 'out') {
newLevel = currentLevel > 1 ? currentLevel - 1 : 0; // Zoom out, floor at 0
} else {
return; // Invalid direction, do nothing
}
// Add the new zoom class (skip if level is 0)
if (newLevel > 0) {
container.className += ' icon-zoom-' + newLevel;
}
}
// Example usage with event listeners
document.getElementById('zoomInBtn').onclick = function() {
adjustZoom('in');
};
document.getElementById('zoomOutBtn').onclick = function() {
adjustZoom('out');
};
// Responsive Draggable Stack Control Panel with Grid Snapping
document.addEventListener('DOMContentLoaded', function() {
const stackControl = document.getElementById('stack-control');
const container = document.getElementById('stackgenerator-container');
if (!stackControl || !container) return;
// Make the panel draggable only by its header
const panelHeader = stackControl.querySelector('.panel-hdr > h2');
if (!panelHeader) return;
// Add draggable cursor and styling
panelHeader.style.cursor = 'move';
stackControl.style.position = 'absolute';
stackControl.style.zIndex = '50';
// Offset for boundaries
const offsetX = 16;
const offsetY = 16;
// Grid size for snapping (in pixels)
const gridSize = 20;
// Variables to track dragging
let isDragging = false;
let startX, startY;
let startPosX, startPosY;
// Initialize save and copy button states
updateSaveButtonState();
// Function to position the panel within bounds
function positionPanelWithinBounds(animate = false) {
const containerRect = container.getBoundingClientRect();
const panelRect = stackControl.getBoundingClientRect();
// Get current position
let currentLeft = parseInt(stackControl.style.left) || 0;
let currentTop = parseInt(stackControl.style.top) || 0;
// Calculate boundaries
const maxX = containerRect.width - panelRect.width - offsetX;
const maxY = containerRect.height - panelRect.height - offsetY;
const minX = offsetX;
const minY = offsetY;
// Adjust position if needed
let newLeft = Math.max(minX, Math.min(maxX, currentLeft));
let newTop = Math.max(minY, Math.min(maxY, currentTop));
// Apply position with or without animation
if (animate) {
stackControl.style.transition = 'all 0.3s ease-out';
setTimeout(() => {
stackControl.style.transition = '';
}, 300);
}
stackControl.style.left = newLeft + 'px';
stackControl.style.top = newTop + 'px';
}
// Set initial position in top right corner with offset
function setInitialPosition() {
const containerRect = container.getBoundingClientRect();
const panelRect = stackControl.getBoundingClientRect();
// Position in top right corner with offset
const rightPosition = containerRect.width - panelRect.width - offsetX;
stackControl.style.top = offsetY + 'px';
stackControl.style.left = rightPosition + 'px';
}
// Initialize position
setInitialPosition();
// Start dragging
panelHeader.addEventListener('mousedown', function(e) {
// Only allow left mouse button
if (e.button !== 0) return;
e.preventDefault();
// Get initial positions
isDragging = true;
startX = e.clientX;
startY = e.clientY;
// Get current panel position
const rect = stackControl.getBoundingClientRect();
startPosX = rect.left;
startPosY = rect.top;
// Add dragging class for visual feedback
stackControl.classList.add('is-dragging');
// Add temporary overlay to prevent text selection during drag
const overlay = document.createElement('div');
overlay.id = 'drag-overlay';
overlay.style.position = 'fixed';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.zIndex = '999';
overlay.style.cursor = 'move';
document.body.appendChild(overlay);
});
// Handle dragging
document.addEventListener('mousemove', function(e) {
if (!isDragging) return;
// Calculate new position
const dx = e.clientX - startX;
const dy = e.clientY - startY;
// Get container boundaries
const containerRect = container.getBoundingClientRect();
const panelRect = stackControl.getBoundingClientRect();
// Calculate new position with boundaries
let newX = startPosX + dx;
let newY = startPosY + dy;
// Apply boundaries with offset
const maxX = containerRect.right - panelRect.width - offsetX;
const maxY = containerRect.bottom - panelRect.height - offsetY;
const minX = containerRect.left + offsetX;
const minY = containerRect.top + offsetY;
newX = Math.max(minX, Math.min(maxX, newX));
newY = Math.max(minY, Math.min(maxY, newY));
// Update position
stackControl.style.left = (newX - containerRect.left) + 'px';
stackControl.style.top = (newY - containerRect.top) + 'px';
});
// End dragging with grid snapping
document.addEventListener('mouseup', function() {
if (!isDragging) return;
isDragging = false;
// Remove dragging class
stackControl.classList.remove('is-dragging');
// Remove overlay
const overlay = document.getElementById('drag-overlay');
if (overlay) overlay.remove();
// Apply grid snapping
const rect = stackControl.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();
// Calculate position relative to container
let relativeX = rect.left - containerRect.left;
let relativeY = rect.top - containerRect.top;
// Snap to grid
relativeX = Math.round(relativeX / gridSize) * gridSize;
relativeY = Math.round(relativeY / gridSize) * gridSize;
// Apply snapped position with animation
stackControl.style.transition = 'all 0.2s ease-out';
stackControl.style.left = relativeX + 'px';
stackControl.style.top = relativeY + 'px';
// Remove transition after animation completes
setTimeout(() => {
stackControl.style.transition = '';
}, 200);
});
// Handle window resize to keep panel in bounds
window.addEventListener('resize', function() {
// Use requestAnimationFrame to avoid excessive calculations during resize
requestAnimationFrame(function() {
positionPanelWithinBounds(true);
});
});
// Handle container size changes (for responsive layouts)
// Use ResizeObserver if available, fallback to periodic checks
if (typeof ResizeObserver !== 'undefined') {
const resizeObserver = new ResizeObserver(function() {
positionPanelWithinBounds(true);
});
resizeObserver.observe(container);
} else {
// Fallback for browsers without ResizeObserver
let lastWidth = container.clientWidth;
let lastHeight = container.clientHeight;
// Check periodically for size changes
setInterval(function() {
if (lastWidth !== container.clientWidth || lastHeight !== container.clientHeight) {
lastWidth = container.clientWidth;
lastHeight = container.clientHeight;
positionPanelWithinBounds(true);
}
}, 250);
}
// Add CSS for visual feedback during dragging
const style = document.createElement('style');
style.textContent = `
#stack-control.is-dragging {
opacity: 0.8;
box-shadow: 0 0 10px rgba(0,0,0,0.3);
}
#stack-control .panel-hdr:hover {
background-color: rgba(0,0,0,0.05);
}
`;
document.head.appendChild(style);
});
// Icon Stack Management System
document.addEventListener('DOMContentLoaded', function() {
// Global variables for icon stack management are now defined at the top of the file
const MAX_LAYERS = 4;
// Initialize the icon stack management
function initIconStackManagement() {
const modal = document.getElementById('example-modal-backdrop-transparent');
const selectButton = modal.querySelector('.modal-footer .btn-primary');
const iconContainer = document.getElementById('my-icon');
const stackControl = document.getElementById('stack-control');
const stackControlContent = stackControl.querySelector('.panel-content');
// Add event listener for modal hidden event to reset state
modal.addEventListener('hidden.bs.modal', function() {
// Reset selection state when modal is closed
selectedIcon = null;
document.querySelectorAll('#iconList li a.js-showcase-icon').forEach(i => {
i.classList.remove('selected-icon');
});
// Disable the select button
if (selectButton) {
selectButton.disabled = true;
selectButton.classList.add('disabled');
}
console.log('Modal closed, reset selection state');
});
// Add event listener for modal shown event to ensure handlers are attached
modal.addEventListener('shown.bs.modal', function() {
console.log('Modal opened, reattaching icon click handlers');
// Reattach icon click handlers to ensure they work
setTimeout(addIconClickHandlers, 100);
});
// Initialize Sortable for drag and drop layer reordering
let sortableInstance = null;
function initSortable() {
if (sortableInstance) {
sortableInstance.destroy();
}
sortableInstance = new Sortable(stackControlContent, {
animation: 150,
handle: '.drag-handle',
ghostClass: 'sortable-ghost',
chosenClass: 'sortable-chosen',
dragClass: 'sortable-drag',
onEnd: function(evt) {
// Reorder the actual icon layers based on the new order in the control panel
reorderIconLayers();
}
});
}
// Function to reorder icon layers based on the order in the control panel
function reorderIconLayers() {
// Get all layers from the control panel
const controlLayers = stackControlContent.querySelectorAll('.stack-layers');
// Create a new array to hold the reordered layers
const reorderedLayers = [];
// Store the settings for each layer by its original index
const layerSettingsByOriginalIndex = {};
const iconLayers = iconContainer.querySelectorAll('.icon-layers');
// First, store settings for each layer by its original index
iconLayers.forEach((layer, index) => {
const iconElement = layer.querySelector('svg, i');
// Get rotation value
let rotation = '0';
if (iconElement) {
for (const cls of iconElement.classList) {
if (cls.startsWith('rotate-')) {
rotation = cls.replace('rotate-', '');
break;
}
}
}
// Get opacity value
let opacity = '10';
if (iconElement) {
for (const cls of iconElement.classList) {
if (cls.startsWith('alpha-')) {
opacity = cls.replace('alpha-', '');
break;
}
}
}
// Store settings by original index
layerSettingsByOriginalIndex[index] = {
rotation: rotation,
opacity: opacity
};
});
// Create a mapping of original indices to new positions
const newPositions = {};
// Get the layers in their new order from the control panel
controlLayers.forEach((controlLayer, newIndex) => {
const originalIndex = parseInt(controlLayer.dataset.layerIndex);
if (originalIndex >= 0 && originalIndex < iconLayers.length) {
reorderedLayers.push(iconLayers[originalIndex]);
newPositions[originalIndex] = newIndex;
}
});
// Clear the icon container
iconContainer.innerHTML = '';
// Add the layers back in their new order
reorderedLayers.forEach((layer) => {
iconContainer.appendChild(layer);
});
// Update the stack classes
//updateStackClasses();
// Update the control panel with the new order
updateStackControlPanel();
// Now restore each layer's settings based on its original index
const newControlLayers = stackControlContent.querySelectorAll('.stack-layers');
newControlLayers.forEach((controlLayer) => {
const newIndex = parseInt(controlLayer.dataset.layerIndex);
// Find which original layer is now at this position
let originalIndex = null;
for (const [origIdx, newPos] of Object.entries(newPositions)) {
if (parseInt(newPos) === newIndex) {
originalIndex = parseInt(origIdx);
break;
}
}
if (originalIndex !== null && layerSettingsByOriginalIndex[originalIndex]) {
const settings = layerSettingsByOriginalIndex[originalIndex];
// Update rotation slider
const rotationSlider = controlLayer.querySelector(`.rotation-slider`);
if (rotationSlider) {
rotationSlider.value = settings.rotation;
const rotationValue = rotationSlider.parentElement.querySelector('.rotation-value');
if (rotationValue) {
rotationValue.textContent = `${settings.rotation}°`;
}
}
// Update opacity slider
const opacitySlider = controlLayer.querySelector(`.opacity-slider`);
if (opacitySlider) {
opacitySlider.value = settings.opacity;
const opacityValue = opacitySlider.parentElement.querySelector('.opacity-value');
if (opacityValue) {
opacityValue.textContent = `${settings.opacity}0%`;
}
}
}
});
}
// Function to update stack classes based on layer position
// function updateStackClasses() {
// const iconLayers = iconContainer.querySelectorAll('.icon-layers');
// iconLayers.forEach((layer, idx) => {
// const iconElement = layer.querySelector('svg, i');
// if (!iconElement) return;
// // Only apply stack classes if they don't already exist
// if (!iconElement.classList.contains('stack-1x') &&
// !iconElement.classList.contains('stack-2x') &&
// !iconElement.classList.contains('stack-3x')) {
// // Add appropriate stack class based on position
// if (idx === 0 && iconLayers.length >= 1) {
// iconElement.classList.add('stack-3x'); // Bottom layer (largest)
// } else if (idx === 1 || (idx === 0 && iconLayers.length === 1)) {
// iconElement.classList.add('stack-2x'); // Middle layer or single layer
// } else {
// iconElement.classList.add('stack-1x'); // Top layer (smallest)
// }
// }
// });
// }
// Clear existing layers in stack control panel and add fine-tuning controls
function updateStackControlPanel() {
// Clear the existing content
stackControlContent.innerHTML = '';
// Get all icon layers
const iconLayers = iconContainer.querySelectorAll('.icon-layers');
// Create a control panel for each layer
iconLayers.forEach((layer, index) => {
const layerContent = layer.innerHTML.trim();
const layerClass = layer.className;
// Create a new layer in the stack control panel
const layerElement = document.createElement('div');
layerElement.className = 'stack-layers d-flex flex-column';
layerElement.dataset.layerIndex = index;
// Extract icon name for display
let iconName = 'Unknown Icon';
if (layerContent.includes('class="')) {
const classMatch = layerContent.match(/class="([^"]+)"/);
if (classMatch && classMatch[1]) {
// For SVG icons, extract name from href attribute
if (layerContent.includes('