1290 lines
41 KiB
JavaScript
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
|
|
});
|