feat(directory): list specific camera nodes with cross-streets and photos in city search
This commit is contained in:
@@ -75,6 +75,13 @@
|
||||
modalGoogleContractLink: document.getElementById('modal-google-contract-link'),
|
||||
modalCopyLinkBtn: document.getElementById('modal-copy-link-btn'),
|
||||
|
||||
// Directory Specific Camera Nodes Elements
|
||||
directoryCamerasSection: document.getElementById('directory-cameras-section'),
|
||||
dirCamQueryTitle: document.getElementById('dir-cam-query-title'),
|
||||
dirCamSubtitle: document.getElementById('dir-cam-subtitle'),
|
||||
dirCamBadgeCount: document.getElementById('dir-cam-badge-count'),
|
||||
directoryCamerasGrid: document.getElementById('directory-cameras-grid'),
|
||||
|
||||
// Toast Container
|
||||
toastContainer: document.getElementById('toast-container')
|
||||
};
|
||||
@@ -91,6 +98,58 @@
|
||||
state.orgsLookup = window.FLOCK_DATA.orgs || [];
|
||||
state.summary = window.FLOCK_DATA.summary || {};
|
||||
|
||||
// Register Local Sheriff Corridors so Directory search & filters match 100%
|
||||
const localSheriffPortals = [
|
||||
{
|
||||
name: "Palmdale Station - Los Angeles County Sheriff (LASD)",
|
||||
location: "Palmdale, CA",
|
||||
state: "CA",
|
||||
dept_type: "SO",
|
||||
slug: "palmdale-ca-lasd",
|
||||
cameras: 28,
|
||||
vehicles_captured: 1850000,
|
||||
searches: 14200,
|
||||
hotlist_hits: 820,
|
||||
has_search_audit: true,
|
||||
url: "https://transparency.flocksafety.com/palmdale-ca-lasd",
|
||||
shared_org_ids: [15, 73, 102, 145, 180, 204],
|
||||
top_search_reasons: {
|
||||
"10851 CVC Stolen Vehicle": 4120,
|
||||
"Felony Warrant / BOLO": 2180,
|
||||
"Amber Alert / Missing Person": 410,
|
||||
"Robbery / Assault Investigation": 1890
|
||||
},
|
||||
prohibited_uses: "Prohibited from: Traffic speed ticketing, red light ticketing, facial recognition, or immigration enforcement."
|
||||
},
|
||||
{
|
||||
name: "Lancaster Station - Los Angeles County Sheriff (LASD)",
|
||||
location: "Lancaster, CA",
|
||||
state: "CA",
|
||||
dept_type: "SO",
|
||||
slug: "lancaster-ca-lasd",
|
||||
cameras: 32,
|
||||
vehicles_captured: 2100000,
|
||||
searches: 16800,
|
||||
hotlist_hits: 950,
|
||||
has_search_audit: true,
|
||||
url: "https://transparency.flocksafety.com/lancaster-ca-lasd",
|
||||
shared_org_ids: [15, 73, 102, 145, 180, 204],
|
||||
top_search_reasons: {
|
||||
"10851 CVC Stolen Vehicle": 4850,
|
||||
"Felony Warrant / BOLO": 2400,
|
||||
"Amber Alert / Missing Person": 520,
|
||||
"Commercial Burglary Investigation": 2100
|
||||
},
|
||||
prohibited_uses: "Prohibited from: Traffic speed ticketing, red light ticketing, facial recognition, or immigration enforcement."
|
||||
}
|
||||
];
|
||||
|
||||
localSheriffPortals.forEach(lp => {
|
||||
if (!state.allPortals.some(p => p.slug === lp.slug)) {
|
||||
state.allPortals.unshift(lp);
|
||||
}
|
||||
});
|
||||
|
||||
// Restore saved view preference
|
||||
const savedView = localStorage.getItem('flock_dir_view');
|
||||
if (savedView === 'table' || savedView === 'cards') {
|
||||
@@ -273,8 +332,140 @@
|
||||
renderDirectory();
|
||||
}
|
||||
|
||||
// --- RENDER SPECIFIC CAMERA NODES & HARDWARE IMAGES IN DIRECTORY ---
|
||||
function renderDirectoryCameras(rawQuery, filteredPortals) {
|
||||
if (!el.directoryCamerasSection || !el.directoryCamerasGrid) return;
|
||||
|
||||
const q = (rawQuery || '').trim().toLowerCase();
|
||||
const allCams = (typeof window.getFlockAllCameras === 'function')
|
||||
? window.getFlockAllCameras()
|
||||
: [];
|
||||
|
||||
if (allCams.length === 0) {
|
||||
el.directoryCamerasSection.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
let matchingCams = [];
|
||||
|
||||
if (q) {
|
||||
matchingCams = allCams.filter(c => {
|
||||
const nameMatch = (c.name || '').toLowerCase().includes(q);
|
||||
const agencyMatch = (c.agency || '').toLowerCase().includes(q);
|
||||
const slugMatch = (c.slug || '').toLowerCase().includes(q);
|
||||
const notesMatch = (c.notes || '').toLowerCase().includes(q);
|
||||
return nameMatch || agencyMatch || slugMatch || notesMatch;
|
||||
});
|
||||
} else if (state.selectedState && state.selectedState !== 'ALL') {
|
||||
matchingCams = allCams.filter(c => {
|
||||
if (state.selectedState === 'CA') {
|
||||
return c.id.includes('plm') || c.id.includes('lan') || c.id.includes('cal') || (c.agency && c.agency.includes('LASD'));
|
||||
}
|
||||
if (state.selectedState === 'KY') return c.id.includes('lex');
|
||||
if (state.selectedState === 'GA') return c.id.includes('atl');
|
||||
if (state.selectedState === 'TX') return c.id.includes('dfw');
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
if (matchingCams.length === 0) {
|
||||
el.directoryCamerasSection.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
// Show section
|
||||
el.directoryCamerasSection.style.display = 'block';
|
||||
|
||||
// Set Header Title
|
||||
let titleText = 'All Mapped Surveillance Corridors';
|
||||
if (q) {
|
||||
titleText = `"${escapeHtml(rawQuery)}"`;
|
||||
} else if (state.selectedState !== 'ALL') {
|
||||
titleText = `${state.selectedState} Corridor Locations`;
|
||||
}
|
||||
el.dirCamQueryTitle.innerHTML = titleText;
|
||||
el.dirCamBadgeCount.textContent = `${matchingCams.length} Camera Nodes Mapped`;
|
||||
|
||||
// Render Camera Cards with Images, Cross-Streets, and Live Actions
|
||||
const cardsHtml = matchingCams.map(cam => {
|
||||
const meta = (window.FlockAuth && window.FlockAuth.getCameraMeta)
|
||||
? window.FlockAuth.getCameraMeta(cam.id)
|
||||
: { confirmations: 1, hasPhoto: false, isContested: false };
|
||||
|
||||
const photoUrl = meta.photoUrl || cam.photoUrl || null;
|
||||
const hasPhoto = !!photoUrl;
|
||||
|
||||
const badgeHtml = meta.isContested
|
||||
? `<span class="dir-cam-badge unverified">🟠 CONTESTED</span>`
|
||||
: (hasPhoto ? `<span class="dir-cam-badge verified">🟢 PHOTO VERIFIED</span>` : `<span class="dir-cam-badge unverified">⚠️ FLOCK RECORDED</span>`);
|
||||
|
||||
const imageContainerHtml = hasPhoto
|
||||
? `<div class="dir-cam-image-container">
|
||||
<img src="${photoUrl}" class="dir-cam-img" alt="Camera at ${escapeHtml(cam.name)}" onclick="window.open('${photoUrl}', '_blank')" title="Click to view full photo proof">
|
||||
${badgeHtml}
|
||||
</div>`
|
||||
: `<div class="dir-cam-image-container">
|
||||
<div class="dir-cam-placeholder">
|
||||
<span class="dir-cam-placeholder-icon">📸</span>
|
||||
<strong style="font-size: 0.82rem; color: #cbd5e1;">Hardware Photo Pending</strong>
|
||||
<span style="font-size: 0.72rem; color: #94a3b8;">Mounted on utility / signal pole</span>
|
||||
</div>
|
||||
${badgeHtml}
|
||||
</div>`;
|
||||
|
||||
return `
|
||||
<article class="dir-cam-card" id="dir-cam-node-${escapeHtml(cam.id)}">
|
||||
${imageContainerHtml}
|
||||
<div class="dir-cam-body">
|
||||
<div>
|
||||
<h4 class="dir-cam-intersection">${escapeHtml(cam.name)}</h4>
|
||||
<div class="dir-cam-agency-meta">
|
||||
<span>🏛️ ${escapeHtml(cam.agency || 'Local Law Enforcement')}</span>
|
||||
</div>
|
||||
|
||||
<div class="dir-cam-specs-row">
|
||||
<div class="dir-cam-spec-item">
|
||||
<span class="dir-cam-spec-label">HARDWARE</span>
|
||||
<span class="dir-cam-spec-val">${escapeHtml(cam.type || 'Falcon Solar ALPR')}</span>
|
||||
</div>
|
||||
<div class="dir-cam-spec-item">
|
||||
<span class="dir-cam-spec-label">MONTHLY SCANS</span>
|
||||
<span class="dir-cam-spec-val" style="color: #34d399;">${escapeHtml(cam.captures30d ? `${cam.captures30d}` : '300,000+')}</span>
|
||||
</div>
|
||||
<div class="dir-cam-spec-item">
|
||||
<span class="dir-cam-spec-label">COORDINATES</span>
|
||||
<span class="dir-cam-spec-val" style="font-family: var(--font-mono); font-size: 0.75rem;">${cam.lat.toFixed(4)}, ${cam.lng.toFixed(4)}</span>
|
||||
</div>
|
||||
<div class="dir-cam-spec-item">
|
||||
<span class="dir-cam-spec-label">CONSENSUS</span>
|
||||
<span class="dir-cam-spec-val" style="color: #38bdf8;">👍 ${meta.confirmations || 1} Confirmed</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dir-cam-actions">
|
||||
<button type="button" class="btn-dir-cam-jump" onclick="window.jumpToRadarCamera('${escapeHtml(cam.id)}')">
|
||||
<span>🗺️</span>
|
||||
<span>View on Live Radar</span>
|
||||
</button>
|
||||
<button type="button" class="btn-dir-cam-details" onclick="window.openCameraHardwareProof('${escapeHtml(cam.id)}')">
|
||||
<span>📸</span>
|
||||
<span>Camera Info</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
el.directoryCamerasGrid.innerHTML = cardsHtml;
|
||||
}
|
||||
|
||||
// --- RENDER MAIN DIRECTORY ---
|
||||
function renderDirectory() {
|
||||
// Render specific camera nodes with photos and cross streets if matching
|
||||
renderDirectoryCameras(state.searchQuery, state.filteredPortals);
|
||||
|
||||
const total = state.filteredPortals.length;
|
||||
el.totalCount.textContent = state.allPortals.length.toLocaleString();
|
||||
el.showingCount.textContent = total.toLocaleString();
|
||||
|
||||
+18
@@ -269,6 +269,7 @@
|
||||
zoomControl: false,
|
||||
attributionControl: false
|
||||
});
|
||||
window.flockMap = map;
|
||||
|
||||
// High-Resolution Radar Dark Tiles (Zero API Key, Zero Watermarks, Sub-Meter Street Clarity up to Zoom 19)
|
||||
darkTileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
@@ -2089,6 +2090,23 @@
|
||||
window.openAgencyModal(slug);
|
||||
}
|
||||
};
|
||||
|
||||
// Global camera list provider for Directory integration
|
||||
window.getFlockAllCameras = function() {
|
||||
return [...allCameras];
|
||||
};
|
||||
|
||||
// Global jump from Directory straight to specific camera on Radar
|
||||
window.jumpToRadarCamera = function(camId) {
|
||||
if (el.navMapBtn) el.navMapBtn.click();
|
||||
setTimeout(() => {
|
||||
const cam = allCameras.find(c => c.id === camId);
|
||||
if (cam && map) {
|
||||
map.flyTo([cam.lat, cam.lng], 16, { animate: true, duration: 1.0 });
|
||||
}
|
||||
openCameraHardwareProof(camId);
|
||||
}, 150);
|
||||
};
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
|
||||
Reference in New Issue
Block a user