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 = [
'',
''
].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;
})();