diff --git a/src/lib/components/NewMemoryPopup.svelte b/src/lib/components/NewMemoryPopup.svelte
index 2625cfc..b7323de 100644
--- a/src/lib/components/NewMemoryPopup.svelte
+++ b/src/lib/components/NewMemoryPopup.svelte
@@ -255,8 +255,6 @@
{#if showImageError}
diff --git a/src/lib/components/WorldMap.svelte b/src/lib/components/WorldMap.svelte
index 7a764be..5c4073a 100644
--- a/src/lib/components/WorldMap.svelte
+++ b/src/lib/components/WorldMap.svelte
@@ -15,40 +15,46 @@
lat: number;
lng: number;
};
+ startDate: string;
+ endDate: string;
}
- async function getPastTripLocations(): Promise {
+ function formatDate(dateStr: string): string {
+ const date = new Date(dateStr);
+ return `${date.getDate().toString().padStart(2, '0')}.${(date.getMonth() + 1)
+ .toString()
+ .padStart(2, '0')}.${date.getFullYear()}`;
+ }
+
+ async function getPastTripLocations(): Promise> {
try {
const tripsRef = ref(db, 'trips');
const snapshot = await get(tripsRef);
-
- if (!snapshot.exists()) return [];
+ if (!snapshot.exists()) return {};
- // Get today's date at midnight for comparison
const today = new Date();
today.setHours(0, 0, 0, 0);
- // Create a Set to store unique locations
- const uniqueLocations = new Map();
+ const locationMap: Record = {};
- // Filter past trips and extract unique destinations
Object.values(snapshot.val()).forEach((trip: any) => {
const endDate = new Date(trip.endDate);
if (endDate < today && trip.destination?.location) {
- const locationKey = `${trip.destination.location.lat},${trip.destination.location.lng}`;
- if (!uniqueLocations.has(locationKey)) {
- uniqueLocations.set(locationKey, {
- name: trip.destination.name,
- location: trip.destination.location
- });
- }
+ const key = `${trip.destination.location.lat},${trip.destination.location.lng}`;
+ if (!locationMap[key]) locationMap[key] = [];
+ locationMap[key].push({
+ name: trip.destination.name,
+ location: trip.destination.location,
+ startDate: trip.startDate,
+ endDate: trip.endDate
+ });
}
});
- return Array.from(uniqueLocations.values());
+ return locationMap;
} catch (error) {
console.error('Error fetching past trips:', error);
- return [];
+ return {};
}
}
@@ -63,7 +69,6 @@
const width = mapContainer.clientWidth;
const height = mapContainer.clientHeight;
- // Create SVG
const svg = d3.select(mapContainer)
.append('svg')
.attr('width', '100%')
@@ -71,30 +76,24 @@
.attr('viewBox', `0 0 ${width} ${height}`)
.attr('preserveAspectRatio', 'xMidYMid meet') as d3.Selection;
- // Add a group for all map elements that will be transformed
const g = svg.append('g');
- // Create projection
const projection = d3.geoMercator()
.scale(width / (2 * Math.PI))
- .translate([width / 2, height / 1.6]); // position the map, horizontally centered but is slighty upward
+ .translate([width / 2, height / 1.6]);
const path = d3.geoPath().projection(projection);
try {
- // Get past trip locations
- const pastLocations = await getPastTripLocations();
+ const locationMap = await getPastTripLocations();
if (!mounted) return;
- // Load world map data
const response = await fetch('https://unpkg.com/world-atlas@2/countries-110m.json');
if (!mounted) return;
const world = await response.json();
-
- // Convert TopoJSON to GeoJSON
+
const countries = feature(world, world.objects.countries) as any;
- // Draw the map
g.append('g')
.selectAll('path')
.data(countries.features)
@@ -106,7 +105,8 @@
.attr('stroke', Colors.gray.light50)
.attr('stroke-width', '0.5');
- // Add markers for past trip locations
+ const pastLocations = Object.values(locationMap).map(trips => trips[0]);
+
g.selectAll('circle')
.data(pastLocations)
.enter()
@@ -116,8 +116,69 @@
.attr('r', 5)
.attr('class', 'marker')
.attr('fill', Colors.planner.med400)
+ .on('click', function (event, d) {
+ d3.selectAll('.trip-label').remove();
+ d3.selectAll('.trip-marker').remove();
+ d3.selectAll('.trip-line').remove();
+ event.stopPropagation();
+
+ const key = `${d.location.lat},${d.location.lng}`;
+ const trips = locationMap[key];
+
+ if (trips && trips.length > 0) {
+ const baseX = projection([d.location.lng, d.location.lat])![0];
+ const baseY = projection([d.location.lng, d.location.lat])![1];
+
+ trips.sort((a, b) => new Date(a.endDate).getTime() - new Date(b.endDate).getTime());
+
+ if (trips.length > 1) {
+ g.append('line')
+ .attr('x1', baseX)
+ .attr('y1', baseY + 5)
+ .attr('x2', baseX)
+ .attr('y2', baseY - (trips.length - 1) * 24 + 6)
+ .attr('stroke', Colors.planner.med400)
+ .attr('stroke-width', 2)
+ .attr('class', 'trip-line');
+ }
+
+ trips.forEach((trip, idx) => {
+ const offsetY = idx * 24;
+
+ const markerGroup = g.append('g')
+ .attr('class', 'trip-group')
+ .on('mouseover', function () {
+ d3.select(this).select('circle').transition().duration(200).attr('r', 7);
+ d3.select(this).select('text').transition().duration(200).attr('fill', Colors.planner.med400);
+ })
+ .on('mouseout', function () {
+ d3.select(this).select('circle').transition().duration(200).attr('r', 5);
+ d3.select(this).select('text').transition().duration(200).attr('fill', Colors.black);
+ });
+
+ markerGroup.append('circle')
+ .attr('cx', baseX)
+ .attr('cy', baseY - offsetY)
+ .attr('r', 5)
+ .attr('fill', Colors.planner.med400)
+ .attr('class', 'trip-marker');
+
+ markerGroup.append('text')
+ .attr('x', baseX + 10)
+ .attr('y', baseY - offsetY + 4)
+ .attr('font-size', '12px')
+ .attr('class', 'trip-label')
+ .text(`${formatDate(trip.startDate)} - ${formatDate(trip.endDate)}`);
+ });
+ }
+ });
+
+ svg.on('click', () => {
+ d3.selectAll('.trip-label').remove();
+ d3.selectAll('.trip-marker').remove();
+ d3.selectAll('.trip-line').remove();
+ });
- // Add zoom behavior
const zoom = d3.zoom()
.scaleExtent([1, 8])
.on('zoom', (event) => {
@@ -168,24 +229,31 @@
}
:global(.marker:hover) {
- r: 8;
+ r: 7;
cursor: pointer;
}
+ :global(.trip-label),
+ :global(.trip-marker),
+ :global(.trip-line) {
+ transition: all 0.2s ease;
+ pointer-events: all;
+ }
+
:global(.dark .map-wrapper) {
background-color: var(--black);
}
:global(.dark .country) {
- fill: #121212;
- stroke: var(--gray-700);
+ fill: #121212;
+ stroke: var(--gray-700);
}
:global(.dark .country:hover) {
- fill: #5a1c05;
+ fill: #5a1c05;
}
:global(.dark .marker) {
- fill: var(--memory-500);
+ fill: var(--memory-500);
}
-
\ No newline at end of file
+