/** * Cut Controls for Public Map * Handles cut selection and display on the public map interface */ import { cutManager } from './cut-manager.js'; import { showStatus } from './utils.js'; let cutSelector = null; let cutLegend = null; let cutLegendContent = null; let legendExpanded = false; let mobileOverlayModal = null; let mobileOverlayBtn = null; let cutSelectorListenerAttached = false; // Flag to prevent duplicate listeners let currentDropdownContainer = null; // Track current dropdown container /** * Initialize cut controls */ export async function initializeCutControls() { // Get DOM elements cutSelector = document.getElementById('cut-selector'); cutLegend = document.getElementById('cut-legend'); cutLegendContent = document.getElementById('cut-legend-content'); mobileOverlayModal = document.getElementById('mobile-overlay-modal'); mobileOverlayBtn = document.getElementById('mobile-overlay-btn'); if (!cutSelector) { console.warn('Cut selector not found'); return; } // Debug logging console.log('Mobile overlay button found:', !!mobileOverlayBtn); console.log('Mobile overlay modal found:', !!mobileOverlayModal); // Set up event listeners - remove the change listener since we're not using the select element as a dropdown // cutSelector.addEventListener('change', handleCutSelection); // Set up mobile overlay event delegation if (mobileOverlayModal) { setupMobileOverlayEventListeners(); } // Mobile overlay button is handled in ui-controls.js console.log('Cut controls setup - mobile overlay handled by ui-controls.js'); // Load and populate cuts await loadAndPopulateCuts(); console.log('Cut controls initialized'); } /** * Load cuts and populate selector */ async function loadAndPopulateCuts() { try { const response = await fetch('/api/cuts/public'); if (!response.ok) { console.warn('Failed to fetch cuts:', response.status); // For testing: create mock data if API fails const mockCuts = [ { id: 'test-1', name: 'Test Ward 1', description: 'Test overlay for debugging', category: 'Ward', color: '#ff6b6b', opacity: 0.3, is_public: true, is_official: true, geojson: '{"type":"Polygon","coordinates":[[[-113.5,-113.4],[53.5,53.6],[53.6,53.6],[53.6,53.5],[-113.5,-113.4]]]}' }, { id: 'test-2', name: 'Test Neighborhood', description: 'Another test overlay', category: 'Neighborhood', color: '#4ecdc4', opacity: 0.4, is_public: true, is_official: false } ]; console.log('Using mock cuts for testing'); await processCuts(mockCuts); return; } const data = await response.json(); console.log('Raw API response data:', data); const cuts = data.list || []; console.log('Extracted cuts from API:', cuts); await processCuts(cuts); console.log(`Loaded ${cuts.length} public cuts`); } catch (error) { console.error('Error loading cuts:', error); // Fallback to empty array await processCuts([]); } } /** * Process cuts data (shared logic for real and mock data) */ async function processCuts(cuts) { console.log('Processing cuts:', cuts); console.log('Number of cuts to process:', cuts?.length || 0); // Store cuts globally for reference window.cuts = cuts; console.log('Set window.cuts to:', window.cuts); // Populate both desktop and mobile selectors populateCutSelector(cuts); // Add a small delay to ensure mobile DOM elements are ready setTimeout(() => { populateMobileOverlayOptions(cuts); }, 100); // Auto-display all public cuts await autoDisplayAllPublicCuts(cuts); } /** * Auto-display all public cuts on map load */ async function autoDisplayAllPublicCuts(cuts) { if (!cuts || cuts.length === 0) return; // Filter for public cuts that should auto-display const publicCuts = cuts.filter(cut => { // Handle different possible field names for public visibility const isPublic = cut.is_public || cut.Is_public || cut['Public Visibility'] || cut['Public']; return isPublic === true || isPublic === 1 || isPublic === '1'; }); console.log(`Auto-displaying ${publicCuts.length} public cuts`); // Display all public cuts for (const cut of publicCuts) { try { const normalizedCut = normalizeCutData(cut); // Pass true as second parameter to indicate auto-displayed cutManager.displayCut(normalizedCut, true); } catch (error) { console.error(`Failed to auto-display cut: ${cut.name || cut.Name}`, error); } } // Update UI to show which cuts are active if (publicCuts.length > 0) { updateMultipleCutsUI(); console.log('About to update checkbox states after auto-display...'); // Add a small delay to ensure cuts are fully displayed before updating checkboxes setTimeout(() => { updateCheckboxStates(); // Update checkbox states to reflect auto-displayed cuts updateMobileCheckboxStates(); // Update mobile checkboxes too console.log('Checkbox states updated after auto-display'); }, 100); } } /** * Normalize cut data field names for consistent access */ function normalizeCutData(cut) { return { ...cut, id: cut.id || cut.Id || cut.ID, name: cut.name || cut.Name || 'Unnamed Cut', description: cut.description || cut.Description, color: cut.color || cut.Color || '#3388ff', opacity: cut.opacity || cut.Opacity || 0.3, category: cut.category || cut.Category || 'Other', geojson: cut.geojson || cut.GeoJSON || cut['GeoJSON Data'], is_public: cut.is_public || cut['Public Visibility'], is_official: cut.is_official || cut['Official Cut'] }; } /** * Populate the cut selector with all available cuts */ /** * Populate the cut selector with multi-select checkboxes */ function populateCutSelector(cuts) { if (!cutSelector || !cuts?.length) { console.warn('Cannot populate cut selector - missing selector or cuts'); return; } // Store cuts globally for global functions window.cuts = cuts; // Create the main selector (acts as a button to show/hide dropdown) cutSelector.textContent = 'Manage map overlays...'; // Remove any existing checkbox container const existingContainer = cutSelector.parentNode.querySelector('.cut-checkbox-container'); if (existingContainer) { existingContainer.remove(); } // Create checkbox container const checkboxContainer = document.createElement('div'); checkboxContainer.className = 'cut-checkbox-container'; checkboxContainer.style.display = 'none'; // Explicitly set to none initially // Create header with buttons const header = document.createElement('div'); header.className = 'cut-checkbox-header'; const showAllBtn = document.createElement('button'); showAllBtn.type = 'button'; showAllBtn.className = 'btn btn-sm'; showAllBtn.textContent = 'Show All'; showAllBtn.dataset.action = 'show-all'; const hideAllBtn = document.createElement('button'); hideAllBtn.type = 'button'; hideAllBtn.className = 'btn btn-sm'; hideAllBtn.textContent = 'Hide All'; hideAllBtn.dataset.action = 'hide-all'; header.appendChild(showAllBtn); header.appendChild(hideAllBtn); // Create list container const listContainer = document.createElement('div'); listContainer.className = 'cut-checkbox-list'; // Add checkbox items cuts.forEach(cut => { const normalized = normalizeCutData(cut); const isDisplayed = cutManager.isCutDisplayed(normalized.id); const item = document.createElement('div'); item.className = 'cut-checkbox-item'; item.dataset.cutId = normalized.id; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.checked = isDisplayed; checkbox.dataset.cutId = normalized.id; const colorBox = document.createElement('div'); colorBox.className = 'cut-color-box'; colorBox.style.backgroundColor = normalized.color; const nameSpan = document.createElement('span'); nameSpan.className = 'cut-name'; nameSpan.textContent = normalized.name; item.appendChild(checkbox); item.appendChild(colorBox); item.appendChild(nameSpan); if (normalized.is_official) { const badge = document.createElement('span'); badge.className = 'badge official'; badge.textContent = 'Official'; item.appendChild(badge); } listContainer.appendChild(item); }); checkboxContainer.appendChild(header); checkboxContainer.appendChild(listContainer); // Insert after selector cutSelector.parentNode.appendChild(checkboxContainer); // Setup event listeners using delegation setupCutSelectorEventListeners(checkboxContainer); console.log(`Populated cut selector with ${cuts.length} cuts`); } /** * Setup event listeners for cut selector dropdown */ function setupCutSelectorEventListeners(container) { // Store reference to current container currentDropdownContainer = container; // Handle action buttons and checkbox clicks container.addEventListener('click', async (e) => { e.stopPropagation(); // Prevent event bubbling // Handle action buttons if (e.target.matches('button[data-action]')) { const action = e.target.dataset.action; if (action === 'show-all') { await showAllCuts(); updateCheckboxStates(); updateMobileCheckboxStates(); } else if (action === 'hide-all') { await hideAllCuts(); updateCheckboxStates(); updateMobileCheckboxStates(); } return; } // Handle checkbox clicks if (e.target.matches('input[type="checkbox"]')) { const cutId = e.target.dataset.cutId; console.log('Checkbox clicked for cut ID:', cutId, 'checked:', e.target.checked); console.log('Available window.cuts:', window.cuts); const cut = window.cuts?.find(c => { const normalized = normalizeCutData(c); return String(normalized.id) === String(cutId); }); console.log('Found cut:', cut); if (cut) { const normalized = normalizeCutData(cut); console.log('Normalized cut ID:', normalized.id, 'Original cutId:', cutId); if (e.target.checked) { console.log('Attempting to display cut:', normalized.name); const displayResult = cutManager.displayCut(cut); console.log('Display result:', displayResult); } else { console.log('Attempting to hide cut:', normalized.name, 'with ID:', normalized.id); const hideResult = cutManager.hideCutById(normalized.id); // Use normalized.id instead of cutId console.log('Hide result:', hideResult); // Also try with string conversion in case of ID format mismatch if (!hideResult) { console.log('Trying to hide with string ID:', String(normalized.id)); const hideResult2 = cutManager.hideCutById(String(normalized.id)); console.log('Hide result (string ID):', hideResult2); } } updateMultipleCutsUI(); updateSelectorText(); updateMobileCheckboxStates(); // Update mobile checkboxes too } else { console.warn('Cut not found for ID:', cutId, 'Available cuts:', window.cuts?.length || 'undefined'); console.warn('Window.cuts content:', window.cuts); } return; } // Handle clicking on the item (not checkbox) to toggle if (e.target.closest('.cut-checkbox-item') && !e.target.matches('input')) { const item = e.target.closest('.cut-checkbox-item'); const checkbox = item.querySelector('input[type="checkbox"]'); if (checkbox) { checkbox.checked = !checkbox.checked; // Trigger the checkbox change event const clickEvent = new Event('click', { bubbles: true }); checkbox.dispatchEvent(clickEvent); } } }); // Only attach cutSelector event listener once if (!cutSelectorListenerAttached) { cutSelector.addEventListener('click', (e) => { // Use current active container const activeContainer = currentDropdownContainer; if (!activeContainer) return; // Always toggle based on current display state if (activeContainer.style.display === 'block') { activeContainer.style.display = 'none'; } else { activeContainer.style.display = 'block'; // Update checkbox states when opening dropdown to reflect current state updateCheckboxStates(); updateSelectorText(); } }); // Close dropdown when clicking outside document.addEventListener('click', (e) => { const activeContainer = currentDropdownContainer; if (activeContainer && !cutSelector.contains(e.target) && !activeContainer.contains(e.target)) { activeContainer.style.display = 'none'; } }); cutSelectorListenerAttached = true; } } /** * Update checkbox states to match current displayed cuts */ function updateCheckboxStates() { const checkboxes = document.querySelectorAll('.cut-checkbox-item input[type="checkbox"]'); checkboxes.forEach(cb => { const cutId = cb.dataset.cutId; const isDisplayed = cutManager.isCutDisplayed(cutId); cb.checked = isDisplayed; }); } /** * Update mobile checkbox states to match current displayed cuts */ function updateMobileCheckboxStates() { document.querySelectorAll('input[name="mobile-cut"]').forEach(cb => { const cutId = cb.dataset.cutId; cb.checked = cutManager.isCutDisplayed(cutId); }); } /** * Update selector text based on active cuts */ function updateSelectorText() { const activeCuts = cutManager.getDisplayedCuts(); if (activeCuts.length === 0) { cutSelector.textContent = 'Select map overlays...'; } else if (activeCuts.length === 1) { cutSelector.textContent = `📍 ${activeCuts[0].name}`; } else { cutSelector.textContent = `📍 ${activeCuts.length} overlays active`; } } /** * Show all cuts function */ async function showAllCuts() { if (window.cuts) { for (const cut of window.cuts) { const normalized = normalizeCutData(cut); if (!cutManager.isCutDisplayed(normalized.id)) { cutManager.displayCut(cut); console.log(`Displayed cut: ${normalized.name}`); } } updateMultipleCutsUI(); updateSelectorText(); console.log('All cuts shown'); } } /** * Hide all cuts function (updated) */ async function hideAllCuts() { cutManager.hideAllCuts(); updateMultipleCutsUI(); updateSelectorText(); console.log('All cuts hidden by user'); } /** * Handle cut selection change */ async function handleCutSelection(event) { const selectedValue = event.target.value; if (!selectedValue) { // Reset selector to show current state refreshSelectorUI(); return; } if (selectedValue === '__show_all__') { // Show all cuts const allCuts = window.cuts || []; for (const cut of allCuts) { const normalizedCut = normalizeCutData(cut); if (!cutManager.isCutDisplayed(normalizedCut.id)) { cutManager.displayCut(normalizedCut); console.log(`Displayed cut: ${normalizedCut.name}`); } } refreshSelectorUI(); updateLegendAndUI(); return; } if (selectedValue === '__hide_all__') { // Hide all cuts hideAllCuts(); return; } // Individual cut selection - toggle the cut const cut = window.cuts?.find(c => { const cutId = c.id || c.Id || c.ID; return cutId == selectedValue; }); if (cut) { const normalizedCut = normalizeCutData(cut); if (cutManager.isCutDisplayed(normalizedCut.id)) { cutManager.hideCutById(normalizedCut.id); console.log(`Hidden cut: ${normalizedCut.name}`); } else { cutManager.displayCut(normalizedCut); console.log(`Displayed cut: ${normalizedCut.name}`); } // Refresh the selector to show updated state refreshSelectorUI(); updateLegendAndUI(); } } /** * Refresh the selector UI to show current state */ function refreshSelectorUI() { // Re-populate the selector to update the icons if (window.cuts && window.cuts.length > 0) { populateCutSelector(window.cuts); } // Update the selector value based on displayed cuts const displayedCuts = cutManager.getDisplayedCuts(); if (displayedCuts.length === 0) { // No cuts displayed - reset to placeholder cutSelector.value = ''; } else if (displayedCuts.length === 1) { // Single cut displayed cutSelector.value = displayedCuts[0].id; } else { // Multiple cuts displayed - show count in placeholder cutSelector.value = ''; setTimeout(() => { const firstOption = cutSelector.querySelector('option[value=""]'); if (firstOption) { firstOption.textContent = `${displayedCuts.length} cuts active - Select actions...`; } }, 0); } } /** * Update legend and UI components */ function updateLegendAndUI() { const activeCuts = cutManager.getDisplayedCuts(); if (activeCuts.length > 0) { showMultipleCutsLegend(activeCuts); updateCutControlsUI(activeCuts); console.log(`Active cuts: ${activeCuts.map(c => c.name || 'Unnamed').join(', ')}`); } else { hideCutLegend(); updateCutControlsUI([]); console.log('No cuts currently displayed'); } } /** * Show cut legend with cut information */ function showCutLegend(cutData) { if (!cutLegend) return; // Update legend content const legendColor = document.getElementById('legend-color'); const legendName = document.getElementById('legend-name'); const legendDescription = document.getElementById('legend-description'); // Handle different possible field names const color = cutData.color || cutData.Color || '#3388ff'; const opacity = cutData.opacity || cutData.Opacity || 0.3; const name = cutData.name || cutData.Name || 'Unnamed Cut'; const description = cutData.description || cutData.Description || ''; if (legendColor) { legendColor.style.backgroundColor = color; legendColor.style.opacity = opacity; } if (legendName) { legendName.textContent = name; } if (legendDescription) { legendDescription.textContent = description; legendDescription.style.display = description ? 'block' : 'none'; } // Show legend cutLegend.classList.add('visible'); // Auto-expand legend content for first-time users if (!legendExpanded) { expandLegend(); } } /** * Hide cut legend */ function hideCutLegend() { if (!cutLegend) return; cutLegend.classList.remove('visible'); collapseLegend(); } /** * Toggle cut legend expansion */ export function toggleCutLegend() { if (legendExpanded) { collapseLegend(); } else { expandLegend(); } } /** * Expand legend content */ function expandLegend() { if (!cutLegendContent) return; cutLegendContent.classList.add('expanded'); legendExpanded = true; // Update toggle indicator const toggle = cutLegend.querySelector('.legend-toggle'); if (toggle) { toggle.textContent = '▲'; } } /** * Collapse legend content */ function collapseLegend() { if (!cutLegendContent) return; cutLegendContent.classList.remove('expanded'); legendExpanded = false; // Update toggle indicator const toggle = cutLegend.querySelector('.legend-toggle'); if (toggle) { toggle.textContent = '▼'; } } /** * Refresh cut controls (e.g., when new cuts are added) */ export async function refreshCutControls() { await loadAndPopulateCuts(); } /** * Get currently selected cut ID */ export function getCurrentCutSelection() { return cutSelector ? cutSelector.value : null; } /** * Set cut selection programmatically */ export function setCutSelection(cutId) { if (!cutSelector) return false; cutSelector.value = cutId; cutSelector.dispatchEvent(new Event('change')); // Also update mobile selection const mobileRadio = document.querySelector(`input[name="mobile-overlay"][value="${cutId || ''}"]`); if (mobileRadio) { mobileRadio.checked = true; updateMobileOverlayInfo(cutId); } return true; } /** * Populate mobile overlay options with multi-select support */ function populateMobileOverlayOptions(cuts) { console.log('populateMobileOverlayOptions called with', cuts?.length, 'cuts'); const container = document.getElementById('mobile-overlay-list'); // Changed from mobile-overlay-options if (!container) { console.warn('Mobile overlay list container not found'); return; } console.log('Mobile overlay list container found'); container.innerHTML = ''; if (!cuts || cuts.length === 0) { container.innerHTML = '
No cuts available
'; return; } // Create checkboxes for each cut cuts.forEach(cut => { const cutId = cut.id || cut.Id || cut.ID; const cutName = cut.name || cut.Name || 'Unnamed Cut'; const cutDescription = cut.description || cut.Description; const cutColor = cut.color || cut.Color || '#3388ff'; const cutCategory = cut.category || cut.Category || 'Other'; const isDisplayed = cutManager.isCutDisplayed(cutId); const optionDiv = document.createElement('div'); optionDiv.className = 'overlay-option'; const label = document.createElement('label'); label.className = 'cut-checkbox-label'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.name = 'mobile-cut'; checkbox.value = cutId; checkbox.checked = isDisplayed; checkbox.dataset.cutId = cutId; const colorIndicator = document.createElement('span'); colorIndicator.className = 'cut-color-indicator'; colorIndicator.style.backgroundColor = cutColor; const cutInfo = document.createElement('div'); cutInfo.className = 'cut-info'; cutInfo.innerHTML = `
${cutName}
${cutCategory}
${cutDescription ? `
${cutDescription}
` : ''} `; label.appendChild(checkbox); label.appendChild(colorIndicator); label.appendChild(cutInfo); optionDiv.appendChild(label); container.appendChild(optionDiv); }); // Update the active overlays info updateMobileActiveOverlaysInfo(); console.log('Finished populating mobile overlay options with multi-select'); } /** * Open mobile overlay modal */ function openMobileOverlayModal() { console.log('openMobileOverlayModal called'); console.log('mobileOverlayModal exists:', !!mobileOverlayModal); if (!mobileOverlayModal) { console.error('Mobile overlay modal not found!'); return; } // Refresh the mobile options to show current state if (window.cuts && window.cuts.length > 0) { populateMobileOverlayOptions(window.cuts); } // Show modal mobileOverlayModal.classList.remove('hidden'); console.log('Mobile overlay modal opened with multi-select support'); } /** * Close mobile overlay modal */ function closeMobileOverlayModal() { if (!mobileOverlayModal) return; mobileOverlayModal.classList.add('hidden'); // Remove button active state if (mobileOverlayBtn) { mobileOverlayBtn.classList.remove('active'); } } /** * Setup mobile overlay event listeners */ function setupMobileOverlayEventListeners() { // Event delegation for mobile overlay modal mobileOverlayModal.addEventListener('change', (e) => { if (e.target.matches('input[type="checkbox"][name="mobile-cut"]')) { const cutId = e.target.dataset.cutId; handleMobileCutToggle(cutId); } }); // Handle click events for buttons in mobile overlay mobileOverlayModal.addEventListener('click', (e) => { if (e.target.matches('.btn[data-action="show-all"]')) { showAllCutsMobile(); } else if (e.target.matches('.btn[data-action="hide-all"]')) { hideAllCutsMobile(); } else if (e.target.matches('button[data-action="close-modal"]')) { closeMobileOverlayModal(); } }); } /** * Handle mobile cut toggle (checkbox) */ function handleMobileCutToggle(cutId) { console.log('Mobile cut toggle:', cutId); // Find the checkbox that triggered this event - fix the selector const checkbox = document.querySelector(`input[name="mobile-cut"][data-cut-id="${cutId}"]`); if (!checkbox) { console.warn('Mobile checkbox not found for cutId:', cutId); console.warn('Available mobile checkboxes:', document.querySelectorAll('input[name="mobile-cut"]')); return; } const isChecked = checkbox.checked; console.log('Mobile checkbox checked state:', isChecked); const cut = window.cuts?.find(c => { const id = c.id || c.Id || c.ID; return id == cutId; }); if (cut) { const normalizedCut = normalizeCutData(cut); console.log('Mobile toggle - normalized cut ID:', normalizedCut.id, 'original cutId:', cutId); if (isChecked) { // Checkbox is checked - show the cut if (!cutManager.isCutDisplayed(cutId)) { cutManager.displayCut(normalizedCut); console.log(`Mobile: Displayed cut: ${normalizedCut.name}`); } } else { // Checkbox is unchecked - hide the cut console.log(`Mobile: Attempting to hide cut: ${normalizedCut.name} with ID: ${normalizedCut.id}`); // Try different ID formats to ensure the hide works let hideResult = cutManager.hideCutById(normalizedCut.id); if (!hideResult) { hideResult = cutManager.hideCutById(String(normalizedCut.id)); } if (!hideResult) { hideResult = cutManager.hideCutById(Number(normalizedCut.id)); } console.log(`Mobile: Hide result: ${hideResult}`); if (hideResult) { console.log(`Mobile: Successfully hidden cut: ${normalizedCut.name}`); } else { console.error(`Mobile: Failed to hide cut: ${normalizedCut.name} with ID: ${normalizedCut.id}`); } } // Update mobile UI updateMobileActiveOverlaysInfo(); // Update desktop UI as well updateCheckboxStates(); // Sync desktop checkboxes updateSelectorText(); updateMultipleCutsUI(); } else { console.warn('Mobile: Cut not found for ID:', cutId); } } /** * Handle mobile overlay selection (legacy - for compatibility) */ function handleMobileOverlaySelection(cutId) { // Redirect to new toggle function handleMobileCutToggle(cutId); } /** * Show all cuts (mobile) */ function showAllCutsMobile() { const allCuts = window.cuts || []; for (const cut of allCuts) { const normalizedCut = normalizeCutData(cut); if (!cutManager.isCutDisplayed(normalizedCut.id)) { cutManager.displayCut(normalizedCut); } } // Update all mobile checkboxes const checkboxes = document.querySelectorAll('input[name="mobile-cut"]'); checkboxes.forEach(cb => cb.checked = true); // Update desktop checkboxes too updateCheckboxStates(); updateMobileActiveOverlaysInfo(); updateSelectorText(); updateMultipleCutsUI(); console.log('Mobile: All cuts shown'); } /** * Hide all cuts (mobile) */ function hideAllCutsMobile() { cutManager.hideAllCuts(); // Update all mobile checkboxes const checkboxes = document.querySelectorAll('input[name="mobile-cut"]'); checkboxes.forEach(cb => cb.checked = false); // Update desktop checkboxes too updateCheckboxStates(); updateMobileActiveOverlaysInfo(); updateSelectorText(); updateMultipleCutsUI(); console.log('Mobile: All cuts hidden'); } /** * Update mobile overlay info display */ async function updateMobileOverlayInfo(cutId) { const infoContainer = document.getElementById('mobile-current-overlay-info'); const colorDiv = document.getElementById('mobile-overlay-color'); const nameDiv = document.getElementById('mobile-overlay-name'); const descDiv = document.getElementById('mobile-overlay-description'); if (!infoContainer || !colorDiv || !nameDiv || !descDiv) return; if (!cutId) { infoContainer.style.display = 'none'; return; } try { const response = await fetch(`/api/cuts/public`); const data = await response.json(); const cut = data.list?.find(c => c.id == cutId); if (cut) { colorDiv.style.backgroundColor = cut.color || '#3388ff'; nameDiv.textContent = cut.name; descDiv.textContent = cut.description || 'No description available'; infoContainer.style.display = 'block'; } else { infoContainer.style.display = 'none'; } } catch (error) { console.error('Error fetching cut info:', error); infoContainer.style.display = 'none'; } } /** * Update mobile active overlays info */ function updateMobileActiveOverlaysInfo() { const activeCountEl = document.getElementById('mobile-active-count'); const overlayListEl = document.getElementById('mobile-overlay-list'); if (!activeCountEl || !overlayListEl) return; const activeCuts = cutManager.getDisplayedCuts(); activeCountEl.textContent = `(${activeCuts.length})`; if (activeCuts.length === 0) { overlayListEl.innerHTML = '
No active overlays
'; return; } overlayListEl.innerHTML = activeCuts.map(cut => { const cutColor = cut.color || cut.Color || '#3388ff'; const cutName = cut.name || cut.Name || 'Unnamed Cut'; const cutDescription = cut.description || cut.Description; return `
${cutName}
${cutDescription ? `
${cutDescription}
` : ''}
`; }).join(''); } /** * Update UI controls to show multiple active cuts */ function updateCutControlsUI(activeCuts) { if (!cutSelector || !activeCuts || activeCuts.length === 0) return; // Update selector to show multiple active cuts const allCuts = window.cuts || []; cutSelector.innerHTML = ` ${allCuts.map(cut => { // Normalize field names const cutId = cut.id || cut.Id || cut.ID; const cutName = cut.name || cut.Name || 'Unnamed Cut'; const isActive = cutManager.isCutDisplayed(cutId); return ``; }).join('')} `; } /** * Show legend for multiple active cuts */ function showMultipleCutsLegend(activeCuts) { if (!cutLegend || !cutLegendContent) return; cutLegend.style.display = 'block'; // Create header const header = document.createElement('div'); header.className = 'legend-header'; const title = document.createElement('h3'); title.textContent = `📍 Active Cut Overlays (${activeCuts.length})`; const toggleBtn = document.createElement('button'); toggleBtn.className = 'legend-toggle'; toggleBtn.textContent = '▼'; toggleBtn.dataset.action = 'toggle-legend'; header.appendChild(title); header.appendChild(toggleBtn); // Create content container const content = document.createElement('div'); content.className = 'legend-content'; // Add cut items activeCuts.forEach(cut => { const cutId = cut.id || cut.Id || cut.ID; const cutName = cut.name || cut.Name || 'Unnamed Cut'; const cutDescription = cut.description || cut.Description; const cutColor = cut.color || cut.Color || '#3388ff'; const item = document.createElement('div'); item.className = 'cut-legend-item'; item.dataset.cutId = cutId; const colorSpan = document.createElement('span'); colorSpan.className = 'overlay-color'; colorSpan.style.backgroundColor = cutColor; const details = document.createElement('div'); details.className = 'overlay-details'; const nameDiv = document.createElement('div'); nameDiv.className = 'overlay-name'; nameDiv.textContent = cutName; details.appendChild(nameDiv); if (cutDescription) { const descDiv = document.createElement('div'); descDiv.className = 'overlay-description'; descDiv.textContent = cutDescription; details.appendChild(descDiv); } const toggleBtn = document.createElement('button'); toggleBtn.className = 'cut-toggle-btn'; toggleBtn.title = 'Toggle visibility'; toggleBtn.textContent = '👁️'; toggleBtn.dataset.cutId = cutId; toggleBtn.dataset.action = 'toggle-cut'; item.appendChild(colorSpan); item.appendChild(details); item.appendChild(toggleBtn); content.appendChild(item); }); // Add actions const actions = document.createElement('div'); actions.className = 'legend-actions'; const hideAllBtn = document.createElement('button'); hideAllBtn.className = 'btn btn-sm'; hideAllBtn.textContent = 'Hide All'; hideAllBtn.dataset.action = 'hide-all'; actions.appendChild(hideAllBtn); content.appendChild(actions); // Clear and rebuild content cutLegendContent.innerHTML = ''; cutLegendContent.appendChild(header); cutLegendContent.appendChild(content); // Setup event listeners for this legend setupLegendEventListeners(); } /** * Setup legend event listeners */ function setupLegendEventListeners() { if (!cutLegend) return; // Remove existing listeners to prevent duplicates const clonedLegend = cutLegend.cloneNode(true); cutLegend.parentNode.replaceChild(clonedLegend, cutLegend); cutLegend = clonedLegend; cutLegendContent = cutLegend.querySelector('#cut-legend-content'); // Add event delegation for legend cutLegend.addEventListener('click', (e) => { if (e.target.matches('button[data-action="toggle-legend"]')) { toggleCutLegend(); } else if (e.target.matches('button[data-action="toggle-cut"]')) { const cutId = e.target.dataset.cutId; toggleIndividualCut(cutId); } else if (e.target.matches('button[data-action="hide-all"]')) { hideAllCuts(); } }); } /** * Toggle individual cut visibility */ function toggleIndividualCut(cutId) { if (cutManager.isCutDisplayed(cutId)) { cutManager.hideCutById(cutId); console.log(`Hidden cut ID: ${cutId}`); } else { const cut = window.cuts?.find(c => { const id = c.id || c.Id || c.ID; return id == cutId; }); if (cut) { const normalizedCut = normalizeCutData(cut); cutManager.displayCut(normalizedCut); console.log(`Displayed cut: ${normalizedCut.name}`); } } // Refresh the UI const activeCuts = cutManager.getDisplayedCuts(); if (activeCuts.length > 0) { showMultipleCutsLegend(activeCuts); updateCutControlsUI(activeCuts); } else { hideCutLegend(); updateCutControlsUI([]); } } /** * Toggle display of individual cut */ window.toggleCutDisplay = async function(cutId) { const cut = window.cuts?.find(c => { const normalized = normalizeCutData(c); return normalized.id == cutId; }); if (cut) { const normalized = normalizeCutData(cut); if (cutManager.isCutDisplayed(cutId)) { cutManager.hideCutById(cutId); } else { cutManager.displayCut(normalized); } updateMultipleCutsUI(); } }; /** * Show all cuts */ window.showAllCuts = async function() { await showAllCuts(); updateCheckboxStates(); }; /** * Hide all cuts */ window.hideAllCuts = async function() { await hideAllCuts(); updateCheckboxStates(); }; /** * Update UI for multiple active cuts */ function updateMultipleCutsUI() { const activeCuts = cutManager.getDisplayedCuts(); // Update selector text - now using button instead of select if (cutSelector) { if (activeCuts.length === 0) { cutSelector.textContent = 'Select map overlays...'; } else if (activeCuts.length === 1) { cutSelector.textContent = `📍 ${activeCuts[0].name}`; } else { cutSelector.textContent = `📍 ${activeCuts.length} overlays active`; } } // Update legend for multiple cuts if (activeCuts.length > 0) { showMultipleCutsLegend(activeCuts); } else { hideCutLegend(); } } /** * Make functions available globally for external access and backwards compatibility */ window.toggleCutDisplay = async function(cutId) { const cut = window.cuts?.find(c => { const normalized = normalizeCutData(c); return String(normalized.id) === String(cutId); }); if (cut) { if (cutManager.isCutDisplayed(cutId)) { cutManager.hideCutById(cutId); } else { cutManager.displayCut(cut); } updateMultipleCutsUI(); updateSelectorText(); } }; // Assign other functions to window for global access window.showAllCutsMobile = showAllCutsMobile; window.hideAllCutsMobile = hideAllCutsMobile; window.toggleCutLegend = toggleCutLegend; window.toggleIndividualCut = toggleIndividualCut; window.handleMobileCutToggle = handleMobileCutToggle; window.openMobileOverlayModal = function() { if (mobileOverlayModal) { // Populate mobile overlay list populateMobileOverlayOptions(window.cuts || []); mobileOverlayModal.classList.remove('hidden'); } }; window.closeMobileOverlayModal = function() { if (mobileOverlayModal) { mobileOverlayModal.classList.add('hidden'); } }; // Debug: Log that functions are available console.log('Cut controls functions made global:', { toggleCutDisplay: typeof window.toggleCutDisplay, showAllCuts: typeof window.showAllCuts, hideAllCuts: typeof window.hideAllCuts, openMobileOverlayModal: typeof window.openMobileOverlayModal, closeMobileOverlayModal: typeof window.closeMobileOverlayModal, toggleCutLegend: typeof window.toggleCutLegend });