Files
thrillwiki_laravel/resources/views/livewire/location/location-map.blade.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: '&copy; <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