edit marker

This commit is contained in:
Chaebean Yang 2025-06-08 05:58:08 +09:00
parent 4418783441
commit 3a889bd25f
2 changed files with 102 additions and 36 deletions

View File

@ -255,8 +255,6 @@
<uc-upload-ctx-provider
ctx-name="my-uploader"
bind:this={uploaderCtxEl}
bind:ctx={uploaderCtx}
></uc-upload-ctx-provider>
{#if showImageError}

View File

@ -15,40 +15,46 @@
lat: number;
lng: number;
};
startDate: string;
endDate: string;
}
async function getPastTripLocations(): Promise<TripLocation[]> {
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<Record<string, TripLocation[]>> {
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<string, TripLocation>();
const locationMap: Record<string, TripLocation[]> = {};
// 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<SVGSVGElement, unknown, null, undefined>;
// 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<SVGSVGElement, unknown>()
.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);
}
</style>
</style>