mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-22 15:11:09 -05:00
Refactor templates to utilize AlpineJS for state management and interactions, replacing custom JavaScript. Updated navigation links for parks and rides, streamlined mobile filter functionality, and enhanced advanced search features. Removed legacy JavaScript code for improved performance and maintainability.
This commit is contained in:
@@ -197,310 +197,10 @@
|
||||
<!-- Leaflet MarkerCluster JS -->
|
||||
<script src="https://unpkg.com/leaflet.markercluster@1.4.1/dist/leaflet.markercluster.js"></script>
|
||||
|
||||
<script>
|
||||
// Map initialization and management
|
||||
class ThrillWikiMap {
|
||||
constructor(containerId, options = {}) {
|
||||
this.containerId = containerId;
|
||||
this.options = {
|
||||
center: [39.8283, -98.5795], // Center of USA
|
||||
zoom: 4,
|
||||
enableClustering: true,
|
||||
...options
|
||||
};
|
||||
this.map = null;
|
||||
this.markers = new L.MarkerClusterGroup();
|
||||
this.currentData = [];
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
// Initialize the map
|
||||
this.map = L.map(this.containerId, {
|
||||
center: this.options.center,
|
||||
zoom: this.options.zoom,
|
||||
zoomControl: false
|
||||
});
|
||||
|
||||
// Add custom zoom control
|
||||
L.control.zoom({
|
||||
position: 'bottomright'
|
||||
}).addTo(this.map);
|
||||
|
||||
// Add tile layers with dark mode support
|
||||
const lightTiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors',
|
||||
className: 'map-tiles'
|
||||
});
|
||||
|
||||
const darkTiles = L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
|
||||
attribution: '© OpenStreetMap contributors, © CARTO',
|
||||
className: 'map-tiles-dark'
|
||||
});
|
||||
|
||||
// Set initial tiles based on theme
|
||||
if (document.documentElement.classList.contains('dark')) {
|
||||
darkTiles.addTo(this.map);
|
||||
} else {
|
||||
lightTiles.addTo(this.map);
|
||||
}
|
||||
|
||||
// Listen for theme changes
|
||||
this.observeThemeChanges(lightTiles, darkTiles);
|
||||
|
||||
// Add markers cluster group
|
||||
this.map.addLayer(this.markers);
|
||||
|
||||
// Bind map events
|
||||
this.bindEvents();
|
||||
|
||||
// Load initial data
|
||||
this.loadMapData();
|
||||
}
|
||||
|
||||
observeThemeChanges(lightTiles, darkTiles) {
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.attributeName === 'class') {
|
||||
if (document.documentElement.classList.contains('dark')) {
|
||||
this.map.removeLayer(lightTiles);
|
||||
this.map.addLayer(darkTiles);
|
||||
} else {
|
||||
this.map.removeLayer(darkTiles);
|
||||
this.map.addLayer(lightTiles);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ['class']
|
||||
});
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
// Update map when bounds change
|
||||
this.map.on('moveend zoomend', () => {
|
||||
this.updateMapBounds();
|
||||
});
|
||||
|
||||
// Handle filter form changes
|
||||
document.getElementById('map-filters').addEventListener('htmx:afterRequest', (event) => {
|
||||
if (event.detail.successful) {
|
||||
this.loadMapData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadMapData() {
|
||||
try {
|
||||
document.getElementById('map-loading').style.display = 'flex';
|
||||
|
||||
const formData = new FormData(document.getElementById('map-filters'));
|
||||
const queryParams = {};
|
||||
|
||||
// Add form data to params
|
||||
for (let [key, value] of formData.entries()) {
|
||||
queryParams[key] = value;
|
||||
}
|
||||
|
||||
// Add map bounds
|
||||
const bounds = this.map.getBounds();
|
||||
queryParams.north = bounds.getNorth();
|
||||
queryParams.south = bounds.getSouth();
|
||||
queryParams.east = bounds.getEast();
|
||||
queryParams.west = bounds.getWest();
|
||||
queryParams.zoom = this.map.getZoom();
|
||||
|
||||
// Create temporary form for HTMX request
|
||||
const tempForm = document.createElement('form');
|
||||
tempForm.setAttribute('hx-get', '{{ map_api_urls.locations }}');
|
||||
tempForm.setAttribute('hx-vals', JSON.stringify(queryParams));
|
||||
tempForm.setAttribute('hx-trigger', 'submit');
|
||||
tempForm.setAttribute('hx-swap', 'none');
|
||||
|
||||
tempForm.addEventListener('htmx:afterRequest', (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.detail.xhr.responseText);
|
||||
|
||||
if (data.status === 'success') {
|
||||
this.updateMarkers(data.data);
|
||||
} else {
|
||||
console.error('Map data error:', data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load map data:', error);
|
||||
} finally {
|
||||
document.getElementById('map-loading').style.display = 'none';
|
||||
document.body.removeChild(tempForm);
|
||||
}
|
||||
});
|
||||
|
||||
tempForm.addEventListener('htmx:error', (event) => {
|
||||
console.error('Failed to load map data:', event.detail.error);
|
||||
document.getElementById('map-loading').style.display = 'none';
|
||||
document.body.removeChild(tempForm);
|
||||
});
|
||||
|
||||
document.body.appendChild(tempForm);
|
||||
htmx.trigger(tempForm, 'submit');
|
||||
} catch (error) {
|
||||
console.error('Failed to load map data:', error);
|
||||
document.getElementById('map-loading').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
updateMarkers(data) {
|
||||
// Clear existing markers
|
||||
this.markers.clearLayers();
|
||||
|
||||
// Add location markers
|
||||
if (data.locations) {
|
||||
data.locations.forEach(location => {
|
||||
this.addLocationMarker(location);
|
||||
});
|
||||
}
|
||||
|
||||
// Add cluster markers
|
||||
if (data.clusters) {
|
||||
data.clusters.forEach(cluster => {
|
||||
this.addClusterMarker(cluster);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addLocationMarker(location) {
|
||||
const icon = this.getLocationIcon(location.type);
|
||||
const marker = L.marker([location.latitude, location.longitude], { icon });
|
||||
|
||||
// Create popup content
|
||||
const popupContent = this.createPopupContent(location);
|
||||
marker.bindPopup(popupContent);
|
||||
|
||||
// Add click handler for detailed view
|
||||
marker.on('click', () => {
|
||||
this.showLocationDetails(location.type, location.id);
|
||||
});
|
||||
|
||||
this.markers.addLayer(marker);
|
||||
}
|
||||
|
||||
addClusterMarker(cluster) {
|
||||
const marker = L.marker([cluster.latitude, cluster.longitude], {
|
||||
icon: L.divIcon({
|
||||
className: 'cluster-marker',
|
||||
html: `<div class="cluster-marker-inner">${cluster.count}</div>`,
|
||||
iconSize: [40, 40]
|
||||
})
|
||||
});
|
||||
|
||||
marker.bindPopup(`${cluster.count} locations in this area`);
|
||||
this.markers.addLayer(marker);
|
||||
}
|
||||
|
||||
getLocationIcon(type) {
|
||||
const iconMap = {
|
||||
'park': '🎢',
|
||||
'ride': '🎠',
|
||||
'company': '🏢',
|
||||
'generic': '📍'
|
||||
};
|
||||
|
||||
return L.divIcon({
|
||||
className: 'location-marker',
|
||||
html: `<div class="location-marker-inner">${iconMap[type] || '📍'}</div>`,
|
||||
iconSize: [30, 30],
|
||||
iconAnchor: [15, 15]
|
||||
});
|
||||
}
|
||||
|
||||
createPopupContent(location) {
|
||||
return `
|
||||
<div class="location-info-popup">
|
||||
<h3>${location.name}</h3>
|
||||
${location.formatted_location ? `<p><i class="fas fa-map-marker-alt mr-1"></i>${location.formatted_location}</p>` : ''}
|
||||
${location.operator ? `<p><i class="fas fa-building mr-1"></i>${location.operator}</p>` : ''}
|
||||
${location.ride_count ? `<p><i class="fas fa-rocket mr-1"></i>${location.ride_count} rides</p>` : ''}
|
||||
<div class="mt-2">
|
||||
<button onclick="thrillwikiMap.showLocationDetails('${location.type}', ${location.id})"
|
||||
class="px-3 py-1 text-sm text-white bg-blue-600 rounded hover:bg-blue-700">
|
||||
View Details
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
showLocationDetails(type, id) {
|
||||
// Create temporary form for HTMX request
|
||||
const tempForm = document.createElement('form');
|
||||
tempForm.setAttribute('hx-get', `{% url 'maps:htmx_location_detail' 'TYPE' 0 %}`.replace('TYPE', type).replace('0', id));
|
||||
tempForm.setAttribute('hx-target', '#location-modal');
|
||||
tempForm.setAttribute('hx-swap', 'innerHTML');
|
||||
tempForm.setAttribute('hx-trigger', 'submit');
|
||||
|
||||
tempForm.addEventListener('htmx:afterRequest', (event) => {
|
||||
if (event.detail.successful) {
|
||||
document.getElementById('location-modal').classList.remove('hidden');
|
||||
}
|
||||
document.body.removeChild(tempForm);
|
||||
});
|
||||
|
||||
tempForm.addEventListener('htmx:error', (event) => {
|
||||
console.error('Failed to load location details:', event.detail.error);
|
||||
document.body.removeChild(tempForm);
|
||||
});
|
||||
|
||||
document.body.appendChild(tempForm);
|
||||
htmx.trigger(tempForm, 'submit');
|
||||
}
|
||||
|
||||
updateMapBounds() {
|
||||
// This could trigger an HTMX request to update data based on new bounds
|
||||
// For now, we'll just reload data when the map moves significantly
|
||||
clearTimeout(this.boundsUpdateTimeout);
|
||||
this.boundsUpdateTimeout = setTimeout(() => {
|
||||
this.loadMapData();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize map when page loads
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
window.thrillwikiMap = new ThrillWikiMap('map-container', {
|
||||
{% if initial_bounds %}
|
||||
center: [{{ initial_bounds.north|add:initial_bounds.south|floatformat:6|div:2 }}, {{ initial_bounds.east|add:initial_bounds.west|floatformat:6|div:2 }}],
|
||||
{% endif %}
|
||||
enableClustering: {{ enable_clustering|yesno:"true,false" }}
|
||||
});
|
||||
|
||||
// Handle filter pill toggles
|
||||
document.querySelectorAll('.filter-pill').forEach(pill => {
|
||||
const checkbox = pill.querySelector('input[type="checkbox"]');
|
||||
|
||||
// Set initial state
|
||||
if (checkbox.checked) {
|
||||
pill.classList.add('active');
|
||||
}
|
||||
|
||||
pill.addEventListener('click', () => {
|
||||
checkbox.checked = !checkbox.checked;
|
||||
pill.classList.toggle('active', checkbox.checked);
|
||||
|
||||
// Trigger form change
|
||||
document.getElementById('map-filters').dispatchEvent(new Event('change'));
|
||||
});
|
||||
});
|
||||
|
||||
// Close modal handler
|
||||
document.addEventListener('click', (e) => {
|
||||
if (e.target.id === 'location-modal') {
|
||||
document.getElementById('location-modal').classList.add('hidden');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<!-- AlpineJS Map Component (HTMX + AlpineJS Only) -->
|
||||
<div x-data="universalMap" x-init="initMap()" style="display: none;">
|
||||
<!-- Map functionality handled by AlpineJS + HTMX -->
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.cluster-marker {
|
||||
|
||||
Reference in New Issue
Block a user