mirror of
https://github.com/pacnpal/thrillwiki_laravel.git
synced 2025-12-20 05:31:10 -05:00
207 lines
7.0 KiB
PHP
207 lines
7.0 KiB
PHP
<div
|
|
wire:ignore
|
|
x-data="mapComponent(@js($mapConfig))"
|
|
x-init="initializeMap()"
|
|
class="relative w-full h-[400px] rounded-lg overflow-hidden"
|
|
>
|
|
{{-- Map Container --}}
|
|
<div id="map" class="absolute inset-0 w-full h-full"></div>
|
|
|
|
{{-- Loading Overlay --}}
|
|
<div
|
|
x-show="loading"
|
|
class="absolute inset-0 bg-white/60 flex items-center justify-center"
|
|
>
|
|
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
|
|
</div>
|
|
|
|
{{-- Map Controls --}}
|
|
@if($showControls)
|
|
<div class="absolute top-2 right-2 flex flex-col gap-2 z-[1000]">
|
|
{{-- Zoom Controls --}}
|
|
<div class="flex flex-col bg-white rounded-lg shadow-lg">
|
|
<button
|
|
@click="zoomIn"
|
|
class="p-2 hover:bg-gray-100 rounded-t-lg border-b"
|
|
title="Zoom in"
|
|
>
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v12m6-6H6"/>
|
|
</svg>
|
|
</button>
|
|
<button
|
|
@click="zoomOut"
|
|
class="p-2 hover:bg-gray-100 rounded-b-lg"
|
|
title="Zoom out"
|
|
>
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
{{-- Center Control --}}
|
|
<button
|
|
@click="centerOnMarkers"
|
|
class="p-2 bg-white rounded-lg shadow-lg hover:bg-gray-100"
|
|
title="Center map on markers"
|
|
>
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Selected Location Info --}}
|
|
@if($selectedLocation)
|
|
<div
|
|
x-show="selectedLocation"
|
|
class="absolute bottom-4 left-4 right-4 bg-white rounded-lg shadow-lg p-4 z-[1000] max-w-sm"
|
|
>
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<h3 class="font-semibold text-gray-900" x-text="selectedLocation.name || 'Selected Location'"></h3>
|
|
<p class="text-sm text-gray-600 mt-1" x-text="selectedLocation.address || formatCoordinates(selectedLocation)"></p>
|
|
</div>
|
|
<button
|
|
@click="clearSelection"
|
|
class="text-gray-400 hover:text-gray-600"
|
|
>
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
@push('scripts')
|
|
<script>
|
|
document.addEventListener('alpine:init', () => {
|
|
Alpine.data('mapComponent', (config) => ({
|
|
map: null,
|
|
markers: [],
|
|
markerLayer: null,
|
|
loading: true,
|
|
selectedLocation: config.selectedLocation,
|
|
|
|
async initializeMap() {
|
|
// Wait for Leaflet to be loaded
|
|
if (typeof L === 'undefined') {
|
|
await this.loadLeaflet();
|
|
}
|
|
|
|
// Initialize the map
|
|
this.map = L.map('map', {
|
|
center: [config.center.lat || 0, config.center.lng || 0],
|
|
zoom: config.zoom || 13,
|
|
zoomControl: false,
|
|
dragging: config.interactive,
|
|
touchZoom: config.interactive,
|
|
doubleClickZoom: config.interactive,
|
|
scrollWheelZoom: config.interactive,
|
|
boxZoom: config.interactive,
|
|
tap: config.interactive
|
|
});
|
|
|
|
// Add tile layer
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
|
maxZoom: 18
|
|
}).addTo(this.map);
|
|
|
|
// Initialize marker layer
|
|
this.markerLayer = L.layerGroup().addTo(this.map);
|
|
|
|
// Add markers
|
|
this.updateMarkers(config.markers);
|
|
|
|
// Set up event listeners
|
|
this.map.on('moveend', () => this.handleMapMoved());
|
|
this.map.on('zoomend', () => this.handleZoomChanged());
|
|
|
|
this.loading = false;
|
|
},
|
|
|
|
async loadLeaflet() {
|
|
// Load Leaflet CSS
|
|
if (!document.querySelector('link[href*="leaflet.css"]')) {
|
|
const link = document.createElement('link');
|
|
link.rel = 'stylesheet';
|
|
link.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css';
|
|
document.head.appendChild(link);
|
|
}
|
|
|
|
// Load Leaflet JS
|
|
if (typeof L === 'undefined') {
|
|
const script = document.createElement('script');
|
|
script.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js';
|
|
document.head.appendChild(script);
|
|
|
|
await new Promise(resolve => script.onload = resolve);
|
|
}
|
|
},
|
|
|
|
updateMarkers(markers) {
|
|
this.markerLayer.clearLayers();
|
|
this.markers = markers.map(marker => {
|
|
const leafletMarker = L.marker([marker.latitude, marker.longitude], {
|
|
title: marker.name || 'Location'
|
|
});
|
|
|
|
leafletMarker.on('click', () => {
|
|
this.$wire.handleMarkerClicked(marker);
|
|
});
|
|
|
|
this.markerLayer.addLayer(leafletMarker);
|
|
return leafletMarker;
|
|
});
|
|
|
|
if (markers.length > 0) {
|
|
this.centerOnMarkers();
|
|
}
|
|
},
|
|
|
|
handleMapMoved() {
|
|
const center = this.map.getCenter();
|
|
this.$wire.handleMapMoved(center.lat, center.lng);
|
|
},
|
|
|
|
handleZoomChanged() {
|
|
this.$wire.handleZoomChanged(this.map.getZoom());
|
|
},
|
|
|
|
zoomIn() {
|
|
this.map.zoomIn();
|
|
},
|
|
|
|
zoomOut() {
|
|
this.map.zoomOut();
|
|
},
|
|
|
|
centerOnMarkers() {
|
|
if (this.markers.length === 0) return;
|
|
|
|
if (this.markers.length === 1) {
|
|
this.map.setView(this.markers[0].getLatLng(), this.map.getZoom());
|
|
} else {
|
|
const group = L.featureGroup(this.markers);
|
|
this.map.fitBounds(group.getBounds().pad(0.1));
|
|
}
|
|
},
|
|
|
|
clearSelection() {
|
|
this.selectedLocation = null;
|
|
this.$wire.handleLocationSelected(null);
|
|
},
|
|
|
|
formatCoordinates(location) {
|
|
if (!location?.latitude || !location?.longitude) return '';
|
|
return `${location.latitude.toFixed(6)}, ${location.longitude.toFixed(6)}`;
|
|
}
|
|
}));
|
|
});
|
|
</script>
|
|
@endpush |