mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 17:21:38 -05:00
Move fetchAndDisplayPhotos to maps/helpers.js
This commit is contained in:
parent
198bf3128a
commit
9522f81abf
7 changed files with 390 additions and 81 deletions
|
|
@ -8,7 +8,12 @@ class TripsController < ApplicationController
|
|||
@trips = current_user.trips
|
||||
end
|
||||
|
||||
def show; end
|
||||
def show
|
||||
@coordinates = @trip.points.pluck(
|
||||
:latitude, :longitude, :battery, :altitude, :timestamp, :velocity, :id,
|
||||
:country
|
||||
).map { [_1.to_f, _2.to_f, _3.to_s, _4.to_s, _5.to_s, _6.to_s, _7.to_s, _8.to_s] }
|
||||
end
|
||||
|
||||
def new
|
||||
@trip = Trip.new
|
||||
|
|
|
|||
|
|
@ -106,4 +106,8 @@ module ApplicationHelper
|
|||
|
||||
'text-blue-600'
|
||||
end
|
||||
|
||||
def human_date(date)
|
||||
date.strftime('%e %B %Y')
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { fetchAndDrawAreas } from "../maps/areas";
|
|||
import { handleAreaCreated } from "../maps/areas";
|
||||
|
||||
import { showFlashMessage } from "../maps/helpers";
|
||||
import { fetchAndDisplayPhotos } from '../maps/helpers';
|
||||
|
||||
import { osmMapLayer } from "../maps/layers";
|
||||
import { osmHotMapLayer } from "../maps/layers";
|
||||
|
|
@ -83,14 +84,13 @@ export default class extends Controller {
|
|||
Photos: this.photoMarkers
|
||||
};
|
||||
|
||||
L.control
|
||||
.scale({
|
||||
position: "bottomright",
|
||||
metric: true,
|
||||
imperial: true,
|
||||
maxWidth: 120,
|
||||
})
|
||||
.addTo(this.map);
|
||||
// Add scale control to bottom right
|
||||
L.control.scale({
|
||||
position: 'bottomright',
|
||||
imperial: this.distanceUnit === 'mi',
|
||||
metric: this.distanceUnit === 'km',
|
||||
maxWidth: 120
|
||||
}).addTo(this.map)
|
||||
|
||||
this.layerControl = L.control.layers(this.baseMaps(), controlsLayer).addTo(this.map);
|
||||
|
||||
|
|
@ -132,16 +132,22 @@ export default class extends Controller {
|
|||
this.initializeDrawControl();
|
||||
|
||||
// Add event listeners to toggle draw controls
|
||||
this.map.on('overlayadd', (e) => {
|
||||
this.map.on('overlayadd', async (e) => {
|
||||
if (e.name === 'Areas') {
|
||||
this.map.addControl(this.drawControl);
|
||||
}
|
||||
if (e.name === 'Photos') {
|
||||
// Extract dates from URL parameters
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const startDate = urlParams.get('start_at')?.split('T')[0] || new Date().toISOString().split('T')[0];
|
||||
const endDate = urlParams.get('end_at')?.split('T')[0] || new Date().toISOString().split('T')[0];
|
||||
this.fetchAndDisplayPhotos(startDate, endDate);
|
||||
await fetchAndDisplayPhotos({
|
||||
map: this.map,
|
||||
photoMarkers: this.photoMarkers,
|
||||
apiKey: this.apiKey,
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
userSettings: this.userSettings
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -782,87 +788,87 @@ export default class extends Controller {
|
|||
this.layerControl = L.control.layers(this.baseMaps(), layerControl).addTo(this.map);
|
||||
}
|
||||
|
||||
async fetchAndDisplayPhotos(startDate, endDate, retryCount = 0) {
|
||||
const MAX_RETRIES = 3;
|
||||
const RETRY_DELAY = 3000; // 3 seconds
|
||||
// async fetchAndDisplayPhotos(startDate, endDate, retryCount = 0) {
|
||||
// const MAX_RETRIES = 3;
|
||||
// const RETRY_DELAY = 3000; // 3 seconds
|
||||
|
||||
// Create loading control
|
||||
const LoadingControl = L.Control.extend({
|
||||
onAdd: (map) => {
|
||||
const container = L.DomUtil.create('div', 'leaflet-loading-control');
|
||||
container.innerHTML = '<div class="loading-spinner"></div>';
|
||||
return container;
|
||||
}
|
||||
});
|
||||
// // Create loading control
|
||||
// const LoadingControl = L.Control.extend({
|
||||
// onAdd: (map) => {
|
||||
// const container = L.DomUtil.create('div', 'leaflet-loading-control');
|
||||
// container.innerHTML = '<div class="loading-spinner"></div>';
|
||||
// return container;
|
||||
// }
|
||||
// });
|
||||
|
||||
const loadingControl = new LoadingControl({ position: 'topleft' });
|
||||
this.map.addControl(loadingControl);
|
||||
// const loadingControl = new LoadingControl({ position: 'topleft' });
|
||||
// this.map.addControl(loadingControl);
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
api_key: this.apiKey,
|
||||
start_date: startDate,
|
||||
end_date: endDate
|
||||
});
|
||||
// try {
|
||||
// const params = new URLSearchParams({
|
||||
// api_key: this.apiKey,
|
||||
// start_date: startDate,
|
||||
// end_date: endDate
|
||||
// });
|
||||
|
||||
const response = await fetch(`/api/v1/photos?${params}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
// const response = await fetch(`/api/v1/photos?${params}`);
|
||||
// if (!response.ok) {
|
||||
// throw new Error(`HTTP error! status: ${response.status}`);
|
||||
// }
|
||||
|
||||
const photos = await response.json();
|
||||
this.photoMarkers.clearLayers();
|
||||
// const photos = await response.json();
|
||||
// this.photoMarkers.clearLayers();
|
||||
|
||||
// Create a promise for each photo to track when it's fully loaded
|
||||
const photoLoadPromises = photos.map(photo => {
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image();
|
||||
const thumbnailUrl = `/api/v1/photos/${photo.id}/thumbnail.jpg?api_key=${this.apiKey}`;
|
||||
// // Create a promise for each photo to track when it's fully loaded
|
||||
// const photoLoadPromises = photos.map(photo => {
|
||||
// return new Promise((resolve) => {
|
||||
// const img = new Image();
|
||||
// const thumbnailUrl = `/api/v1/photos/${photo.id}/thumbnail.jpg?api_key=${this.apiKey}`;
|
||||
|
||||
img.onload = () => {
|
||||
this.createPhotoMarker(photo);
|
||||
resolve();
|
||||
};
|
||||
// img.onload = () => {
|
||||
// this.createPhotoMarker(photo);
|
||||
// resolve();
|
||||
// };
|
||||
|
||||
img.onerror = () => {
|
||||
console.error(`Failed to load photo ${photo.id}`);
|
||||
resolve(); // Resolve anyway to not block other photos
|
||||
};
|
||||
// img.onerror = () => {
|
||||
// console.error(`Failed to load photo ${photo.id}`);
|
||||
// resolve(); // Resolve anyway to not block other photos
|
||||
// };
|
||||
|
||||
img.src = thumbnailUrl;
|
||||
});
|
||||
});
|
||||
// img.src = thumbnailUrl;
|
||||
// });
|
||||
// });
|
||||
|
||||
// Wait for all photos to be loaded and rendered
|
||||
await Promise.all(photoLoadPromises);
|
||||
// // Wait for all photos to be loaded and rendered
|
||||
// await Promise.all(photoLoadPromises);
|
||||
|
||||
if (!this.map.hasLayer(this.photoMarkers)) {
|
||||
this.photoMarkers.addTo(this.map);
|
||||
}
|
||||
// if (!this.map.hasLayer(this.photoMarkers)) {
|
||||
// this.photoMarkers.addTo(this.map);
|
||||
// }
|
||||
|
||||
// Show checkmark for 1 second before removing
|
||||
const loadingSpinner = document.querySelector('.loading-spinner');
|
||||
loadingSpinner.classList.add('done');
|
||||
// // Show checkmark for 1 second before removing
|
||||
// const loadingSpinner = document.querySelector('.loading-spinner');
|
||||
// loadingSpinner.classList.add('done');
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
// await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching photos:', error);
|
||||
showFlashMessage('error', 'Failed to fetch photos');
|
||||
// } catch (error) {
|
||||
// console.error('Error fetching photos:', error);
|
||||
// showFlashMessage('error', 'Failed to fetch photos');
|
||||
|
||||
if (retryCount < MAX_RETRIES) {
|
||||
console.log(`Retrying in ${RETRY_DELAY/1000} seconds... (Attempt ${retryCount + 1}/${MAX_RETRIES})`);
|
||||
setTimeout(() => {
|
||||
this.fetchAndDisplayPhotos(startDate, endDate, retryCount + 1);
|
||||
}, RETRY_DELAY);
|
||||
} else {
|
||||
showFlashMessage('error', 'Failed to fetch photos after multiple attempts');
|
||||
}
|
||||
} finally {
|
||||
// Remove loading control after the delay
|
||||
this.map.removeControl(loadingControl);
|
||||
}
|
||||
}
|
||||
// if (retryCount < MAX_RETRIES) {
|
||||
// console.log(`Retrying in ${RETRY_DELAY/1000} seconds... (Attempt ${retryCount + 1}/${MAX_RETRIES})`);
|
||||
// setTimeout(() => {
|
||||
// this.fetchAndDisplayPhotos(startDate, endDate, retryCount + 1);
|
||||
// }, RETRY_DELAY);
|
||||
// } else {
|
||||
// showFlashMessage('error', 'Failed to fetch photos after multiple attempts');
|
||||
// }
|
||||
// } finally {
|
||||
// // Remove loading control after the delay
|
||||
// this.map.removeControl(loadingControl);
|
||||
// }
|
||||
// }
|
||||
|
||||
createPhotoMarker(photo) {
|
||||
if (!photo.exifInfo?.latitude || !photo.exifInfo?.longitude) return;
|
||||
|
|
|
|||
139
app/javascript/controllers/trips_controller.js
Normal file
139
app/javascript/controllers/trips_controller.js
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
import { Controller } from "@hotwired/stimulus"
|
||||
import L from "leaflet"
|
||||
import { osmMapLayer } from "../maps/layers"
|
||||
import { createPopupContent } from "../maps/popups"
|
||||
import { osmHotMapLayer } from "../maps/layers"
|
||||
import { OPNVMapLayer } from "../maps/layers"
|
||||
import { openTopoMapLayer } from "../maps/layers"
|
||||
import { cyclOsmMapLayer } from "../maps/layers"
|
||||
import { esriWorldStreetMapLayer } from "../maps/layers"
|
||||
import { esriWorldTopoMapLayer } from "../maps/layers"
|
||||
import { esriWorldImageryMapLayer } from "../maps/layers"
|
||||
import { esriWorldGrayCanvasMapLayer } from "../maps/layers"
|
||||
// import { fetchAndDisplayPhotos } from "../helpers/photoFetcher";
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["container"]
|
||||
|
||||
connect() {
|
||||
this.coordinates = JSON.parse(this.element.dataset.coordinates)
|
||||
this.apiKey = this.element.dataset.api_key
|
||||
this.userSettings = JSON.parse(this.element.dataset.user_settings)
|
||||
this.timezone = this.element.dataset.timezone
|
||||
this.distanceUnit = this.element.dataset.distance_unit
|
||||
|
||||
// Initialize layer groups
|
||||
this.markersLayer = L.layerGroup()
|
||||
this.polylinesLayer = L.layerGroup()
|
||||
this.photoMarkers = L.layerGroup()
|
||||
|
||||
const center = [this.coordinates[0][0], this.coordinates[0][1]]
|
||||
|
||||
// Initialize map
|
||||
this.map = L.map(this.containerTarget).setView(center, 14)
|
||||
|
||||
// Add base map layer
|
||||
osmMapLayer(this.map, "OpenStreetMap")
|
||||
|
||||
// Add scale control to bottom right
|
||||
L.control.scale({
|
||||
position: 'bottomright',
|
||||
imperial: this.distanceUnit === 'mi',
|
||||
metric: this.distanceUnit === 'km',
|
||||
maxWidth: 120
|
||||
}).addTo(this.map)
|
||||
|
||||
const overlayMaps = {
|
||||
"Points": this.markersLayer,
|
||||
"Route": this.polylinesLayer,
|
||||
"Photos": this.photoMarkers
|
||||
}
|
||||
|
||||
// Add layer control
|
||||
L.control.layers(this.baseMaps(), overlayMaps).addTo(this.map)
|
||||
|
||||
// Add markers for each coordinate
|
||||
if (this.coordinates?.length > 0) {
|
||||
this.addMarkers()
|
||||
this.addPolyline()
|
||||
this.fitMapToBounds()
|
||||
}
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (this.map) {
|
||||
this.map.remove()
|
||||
}
|
||||
}
|
||||
|
||||
baseMaps() {
|
||||
let selectedLayerName = this.userSettings.preferred_map_layer || "OpenStreetMap";
|
||||
|
||||
return {
|
||||
OpenStreetMap: osmMapLayer(this.map, selectedLayerName),
|
||||
"OpenStreetMap.HOT": osmHotMapLayer(this.map, selectedLayerName),
|
||||
OPNV: OPNVMapLayer(this.map, selectedLayerName),
|
||||
openTopo: openTopoMapLayer(this.map, selectedLayerName),
|
||||
cyclOsm: cyclOsmMapLayer(this.map, selectedLayerName),
|
||||
esriWorldStreet: esriWorldStreetMapLayer(this.map, selectedLayerName),
|
||||
esriWorldTopo: esriWorldTopoMapLayer(this.map, selectedLayerName),
|
||||
esriWorldImagery: esriWorldImageryMapLayer(this.map, selectedLayerName),
|
||||
esriWorldGrayCanvas: esriWorldGrayCanvasMapLayer(this.map, selectedLayerName)
|
||||
};
|
||||
}
|
||||
|
||||
addMarkers() {
|
||||
this.coordinates.forEach(coord => {
|
||||
const marker = L.circleMarker([coord[0], coord[1]], {radius: 4})
|
||||
|
||||
const popupContent = createPopupContent(coord, this.timezone, this.distanceUnit)
|
||||
marker.bindPopup(popupContent)
|
||||
|
||||
// Add to markers layer instead of directly to map
|
||||
this.markersLayer.addTo(this.map)
|
||||
marker.addTo(this.markersLayer)
|
||||
})
|
||||
}
|
||||
|
||||
addPolyline() {
|
||||
const points = this.coordinates.map(coord => [coord[0], coord[1]])
|
||||
const polyline = L.polyline(points, {
|
||||
color: 'blue',
|
||||
weight: 3,
|
||||
opacity: 0.6
|
||||
})
|
||||
// Add to polylines layer instead of directly to map
|
||||
this.polylinesLayer.addTo(this.map)
|
||||
polyline.addTo(this.polylinesLayer)
|
||||
}
|
||||
|
||||
fitMapToBounds() {
|
||||
const bounds = L.latLngBounds(
|
||||
this.coordinates.map(coord => [coord[0], coord[1]])
|
||||
)
|
||||
this.map.fitBounds(bounds, { padding: [50, 50] })
|
||||
}
|
||||
|
||||
baseMaps() {
|
||||
let selectedLayerName = this.userSettings.preferred_map_layer || "OpenStreetMap";
|
||||
|
||||
return {
|
||||
OpenStreetMap: osmMapLayer(this.map, selectedLayerName),
|
||||
"OpenStreetMap.HOT": osmHotMapLayer(this.map, selectedLayerName),
|
||||
OPNV: OPNVMapLayer(this.map, selectedLayerName),
|
||||
openTopo: openTopoMapLayer(this.map, selectedLayerName),
|
||||
cyclOsm: cyclOsmMapLayer(this.map, selectedLayerName),
|
||||
esriWorldStreet: esriWorldStreetMapLayer(this.map, selectedLayerName),
|
||||
esriWorldTopo: esriWorldTopoMapLayer(this.map, selectedLayerName),
|
||||
esriWorldImagery: esriWorldImageryMapLayer(this.map, selectedLayerName),
|
||||
esriWorldGrayCanvas: esriWorldGrayCanvasMapLayer(this.map, selectedLayerName)
|
||||
};
|
||||
}
|
||||
|
||||
someMethod() {
|
||||
// Example usage
|
||||
const startDate = '2023-01-01';
|
||||
const endDate = '2023-12-31';
|
||||
fetchAndDisplayPhotos(this.map, this.apiKey, this.photoMarkers, startDate, endDate);
|
||||
}
|
||||
}
|
||||
|
|
@ -136,3 +136,131 @@ function classesForFlash(type) {
|
|||
return 'bg-blue-100 text-blue-700 border-blue-300';
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchAndDisplayPhotos({ map, photoMarkers, apiKey, startDate, endDate, userSettings }, retryCount = 0) {
|
||||
const MAX_RETRIES = 3;
|
||||
const RETRY_DELAY = 3000; // 3 seconds
|
||||
|
||||
// Create loading control
|
||||
const LoadingControl = L.Control.extend({
|
||||
onAdd: (map) => {
|
||||
const container = L.DomUtil.create('div', 'leaflet-loading-control');
|
||||
container.innerHTML = '<div class="loading-spinner"></div>';
|
||||
return container;
|
||||
}
|
||||
});
|
||||
|
||||
const loadingControl = new LoadingControl({ position: 'topleft' });
|
||||
map.addControl(loadingControl);
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
api_key: apiKey,
|
||||
start_date: startDate,
|
||||
end_date: endDate
|
||||
});
|
||||
|
||||
const response = await fetch(`/api/v1/photos?${params}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const photos = await response.json();
|
||||
photoMarkers.clearLayers();
|
||||
|
||||
const photoLoadPromises = photos.map(photo => {
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image();
|
||||
const thumbnailUrl = `/api/v1/photos/${photo.id}/thumbnail.jpg?api_key=${apiKey}`;
|
||||
|
||||
img.onload = () => {
|
||||
createPhotoMarker(photo, userSettings.immich_url, photoMarkers, apiKey);
|
||||
resolve();
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
console.error(`Failed to load photo ${photo.id}`);
|
||||
resolve(); // Resolve anyway to not block other photos
|
||||
};
|
||||
|
||||
img.src = thumbnailUrl;
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(photoLoadPromises);
|
||||
|
||||
if (!map.hasLayer(photoMarkers)) {
|
||||
photoMarkers.addTo(map);
|
||||
}
|
||||
|
||||
// Show checkmark for 1 second before removing
|
||||
const loadingSpinner = document.querySelector('.loading-spinner');
|
||||
loadingSpinner.classList.add('done');
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching photos:', error);
|
||||
showFlashMessage('error', 'Failed to fetch photos');
|
||||
|
||||
if (retryCount < MAX_RETRIES) {
|
||||
console.log(`Retrying in ${RETRY_DELAY/1000} seconds... (Attempt ${retryCount + 1}/${MAX_RETRIES})`);
|
||||
setTimeout(() => {
|
||||
fetchAndDisplayPhotos({ map, photoMarkers, apiKey, startDate, endDate }, retryCount + 1);
|
||||
}, RETRY_DELAY);
|
||||
} else {
|
||||
showFlashMessage('error', 'Failed to fetch photos after multiple attempts');
|
||||
}
|
||||
} finally {
|
||||
map.removeControl(loadingControl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function createPhotoMarker(photo, immichUrl, photoMarkers,apiKey) {
|
||||
if (!photo.exifInfo?.latitude || !photo.exifInfo?.longitude) return;
|
||||
|
||||
const thumbnailUrl = `/api/v1/photos/${photo.id}/thumbnail.jpg?api_key=${apiKey}`;
|
||||
|
||||
const icon = L.divIcon({
|
||||
className: 'photo-marker',
|
||||
html: `<img src="${thumbnailUrl}" style="width: 48px; height: 48px;">`,
|
||||
iconSize: [48, 48]
|
||||
});
|
||||
|
||||
const marker = L.marker(
|
||||
[photo.exifInfo.latitude, photo.exifInfo.longitude],
|
||||
{ icon }
|
||||
);
|
||||
|
||||
const startOfDay = new Date(photo.localDateTime);
|
||||
startOfDay.setHours(0, 0, 0, 0);
|
||||
|
||||
const endOfDay = new Date(photo.localDateTime);
|
||||
endOfDay.setHours(23, 59, 59, 999);
|
||||
|
||||
const queryParams = {
|
||||
takenAfter: startOfDay.toISOString(),
|
||||
takenBefore: endOfDay.toISOString()
|
||||
};
|
||||
const encodedQuery = encodeURIComponent(JSON.stringify(queryParams));
|
||||
const immich_photo_link = `${immichUrl}/search?query=${encodedQuery}`;
|
||||
const popupContent = `
|
||||
<div class="max-w-xs">
|
||||
<a href="${immich_photo_link}" target="_blank" onmouseover="this.firstElementChild.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.3)';"
|
||||
onmouseout="this.firstElementChild.style.boxShadow = '';">
|
||||
<img src="${thumbnailUrl}"
|
||||
class="w-8 h-8 mb-2 rounded"
|
||||
style="transition: box-shadow 0.3s ease;"
|
||||
alt="${photo.originalFileName}">
|
||||
</a>
|
||||
<h3 class="font-bold">${photo.originalFileName}</h3>
|
||||
<p>Taken: ${new Date(photo.localDateTime).toLocaleString()}</p>
|
||||
<p>Location: ${photo.exifInfo.city}, ${photo.exifInfo.state}, ${photo.exifInfo.country}</p>
|
||||
${photo.type === 'VIDEO' ? '🎥 Video' : '📷 Photo'}
|
||||
</div>
|
||||
`;
|
||||
marker.bindPopup(popupContent);
|
||||
|
||||
photoMarkers.addLayer(marker);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,4 +4,12 @@ class Trip < ApplicationRecord
|
|||
belongs_to :user
|
||||
|
||||
validates :name, :started_at, :ended_at, presence: true
|
||||
|
||||
def points
|
||||
user.points.where(timestamp: started_at.to_i..ended_at.to_i).order(:timestamp)
|
||||
end
|
||||
|
||||
def countries
|
||||
points.pluck(:country).uniq.compact
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,14 +4,33 @@
|
|||
<!-- Header Section -->
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-4xl font-bold mb-2"><%= @trip.name %></h1>
|
||||
<p class="text-lg text-base-content/60">Countries visited: [Placeholder]</p>
|
||||
<p class="text-md text-base-content/60">
|
||||
<%= human_date(@trip.started_at) %> - <%= human_date(@trip.ended_at) %>
|
||||
</p>
|
||||
<% if @trip.countries.any? %>
|
||||
<p class="text-lg text-base-content/60">
|
||||
<%= @trip.countries.join(', ') %>
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<!-- Map and Description Section -->
|
||||
<div class="bg-base-100 mb-8">
|
||||
<div class="p-6 grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="w-full">
|
||||
<div id="map" class="w-full h-96 bg-base-200">Map Placeholder</div>
|
||||
<div
|
||||
id='map'
|
||||
class="w-full h-full"
|
||||
data-controller="trips"
|
||||
data-trips-target="container"
|
||||
data-distance_unit="<%= DISTANCE_UNIT %>"
|
||||
data-api_key="<%= current_user.api_key %>"
|
||||
data-user_settings="<%= current_user.settings.to_json %>"
|
||||
data-coordinates="<%= @coordinates.to_json %>"
|
||||
data-timezone="<%= Rails.configuration.time_zone %>">
|
||||
<div data-trips-target="container" class="h-[25rem] w-full min-h-screen">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class="prose">
|
||||
|
|
@ -54,7 +73,7 @@
|
|||
<%= link_to "Destroy this trip",
|
||||
trip_path(@trip),
|
||||
data: {
|
||||
turbo_confirm: "Are you sure? This action will delete all points imported with this file",
|
||||
turbo_confirm: "Are you sure?",
|
||||
turbo_method: :delete
|
||||
},
|
||||
class: "btn" %>
|
||||
|
|
|
|||
Loading…
Reference in a new issue