freealberta/map/app/public/js/cut-controls.js

1290 lines
41 KiB
JavaScript

/**
* 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 = '<div class="no-cuts">No cuts available</div>';
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 = `
<div class="cut-name">${cutName}</div>
<div class="cut-category">${cutCategory}</div>
${cutDescription ? `<div class="cut-description">${cutDescription}</div>` : ''}
`;
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 = '<div class="no-active-cuts">No active overlays</div>';
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 `
<div class="active-overlay-item">
<span class="overlay-color" style="background-color: ${cutColor}"></span>
<div class="overlay-details">
<div class="overlay-name">${cutName}</div>
${cutDescription ? `<div class="overlay-description">${cutDescription}</div>` : ''}
</div>
</div>
`;
}).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 = `
<option value="">Select Cut Overlay...</option>
<option value="__multiple__" selected>📍 ${activeCuts.length} Active Cuts</option>
${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 `<option value="${cutId}" ${isActive ? 'data-active="true"' : ''}>${cutName}</option>`;
}).join('')}
<option value="none">🚫 Hide All Cuts</option>
`;
}
/**
* 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
});