document.addEventListener('DOMContentLoaded', function () { /* ---------------- 1. Modal HTML ---------------- */ const modalHTML = `

Know Your Warning

×
Current Storm Location Nearest Storm from your Location Nowcast Warning Next 5 Days Warning
Awaiting input...
Awaiting input...
Awaiting input...
Awaiting input...
`; /* ---------------- 2. CSS ---------------- */ const style = document.createElement('style'); style.innerHTML = ` .kyw-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.7); z-index: 99999; justify-content: center; align-items: center; font-family: Arial, sans-serif; } .kyw-modal { background: #fff; width: 90%; max-width: 1000px; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.5); overflow: hidden; display: flex; flex-direction: column; max-height: 90vh; position: relative; } .kyw-header { background: #0056b3; color: #fff; padding: 15px 20px; display: flex; justify-content: space-between; align-items: center; cursor: grab; user-select: none; touch-action: none; } .kyw-header:active { cursor: grabbing; } .kyw-header h2 { margin: 0; font-size: 1.2rem; pointer-events: none; } .kyw-close { font-size: 24px; cursor: pointer; font-weight: bold; pointer-events: auto; padding: 0 6px; } .kyw-close:hover { color: #ffcccc; } .kyw-input-section { padding: 20px; display: flex; flex-wrap: wrap; gap: 15px; background: #f8f9fa; border-bottom: 1px solid #ddd; align-items: flex-end; } .kyw-input-group { display: flex; flex-direction: column; flex: 1; min-width: 150px; } .kyw-input-group label { font-size: 0.85rem; font-weight: bold; margin-bottom: 5px; color: #333; } .kyw-input-group input { padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 0.9rem; } .kyw-input-group input.kyw-invalid { border-color: #c00; background: #fff5f5; } .kyw-input-group input:disabled, .kyw-input-group input.kyw-disabled { background: #ececec; color: #888; cursor: not-allowed; border-color: #ddd; } .kyw-btn { padding: 9px 20px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; height: 38px; } .kyw-btn:hover { background: #218838; } .kyw-btn:disabled { background: #8bc39a; cursor: default; } .kyw-resolved { padding: 8px 20px; background: #eef6ff; border-bottom: 1px solid #cfe2f7; font-size: 12.5px; color: #124a85; } .kyw-results-container { padding: 20px; overflow-x: auto; overflow-y: auto; } .kyw-table { width: 100%; border-collapse: collapse; table-layout: fixed; } .kyw-table th, .kyw-table td { border: 2px solid #0056b3; padding: 15px; text-align: left; vertical-align: top; height: 200px; } .kyw-table th { background: #e9ecef; text-align: center; height: auto; font-size: 0.9rem; padding: 10px; } .kyw-placeholder { color: #999; font-style: italic; font-size: 0.9rem; text-align: center; margin-top: 80px; } .kyw-modal.kyw-dragging { transition: none; box-shadow: 0 12px 40px rgba(0,0,0,0.6); } .kyw-day-row { display: flex; align-items: flex-start; gap: 8px; margin-bottom: 8px; } .kyw-dot { flex: 0 0 14px; width: 14px; height: 14px; border-radius: 50%; border: 1px solid #555; margin-top: 3px; } `; document.head.appendChild(style); document.body.insertAdjacentHTML('beforeend', modalHTML); /* ---------------- 3. Open / Close ---------------- */ const modal = document.getElementById('kyw-modal-overlay'); const modalBox = document.getElementById('kyw-modal'); const dragHandle = document.getElementById('kyw-drag-handle'); const closeBtn = modal.querySelector('.kyw-close'); const submitBtn = document.getElementById('kyw-submit-btn'); const trigger = document.getElementById('KNOWYOURWARNING'); const resolvedEl = document.getElementById('kyw-resolved'); if (trigger) { trigger.addEventListener('click', function (e) { e.preventDefault(); modal.style.display = 'flex'; modalBox.style.left = ''; modalBox.style.top = ''; modalBox.style.margin = ''; }); } else { console.warn('[KYW] Button #KNOWYOURWARNING not found in DOM.'); } closeBtn.addEventListener('click', function () { modal.style.display = 'none'; }); /* ---------------- 4. Draggable Modal ---------------- */ let isDragging = false; let startX = 0, startY = 0, startLeft = 0, startTop = 0; function onPointerDown(e) { if (e.target.closest('.kyw-close')) return; isDragging = true; const rect = modalBox.getBoundingClientRect(); modalBox.style.position = 'fixed'; modalBox.style.margin = '0'; modalBox.style.left = rect.left + 'px'; modalBox.style.top = rect.top + 'px'; modalBox.style.right = 'auto'; modalBox.style.bottom = 'auto'; modalBox.classList.add('kyw-dragging'); startX = e.clientX; startY = e.clientY; startLeft = rect.left; startTop = rect.top; if (e.pointerId !== undefined && dragHandle.setPointerCapture) { try { dragHandle.setPointerCapture(e.pointerId); } catch (err) {} } document.body.style.userSelect = 'none'; e.preventDefault(); } function onPointerMove(e) { if (!isDragging) return; const dx = e.clientX - startX; const dy = e.clientY - startY; let newLeft = startLeft + dx; let newTop = startTop + dy; const vw = window.innerWidth; const vh = window.innerHeight; const pw = modalBox.offsetWidth; newLeft = Math.max(-pw + 60, Math.min(vw - 60, newLeft)); newTop = Math.max(0, Math.min(vh - 40, newTop)); modalBox.style.left = newLeft + 'px'; modalBox.style.top = newTop + 'px'; e.preventDefault(); } function onPointerUp(e) { if (!isDragging) return; isDragging = false; modalBox.classList.remove('kyw-dragging'); document.body.style.userSelect = ''; if (e.pointerId !== undefined && dragHandle.releasePointerCapture) { try { dragHandle.releasePointerCapture(e.pointerId); } catch (err) {} } } dragHandle.addEventListener('pointerdown', onPointerDown); dragHandle.addEventListener('pointermove', onPointerMove); dragHandle.addEventListener('pointerup', onPointerUp); dragHandle.addEventListener('pointercancel', onPointerUp); if (!window.PointerEvent) { dragHandle.addEventListener('mousedown', function (e) { onPointerDown(e); function mm(ev) { onPointerMove(ev); } function mu(ev) { onPointerUp(ev); document.removeEventListener('mousemove', mm); document.removeEventListener('mouseup', mu); } document.addEventListener('mousemove', mm); document.addEventListener('mouseup', mu); }); } /* ============================================================== * 5. MAP MARKER HELPERS * ============================================================== */ const KYW_PAN_MAP = true; let kywUserMarker = null; function kywFindMap() { function looksLikeLeafletMap(m) { return !!m && typeof m.addLayer === 'function' && typeof m.removeLayer === 'function' && typeof m.getZoom === 'function' && typeof m.setView === 'function' && !!m._container; } try { if (looksLikeLeafletMap(window.KYW_MAP)) return window.KYW_MAP; } catch (e) {} const names = ['map', 'myMap', 'mymap', 'leafletMap', 'mainMap', 'gisMap', 'baseMap', 'mapObj', 'leaflet_map']; for (let i = 0; i < names.length; i++) { try { if (looksLikeLeafletMap(window[names[i]])) return window[names[i]]; } catch (e) {} } try { if (typeof map !== 'undefined' && looksLikeLeafletMap(map)) return map; } catch (e) {} try { for (const k in window) { try { if (looksLikeLeafletMap(window[k])) return window[k]; } catch (e) {} } } catch (e) {} return null; } function kywShowMarker(lat, lng) { const la = parseFloat(lat); const ln = parseFloat(lng); if (!isFinite(la) || !isFinite(ln)) { console.warn('[KYW] Invalid coordinates for marker:', lat, lng); return false; } const m = kywFindMap(); if (!m) { console.warn('[KYW] Map instance not found — marker was not added.'); return false; } const L = window.L; if (!L || typeof L.marker !== 'function') { console.warn('[KYW] Leaflet (window.L) not found — marker was not added.'); return false; } try { if (kywUserMarker && kywUserMarker._map) kywUserMarker._map.removeLayer(kywUserMarker); else if (kywUserMarker && typeof m.removeLayer === 'function') m.removeLayer(kywUserMarker); } catch (e) {} try { kywUserMarker = L.marker([la, ln], { title: 'Your location', zIndexOffset: 1000 }).addTo(m); kywUserMarker.bindPopup( 'Your location
' + la.toFixed(4) + '°N, ' + ln.toFixed(4) + '°E' ); if (KYW_PAN_MAP) { if (typeof m.panTo === 'function') m.panTo([la, ln]); else m.setView([la, ln], m.getZoom()); } return true; } catch (e) { console.warn('[KYW] Could not place marker:', e); return false; } } window.kywShowMarker = kywShowMarker; /* ---------------- 6. Inputs ---------------- */ const latEl = document.getElementById('kyw-lat'); const lngEl = document.getElementById('kyw-lng'); const pinEl = document.getElementById('kyw-pincode'); const locEl = document.getElementById('kyw-location'); function updateInputStates() { const hasLatLng = latEl.value.trim() !== '' || lngEl.value.trim() !== ''; const hasPin = pinEl.value.trim() !== ''; const hasLoc = locEl.value.trim() !== ''; let mode = null; if (hasLatLng) mode = 'latlng'; else if (hasPin) mode = 'pin'; else if (hasLoc) mode = 'loc'; const lockLatLng = (mode === 'pin' || mode === 'loc'); const lockPin = (mode === 'latlng' || mode === 'loc'); const lockLoc = (mode === 'latlng' || mode === 'pin'); latEl.disabled = lockLatLng; lngEl.disabled = lockLatLng; pinEl.disabled = lockPin; locEl.disabled = lockLoc; [latEl, lngEl, pinEl, locEl].forEach(el => { el.classList.toggle('kyw-disabled', el.disabled); }); } pinEl.addEventListener('input', function () { this.value = this.value.replace(/\D/g, '').slice(0, 6); this.classList.remove('kyw-invalid'); updateInputStates(); }); [latEl, lngEl, locEl].forEach(el => el.addEventListener('input', () => { el.classList.remove('kyw-invalid'); updateInputStates(); })); [latEl, lngEl, pinEl, locEl].forEach(el => el.addEventListener('change', updateInputStates)); [latEl, lngEl, pinEl, locEl].forEach(el => el.addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); submitBtn.click(); } })); updateInputStates(); /* ---------------- 7. Submit → fetch live data ---------------- */ submitBtn.addEventListener('click', function () { const latStr = latEl.value.trim(); const lngStr = lngEl.value.trim(); const pincode = pinEl.value.trim(); const location = locEl.value.trim(); let lat = null, lng = null; if (latStr !== '' || lngStr !== '') { if (latStr === '' || lngStr === '') { alert('Please enter both Latitude and Longitude.'); return; } lat = parseFloat(latStr); lng = parseFloat(lngStr); if (!isFinite(lat) || lat < -90 || lat > 90) { latEl.classList.add('kyw-invalid'); alert('Latitude must be a number between -90 and 90.'); return; } if (!isFinite(lng) || lng < -180 || lng > 180) { lngEl.classList.add('kyw-invalid'); alert('Longitude must be a number between -180 and 180.'); return; } } if (lat === null && pincode !== '' && !/^[1-9][0-9]{5}$/.test(pincode)) { pinEl.classList.add('kyw-invalid'); alert('Please enter a valid 6-digit Indian pincode.'); return; } if (lat === null && pincode === '' && location === '') { alert('Please enter Latitude & Longitude, or a Pincode, or a Location.'); return; } resolvedEl.style.display = 'none'; ['kyw-col-1','kyw-col-2','kyw-col-3','kyw-col-4'].forEach(id => { document.getElementById(id).innerHTML = '
Loading data...
'; }); const params = new URLSearchParams({ api: '1' }); if (lat !== null) params.set('lat', lat); if (lng !== null) params.set('lng', lng); if (pincode) params.set('pincode', pincode); if (location) params.set('location', location); submitBtn.disabled = true; fetch('knowYourWarning.php?' + params.toString(), { cache: 'no-store' }) .then(res => { if (!res.ok) throw new Error('HTTP ' + res.status); return res.json(); }) .then(data => { if (data.error) throw new Error(data.error); const inLat = (data.input && data.input.lat !== null) ? data.input.lat : (lat !== null ? lat : null); const inLng = (data.input && data.input.lng !== null) ? data.input.lng : (lng !== null ? lng : null); if (inLat !== null && inLng !== null) { kywShowMarker(inLat, inLng); } else { console.warn('[KYW] Could not resolve coordinates for the marker.'); } if (data.input && data.input.resolved) { resolvedEl.innerHTML = 'Showing results for: ' + escapeHtml(data.input.resolved); resolvedEl.style.display = 'block'; } renderKWY(data); }) .catch(err => { console.error('[KYW] fetch failed:', err); ['kyw-col-1','kyw-col-2','kyw-col-3','kyw-col-4'].forEach(id => { document.getElementById(id).innerHTML = '
' + 'Error: ' + escapeHtml(err.message) + '
'; }); }) .finally(() => { submitBtn.disabled = false; }); }); /* ---------------- 8. Renderer ---------------- */ function renderKWY(data) { const cur = data.current_storm; if (cur && !cur.error) { document.getElementById('kyw-col-1').innerHTML = `
${escapeHtml(cur.cyclone)}
Category: ${escapeHtml(cur.category)}
Position: ${cur.lat}°N, ${cur.lng}°E
Date/Time: ${escapeHtml(cur.datetime)}
Hour: ${escapeHtml(String(cur.hour))}
MSW: ${escapeHtml(String(cur.msw_kt))} kt (${escapeHtml(String(cur.msw_kmph))} kmph)
MSW Range: ${escapeHtml(String(cur.msw_range))} kmph
`; } else { document.getElementById('kyw-col-1').innerHTML = '
No current storm data available.
'; } const near = data.nearest_storm; if (near && !near.error) { document.getElementById('kyw-col-2').innerHTML = `
${escapeHtml(near.cyclone)}
Distance: ${near.distance_km} km from your location
Nearest position: ${near.lat}°N, ${near.lng}°E
Category: ${escapeHtml(near.category)}
Date/Time: ${escapeHtml(near.datetime)}
Hour: ${escapeHtml(String(near.hour))}
MSW: ${escapeHtml(String(near.msw_kt))} kt (${escapeHtml(String(near.msw_kmph))} kmph)
`; } else { document.getElementById('kyw-col-2').innerHTML = '
' + escapeHtml(near && near.error ? near.error : 'No nearest-storm data available.') + '
'; } const nc = data.nowcast_warning; if (nc) { const colorMap = { 'Red': '#dc3545', 'Orange': '#fd7e14', 'Yellow': '#ffc107', 'Green': '#28a745' }; const color = colorMap[nc.level] || nc.color || '#999999'; const badgeTxt = (nc.level === 'Yellow') ? '#000' : '#fff'; const badgeText = ['Red', 'Orange', 'Yellow'].includes(nc.level) ? nc.level + ' Warning' : (nc.level === 'Green' ? 'Green (No Warning)' : nc.level); const detailsHtml = (nc.details && nc.details.length) ? nc.details.map(l => `
${escapeHtml(l)}
`).join('') : `
${escapeHtml(nc.message || '')}
`; document.getElementById('kyw-col-3').innerHTML = `
${nc.district ? `
${escapeHtml(nc.district)}
` : ''}
${escapeHtml(badgeText)}
${detailsHtml} ${nc.issued ? `
Issued: ${escapeHtml(nc.issued)}
` : ''} ${nc.valid_upto ? `
Valid upto: ${escapeHtml(nc.valid_upto)}
` : ''}
Source: ${escapeHtml(nc.source || 'IMD Nowcast')}
`; } else { document.getElementById('kyw-col-3').innerHTML = '
No nowcast warning available.
'; } const n5 = data.next_5_days; if (Array.isArray(n5) && n5.length) { const rows = n5.map(d => `
${escapeHtml(d.day)}: ${escapeHtml(d.warning)}
`).join(''); document.getElementById('kyw-col-4').innerHTML = `
${rows}
`; } else { document.getElementById('kyw-col-4').innerHTML = '
No 5-day forecast available.
'; } } function escapeHtml(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c])); } }); /* ========================================================================= FLOATING ACTIVE LAYERS PANEL (v3) - Captures EVERY L.Control.PanelLayers created on the page, including: * District Warning * QPF Warning * Storm Surge * Flash Flood Guidance * Nowcast Warning This is done by wrapping PanelLayers.prototype.initialize BEFORE the marine page creates any panel. (knowYourWarning.php loads first.) - Properly unchecks the ORIGINAL leaflet-panel-layers checkboxes when a layer is unticked inside the floating panel. - Persistent toggle button so the panel can always be reopened. ========================================================================= */ /* ------------------------------------------------------------------ * PANELLAYERS HOOK — install before any panel is created * ------------------------------------------------------------------ */ (function () { if (window.__kywPanelLayersHooked) return; function install() { if (typeof L === 'undefined' || !L.Control || !L.Control.PanelLayers || !L.Control.PanelLayers.prototype) { return false; } if (window.__kywPanelLayersHooked) return true; window.__kywPanelLayersHooked = true; window.__kywCapturedPanelOverlays = window.__kywCapturedPanelOverlays || []; var origInit = L.Control.PanelLayers.prototype.initialize; L.Control.PanelLayers.prototype.initialize = function (baseLayers, overlayers, options) { try { if (Array.isArray(overlayers)) { overlayers.forEach(function (group) { if (group && group.group && Array.isArray(group.layers)) { // Keep a reference; layers may be added/removed from // the map later, but the group config stays the same. window.__kywCapturedPanelOverlays.push(group); } }); } } catch (e) { /* never break panel creation */ } return origInit.call(this, baseLayers, overlayers, options); }; return true; } if (!install()) { // L not ready yet — retry until it is. var tries = 0; var t = setInterval(function () { if (install() || ++tries > 100) clearInterval(t); }, 30); } })(); /* ------------------------------------------------------------------ * FLOATING PANEL * ------------------------------------------------------------------ */ (function () { var userClosedPanel = false; // ------------------------------------------------------------------- // 1. PANEL + TOGGLE HTML // ------------------------------------------------------------------- function injectPanelHTML() { if (document.getElementById('kyw-active-layers-panel')) return; var wrap = document.createElement('div'); wrap.id = 'kyw-active-layers-panel'; wrap.innerHTML = [ '
', ' 📌 Active Layers ', ' ', '
', '
' ].join(''); document.body.appendChild(wrap); if (!document.getElementById('kyw-alp-toggle')) { var btn = document.createElement('button'); btn.id = 'kyw-alp-toggle'; btn.type = 'button'; btn.title = 'Show active layers'; btn.innerHTML = '📌 0'; document.body.appendChild(btn); } } // ------------------------------------------------------------------- // 2. PANEL + TOGGLE CSS // ------------------------------------------------------------------- function injectPanelCSS() { if (document.getElementById('kyw-alp-style')) return; var css = document.createElement('style'); css.id = 'kyw-alp-style'; css.textContent = [ '#kyw-active-layers-panel {', ' position: fixed; top: 80px; right: 20px; width: 300px;', ' max-height: 70vh; background: #fff;', ' border: 1px solid #cbd5e1; border-radius: 8px;', ' box-shadow: 0 4px 12px rgba(0,0,0,0.18);', ' z-index: 99998; display: none; flex-direction: column;', ' font-family: Arial, sans-serif; font-size: 13px; overflow: hidden;', '}', '#kyw-active-layers-panel.kyw-alp-visible { display: flex !important; }', '#kyw-alp-header {', ' background: #0056b3; color: #fff; padding: 8px 12px;', ' cursor: grab; display: flex; justify-content: space-between;', ' align-items: center; font-weight: bold; user-select: none;', ' touch-action: none;', '}', '#kyw-alp-header:active { cursor: grabbing; }', '#kyw-alp-count {', ' background: #ffc107; color: #000; border-radius: 10px;', ' padding: 1px 7px; font-size: 11px; margin-left: 6px;', '}', '#kyw-alp-close {', ' background: transparent; border: none; color: #fff;', ' font-size: 18px; font-weight: bold; cursor: pointer;', ' line-height: 1; padding: 0 4px;', '}', '#kyw-alp-close:hover { color: #ffcccc; }', '#kyw-alp-body {', ' padding: 8px; overflow-y: auto;', ' max-height: calc(70vh - 40px);', '}', '#kyw-alp-groups .kyw-alp-group {', ' background: #f1f5f9; border-radius: 4px; padding: 6px 8px;', ' margin-bottom: 6px; font-weight: bold; color: #003366;', ' cursor: pointer; position: relative;', '}', '#kyw-alp-groups .kyw-alp-item {', ' padding: 3px 0 3px 16px; display: flex;', ' align-items: center; gap: 6px; color: #333; font-weight: normal;', '}', '#kyw-alp-groups .kyw-alp-item input[type="checkbox"] {', ' margin: 0; cursor: pointer;', '}', '#kyw-alp-groups .kyw-alp-item label {', ' cursor: pointer; font-weight: normal; margin: 0; flex: 1;', '}', '#kyw-alp-groups .kyw-alp-toggle {', ' float: right; border: none; background: none;', ' cursor: pointer; font-weight: bold; font-size: 14px;', ' line-height: 1; padding: 0 4px; color: #003366;', '}', '#kyw-alp-empty {', ' padding: 20px 10px; text-align: center; color: #94a3b8;', ' font-style: italic; font-size: 12px;', '}', '#kyw-alp-toggle {', ' position: fixed; top: 80px; right: 20px; z-index: 99998;', ' display: none; align-items: center; gap: 6px;', ' padding: 8px 14px; background: #0056b3; color: #fff;', ' border: none; border-radius: 20px; cursor: pointer;', ' font-family: Arial, sans-serif; font-size: 13px; font-weight: bold;', ' box-shadow: 0 3px 10px rgba(0,0,0,0.25);', ' transition: background 0.15s, transform 0.15s;', '}', '#kyw-alp-toggle:hover { background: #003d80; transform: translateY(-1px); }', '#kyw-alp-toggle #kyw-alp-toggle-count {', ' background: #ffc107; color: #000; border-radius: 10px;', ' padding: 1px 7px; font-size: 11px;', '}' ].join('\n'); document.head.appendChild(css); } // ------------------------------------------------------------------- // 3. OVERLAY LOOKUP (arrays + PanelLayers captures) // ------------------------------------------------------------------- function collectOverlaySets() { var sets = []; // Preferred: master array if the page exposes one if (Array.isArray(window.allOverLayers) && window.allOverLayers.length) { sets.push(window.allOverLayers); } // Named arrays used by the marine page var names = [ 'overLayers', 'overLayers1', 'overLayers2', 'overLayers3', 'overLayers4', 'overLayers5', 'overLayers6', 'overLayers7', 'overLayers8', 'overLayers9', 'overLayers10', 'overLayers11', 'overLayers12', 'overLayers13', 'awsoverLayers', 'irainsOverlayers', 'cycloneOverlayers', 'awsKarLayers', 'awsTelLayers' ]; names.forEach(function (n) { if (Array.isArray(window[n]) && window[n].length) { sets.push(window[n]); } }); // Captured PanelLayers controls — District Warning, QPF, Storm Surge, // Flash Flood, Nowcast Warning, and any future panel. if (Array.isArray(window.__kywCapturedPanelOverlays) && window.__kywCapturedPanelOverlays.length) { sets.push(window.__kywCapturedPanelOverlays); } return sets; } // ------------------------------------------------------------------- // 4. FIND (group, name) FOR A GIVEN LAYER // ------------------------------------------------------------------- function findLayerInfo(layer) { var sets = collectOverlaySets(); for (var s = 0; s < sets.length; s++) { var set = sets[s]; if (!Array.isArray(set)) continue; for (var g = 0; g < set.length; g++) { var group = set[g]; if (!group || !Array.isArray(group.layers)) continue; for (var i = 0; i < group.layers.length; i++) { if (group.layers[i].layer === layer) { return { group: group.group, name: group.layers[i].name }; } } } } return null; } // ------------------------------------------------------------------- // 5. PROPERLY UNCHECK THE ORIGINAL leaflet-panel-layers CHECKBOX // ------------------------------------------------------------------- function uncheckPanelLayer(layer) { if (!layer) return false; var info = findLayerInfo(layer); var panels = document.querySelectorAll('.leaflet-panel-layers'); if (!panels.length) return false; for (var p = 0; p < panels.length; p++) { var panelEl = panels[p]; var groups; if (info && info.group) { var allGroups = panelEl.querySelectorAll('.leaflet-panel-layers-group'); groups = []; for (var gi = 0; gi < allGroups.length; gi++) { var nameEl = allGroups[gi].querySelector( '.leaflet-panel-layers-group-name' ); var gText = nameEl ? nameEl.textContent.trim() : ''; if (gText === info.group) groups.push(allGroups[gi]); } if (!groups.length) { groups = panelEl.querySelectorAll('.leaflet-panel-layers-group'); } } else { groups = panelEl.querySelectorAll('.leaflet-panel-layers-group'); } for (var g = 0; g < groups.length; g++) { var items = groups[g].querySelectorAll('.leaflet-panel-layers-item'); for (var ii = 0; ii < items.length; ii++) { var itemEl = items[ii]; var selector = itemEl.querySelector( 'input.leaflet-panel-layers-selector' ); if (!selector || !selector.checked) continue; var labelEl = itemEl.querySelector('label'); var labelText = labelEl ? labelEl.textContent.trim() : itemEl.textContent.replace(/\s+/g, ' ').trim(); var matches = false; if (info && info.name) { var a = labelText.replace(/\s+/g, ' ').trim(); var b = String(info.name).replace(/\s+/g, ' ').trim(); matches = (a === b) || (a.indexOf(b) !== -1) || (b.indexOf(a) !== -1); } else { matches = (selector._leaflet_id === layer._leaflet_id); } if (matches) { selector.click(); return true; } } } } return false; } // ------------------------------------------------------------------- // 6. TOGGLE BUTTON VISIBILITY // ------------------------------------------------------------------- function showToggleButton() { var btn = document.getElementById('kyw-alp-toggle'); if (btn) btn.style.display = 'inline-flex'; } function hideToggleButton() { var btn = document.getElementById('kyw-alp-toggle'); if (btn) btn.style.display = 'none'; } // ------------------------------------------------------------------- // 7. MAIN RENDER // ------------------------------------------------------------------- function updateActiveLayers() { var groupsDiv = document.getElementById('kyw-alp-groups'); if (!groupsDiv) return; var mapRef = window.map; if (!mapRef || typeof mapRef.hasLayer !== 'function') return; groupsDiv.innerHTML = ''; var overlaySets = collectOverlaySets(); var anyActive = false; var seenLayers = {}; // dedupe identical layer instances var seenGroups = {}; // dedupe identical group headings var activeCount = 0; function makeItemRow(item, groupKey, mapRef) { var row = document.createElement('div'); row.className = 'kyw-alp-item'; row.dataset.group = groupKey; var cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = true; cb.addEventListener('change', function () { if (this.checked) { mapRef.addLayer(item.layer); } else { var handled = uncheckPanelLayer(item.layer); if (!handled && mapRef.hasLayer(item.layer)) { mapRef.removeLayer(item.layer); } else if (mapRef.hasLayer(item.layer)) { mapRef.removeLayer(item.layer); } } updateActiveLayers(); }); var label = document.createElement('label'); label.textContent = item.name || '(unnamed)'; label.addEventListener('click', function (ev) { ev.preventDefault(); cb.checked = !cb.checked; cb.dispatchEvent(new Event('change')); }); row.appendChild(cb); row.appendChild(label); return row; } overlaySets.forEach(function (overLayerSet) { if (!Array.isArray(overLayerSet)) return; overLayerSet.forEach(function (group) { if (!group || !Array.isArray(group.layers)) return; var activeLayers = group.layers.filter(function (item) { if (!item || !item.layer) return false; if (!mapRef.hasLayer(item.layer)) return false; var key = 'L' + (item.layer._leaflet_id || item.name); if (seenLayers[key]) return false; seenLayers[key] = true; return true; }); if (activeLayers.length === 0) return; anyActive = true; activeCount += activeLayers.length; // If this group was already rendered, append its items to it var gKey = 'G' + (group.group || ''); if (seenGroups[gKey]) { var existing = groupsDiv.querySelector( '.kyw-alp-group[data-gkey="' + CSS.escape(gKey) + '"]' ); if (existing) { var lastNode = existing; var sib = existing.nextElementSibling; while (sib && sib.classList.contains('kyw-alp-item')) { lastNode = sib; sib = sib.nextElementSibling; } activeLayers.forEach(function (item) { var row = makeItemRow(item, existing.dataset.key, mapRef); if (lastNode.nextSibling) { groupsDiv.insertBefore(row, lastNode.nextSibling); } else { groupsDiv.appendChild(row); } lastNode = row; }); return; } } seenGroups[gKey] = true; var groupDiv = document.createElement('div'); groupDiv.className = 'kyw-alp-group'; groupDiv.dataset.gkey = gKey; groupDiv.textContent = group.group; var toggle = document.createElement('button'); toggle.className = 'kyw-alp-toggle'; toggle.textContent = '\u2212'; toggle.addEventListener('click', function (ev) { ev.stopPropagation(); var items = groupDiv.parentNode.querySelectorAll( '.kyw-alp-item[data-group="' + groupDiv.dataset.key + '"]' ); var collapsed = items.length && items[0].style.display === 'none'; items.forEach(function (el) { el.style.display = collapsed ? 'flex' : 'none'; }); toggle.textContent = collapsed ? '\u2212' : '+'; }); groupDiv.dataset.key = 'g' + Math.random().toString(36).slice(2, 8); groupDiv.appendChild(toggle); groupsDiv.appendChild(groupDiv); activeLayers.forEach(function (item) { groupsDiv.appendChild( makeItemRow(item, groupDiv.dataset.key, mapRef) ); }); }); }); var countEl = document.getElementById('kyw-alp-count'); if (countEl) countEl.textContent = activeCount ? String(activeCount) : ''; var toggleCountEl = document.getElementById('kyw-alp-toggle-count'); if (toggleCountEl) toggleCountEl.textContent = String(activeCount); if (!anyActive) { var empty = document.createElement('div'); empty.id = 'kyw-alp-empty'; empty.textContent = 'No active layers selected.'; groupsDiv.appendChild(empty); } var panel = document.getElementById('kyw-active-layers-panel'); if (panel) { if (anyActive && !userClosedPanel) { panel.classList.add('kyw-alp-visible'); hideToggleButton(); } else { panel.classList.remove('kyw-alp-visible'); showToggleButton(); } } } // ------------------------------------------------------------------- // 8. DRAG // ------------------------------------------------------------------- function makeDraggable(el) { if (!el) return; var header = el.querySelector('#kyw-alp-header') || el; var offset = [0, 0]; header.addEventListener('mousedown', function (e) { if (e.target.tagName === 'BUTTON' || e.target.tagName === 'INPUT' || e.target.tagName === 'LABEL' || e.target.closest('button')) return; e.preventDefault(); e.stopPropagation(); offset = [ el.offsetLeft - e.clientX, el.offsetTop - e.clientY ]; document.addEventListener('mousemove', move); document.addEventListener('mouseup', stop); }); function move(e) { el.style.left = (e.clientX + offset[0]) + 'px'; el.style.top = (e.clientY + offset[1]) + 'px'; el.style.right = 'auto'; } function stop() { document.removeEventListener('mousemove', move); document.removeEventListener('mouseup', stop); } } // ------------------------------------------------------------------- // 9. BOOT // ------------------------------------------------------------------- function boot() { injectPanelCSS(); injectPanelHTML(); var closeBtn = document.getElementById('kyw-alp-close'); if (closeBtn) { closeBtn.addEventListener('click', function () { userClosedPanel = true; document.getElementById('kyw-active-layers-panel') .classList.remove('kyw-alp-visible'); showToggleButton(); }); } var toggleBtn = document.getElementById('kyw-alp-toggle'); if (toggleBtn) { toggleBtn.addEventListener('click', function () { userClosedPanel = false; var panel = document.getElementById('kyw-active-layers-panel'); panel.classList.add('kyw-alp-visible'); hideToggleButton(); updateActiveLayers(); }); } makeDraggable(document.getElementById('kyw-active-layers-panel')); var tries = 0; var timer = setInterval(function () { var mapRef = window.map; tries++; if (mapRef && typeof mapRef.on === 'function') { clearInterval(timer); mapRef.on('layeradd', updateActiveLayers); mapRef.on('layerremove', updateActiveLayers); updateActiveLayers(); console.log('[KYW-ActiveLayers] attached to map'); } else if (tries > 40) { clearInterval(timer); console.warn('[KYW-ActiveLayers] map not found — panel disabled'); } }, 300); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', boot); } else { boot(); } window.updateKYWActiveLayersPanel = updateActiveLayers; })();