1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-07-23 06:49:37 +02:00

Refactor map page

This commit is contained in:
Sean Morley 2024-11-02 21:18:52 -04:00
parent e6c5bc9ca8
commit 07263c5697
7 changed files with 127 additions and 182 deletions

View file

@ -1162,4 +1162,3 @@ class ReverseGeocodeViewSet(viewsets.ViewSet):
new_region_count += 1 new_region_count += 1
new_regions[region.id] = region.name new_regions[region.id] = region.name
return Response({"new_regions": new_region_count, "regions": new_regions}) return Response({"new_regions": new_region_count, "regions": new_regions})

View file

@ -43,7 +43,7 @@ services:
- DEBUG=False - DEBUG=False
- FRONTEND_URL='http://localhost:8015' # Used for email generation. This should be the url of the frontend - FRONTEND_URL='http://localhost:8015' # Used for email generation. This should be the url of the frontend
ports: ports:
- "8016:80" # User can change this to any outward port without breaking the setup - "8016:80"
depends_on: depends_on:
- db - db
volumes: volumes:

View file

@ -70,7 +70,8 @@
images: adventureToEdit?.images || [], images: adventureToEdit?.images || [],
user_id: adventureToEdit?.user_id || null, user_id: adventureToEdit?.user_id || null,
collection: adventureToEdit?.collection || collection?.id || null, collection: adventureToEdit?.collection || collection?.id || null,
visits: adventureToEdit?.visits || [] visits: adventureToEdit?.visits || [],
is_visited: adventureToEdit?.is_visited || false
}; };
let markers: Point[] = []; let markers: Point[] = [];

View file

@ -253,6 +253,39 @@ export let ADVENTURE_TYPES = [
{ type: 'other', label: 'Other' } { type: 'other', label: 'Other' }
]; ];
// adventure type to icon mapping
export let ADVENTURE_TYPE_ICONS = {
general: '🌍',
outdoor: '🏞️',
lodging: '🛌',
dining: '🍽️',
activity: '🏄',
attraction: '🎢',
shopping: '🛍️',
nightlife: '🌃',
event: '🎉',
transportation: '🚗',
culture: '🎭',
water_sports: '🚤',
hiking: '🥾',
wildlife: '🦒',
historical_sites: '🏛️',
music_concerts: '🎶',
fitness: '🏋️',
art_museums: '🎨',
festivals: '🎪',
spiritual_journeys: '🧘‍♀️',
volunteer_work: '🤝',
other: '❓'
};
type AdventureType = keyof typeof ADVENTURE_TYPE_ICONS;
export function getAdventureTypeLabel(type: AdventureType) {
const typeObj = ADVENTURE_TYPE_ICONS[type];
return typeObj;
}
export function getRandomBackground() { export function getRandomBackground() {
const randomIndex = Math.floor(Math.random() * randomBackgrounds.backgrounds.length); const randomIndex = Math.floor(Math.random() * randomBackgrounds.backgrounds.length);
return randomBackgrounds.backgrounds[randomIndex] as Background; return randomBackgrounds.backgrounds[randomIndex] as Background;

View file

@ -63,6 +63,9 @@ export type VisitedRegion = {
id: number; id: number;
region: number; region: number;
user_id: number; user_id: number;
longitude: number;
latitude: number;
name: string;
}; };
export type Point = { export type Point = {

View file

@ -19,43 +19,21 @@ export const load = (async (event) => {
Cookie: `${event.cookies.get('auth')}` Cookie: `${event.cookies.get('auth')}`
} }
}); });
let visitedRegions = (await visitedRegionsFetch.json()) as VisitedRegion[];
if (!visitedFetch.ok) { let visitedRegions = (await visitedRegionsFetch.json()) as VisitedRegion[];
let adventures = (await visitedFetch.json()) as Adventure[];
if (!visitedRegionsFetch.ok) {
console.error('Failed to fetch visited regions');
return redirect(302, '/login');
} else if (!visitedFetch.ok) {
console.error('Failed to fetch visited adventures'); console.error('Failed to fetch visited adventures');
return redirect(302, '/login'); return redirect(302, '/login');
} else { } else {
let visited: Adventure[] = [];
try {
let api_result = await visitedFetch.json();
visited = api_result as Adventure[];
if (!Array.isArray(visited) || visited.length === 0 || !visited) {
throw new Error('Visited adventures response is not an array');
}
} catch (error) {
console.error('Error parsing visited adventures:', error);
return redirect(302, '/login');
}
// make a long lat array like this { lngLat: [-20, 0], name: 'Adventure 1' },
let markers = visited
.filter((adventure) => adventure.latitude !== null && adventure.longitude !== null)
.map((adventure) => {
return {
lngLat: [adventure.longitude, adventure.latitude],
name: adventure.name,
visits: adventure.visits,
type: adventure.type,
is_visited: adventure.is_visited
};
});
console.log('sent');
return { return {
props: { props: {
markers, visitedRegions,
visitedRegions adventures
} }
}; };
} }

View file

@ -1,91 +1,53 @@
<script> <script lang="ts">
// @ts-nocheck
import AdventureModal from '$lib/components/AdventureModal.svelte'; import AdventureModal from '$lib/components/AdventureModal.svelte';
import { import { DefaultMarker, MapEvents, MapLibre, Popup, Marker } from 'svelte-maplibre';
DefaultMarker,
MapEvents,
MapLibre,
Popup,
Marker,
GeoJSON,
LineLayer,
FillLayer,
SymbolLayer
} from 'svelte-maplibre';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import type { Adventure, VisitedRegion } from '$lib/types.js';
export let data; export let data;
let clickedName = ''; let createModalOpen: boolean = false;
let showGeo: boolean = false;
let visitedRegions: VisitedRegion[] = data.props.visitedRegions;
let adventures: Adventure[] = data.props.adventures;
let filteredAdventures = adventures;
// Updates the filtered adventures based on the checkboxes
$: {
filteredAdventures = adventures.filter(
(adventure) => (showVisited && adventure.is_visited) || (showPlanned && !adventure.is_visited)
);
}
console.log(data); console.log(data);
let showVisited = true; let showVisited: boolean = true;
let showPlanned = true; let showPlanned: boolean = true;
$: filteredMarkers = markers.filter( let newMarker: { lngLat: any } | null = null;
(marker) => (showVisited && marker.is_visited) || (showPlanned && !marker.is_visited)
);
let newMarker = []; let newLongitude: number | null = null;
let newLatitude: number | null = null;
let newLongitude = null; function addMarker(e: { detail: { lngLat: { lng: any; lat: any } } }) {
let newLatitude = null; newMarker = null;
newMarker = { lngLat: e.detail.lngLat };
function addMarker(e) {
newMarker = [];
newMarker = [...newMarker, { lngLat: e.detail.lngLat, name: 'Marker 1' }];
newLongitude = e.detail.lngLat.lng; newLongitude = e.detail.lngLat.lng;
newLatitude = e.detail.lngLat.lat; newLatitude = e.detail.lngLat.lat;
} }
let markers = []; // let markers = [];
$: { // $: {
markers = data.props.markers; // markers = data.props.markers;
} // }
function createNewAdventure(event) { function createNewAdventure(event: CustomEvent) {
let newMarker = { adventures = [...adventures, event.detail];
lngLat: [event.detail.longitude, event.detail.latitude], newMarker = null;
name: event.detail.name,
type: event.detail.type,
visits: event.detail.visits
};
markers = [...markers, newMarker];
clearMarkers();
createModalOpen = false; createModalOpen = false;
} }
let visitedRegions = data.props.visitedRegions;
let allRegions = [];
let visitArray = [];
// turns in into an array of the visits
visitedRegions.forEach((el) => {
visitArray.push(el.region);
});
function clearMarkers() {
newMarker = [];
newLatitude = null;
newLongitude = null;
}
// mapped to the checkbox
let showGEO = false;
$: {
if (showGEO && allRegions.length === 0) {
(async () => {
allRegions = await fetch('/api/visitedregion/').then((res) => res.json());
})();
} else if (!showGEO) {
allRegions = [];
}
}
let createModalOpen = false;
</script> </script>
<h1 class="text-center font-bold text-4xl">Adventure Map</h1> <h1 class="text-center font-bold text-4xl">Adventure Map</h1>
@ -109,14 +71,14 @@
id="show-geo" id="show-geo"
name="show-geo" name="show-geo"
class="checkbox" class="checkbox"
bind:checked={showGEO} on:click={() => (showGeo = !showGeo)}
/> />
<!-- <div class="divider divider-horizontal"></div> --> <div class="divider divider-horizontal"></div>
{#if newMarker.length > 0} {#if newMarker}
<button type="button" class="btn btn-primary mb-2" on:click={() => (createModalOpen = true)} <button type="button" class="btn btn-primary mb-2" on:click={() => (createModalOpen = true)}
>Add New Adventure at Marker</button >Add New Adventure at Marker</button
> >
<button type="button" class="btn btn-neutral mb-2" on:click={clearMarkers} <button type="button" class="btn btn-neutral mb-2" on:click={() => (newMarker = null)}
>Clear Marker</button >Clear Marker</button
> >
{:else} {:else}
@ -142,12 +104,13 @@
class="relative aspect-[9/16] max-h-[70vh] w-full sm:aspect-video sm:max-h-full" class="relative aspect-[9/16] max-h-[70vh] w-full sm:aspect-video sm:max-h-full"
standardControls standardControls
> >
{#each filteredMarkers as marker} {#each filteredAdventures as adventure}
{#if marker.is_visited} {#if adventure.latitude && adventure.longitude}
<Marker <Marker
lngLat={marker.lngLat} lngLat={[adventure.longitude, adventure.latitude]}
on:click={() => (clickedName = marker.name)} class="grid h-8 w-8 place-items-center rounded-full border border-gray-200 bg-{adventure.is_visited
class="grid h-8 w-8 place-items-center rounded-full border border-gray-200 bg-red-300 text-black shadow-md" ? 'red'
: 'blue'}-300 text-black shadow-md"
> >
<svg <svg
width="24" width="24"
@ -156,58 +119,26 @@
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<circle cx="12" cy="12" r="10" stroke="red" stroke-width="2" fill="red" /> <circle
cx="12"
cy="12"
r="10"
stroke={adventure.is_visited ? 'red' : 'blue'}
stroke-width="2"
fill={adventure.is_visited ? 'red' : 'blue'}
/>
</svg> </svg>
<Popup openOn="click" offset={[0, -10]}> <Popup openOn="click" offset={[0, -10]}>
<div class="text-lg text-black font-bold">{marker.name}</div> <div class="text-lg text-black font-bold">{adventure.name}</div>
<p class="font-semibold text-black text-md">Visited</p>
<p class="font-semibold text-black text-md"> <p class="font-semibold text-black text-md">
{$t(`adventures.activities.${marker.type}`)} {adventure.is_visited ? $t('adventures.visited') : $t('adventures.planned')}
</p> </p>
{#if marker.visits && marker.visits.length > 0}
<p class="text-black text-sm">
{#each marker.visits as visit}
{visit.start_date
? new Date(visit.start_date).toLocaleDateString(undefined, {
timeZone: 'UTC'
})
: ''}
{visit.end_date && visit.end_date !== '' && visit.end_date !== visit.start_date
? ' - ' +
new Date(visit.end_date).toLocaleDateString(undefined, {
timeZone: 'UTC'
})
: ''}
<br />
{/each}
</p>
{/if}
</Popup>
</Marker>
{:else}
<Marker
lngLat={marker.lngLat}
on:click={() => (clickedName = marker.name)}
class="grid h-8 w-8 place-items-center rounded-full border border-gray-200 bg-blue-300 text-black shadow-2xl focus:outline-2 focus:outline-black"
>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="12" cy="12" r="10" stroke="blue" stroke-width="2" fill="blue" />
</svg>
<Popup openOn="click" offset={[0, -10]}>
<div class="text-lg text-black font-bold">{marker.name}</div>
<p class="font-semibold text-black text-md">Planned</p>
<p class="font-semibold text-black text-md"> <p class="font-semibold text-black text-md">
{$t(`adventures.activities.${marker.type}`)} {$t(`adventures.activities.${adventure.type}`)}
</p> </p>
{#if marker.visits && marker.visits.length > 0} {#if adventure.visits && adventure.visits.length > 0}
<p class="text-black text-sm"> <p class="text-black text-sm">
{#each marker.visits as visit} {#each adventure.visits as visit}
{visit.start_date {visit.start_date
? new Date(visit.start_date).toLocaleDateString(undefined, { ? new Date(visit.start_date).toLocaleDateString(undefined, {
timeZone: 'UTC' timeZone: 'UTC'
@ -229,14 +160,14 @@
{/each} {/each}
<MapEvents on:click={addMarker} /> <MapEvents on:click={addMarker} />
{#each newMarker as marker} {#if newMarker}
<DefaultMarker lngLat={marker.lngLat} /> <DefaultMarker lngLat={newMarker.lngLat} />
{/each} {/if}
{#each allRegions as { longitude, latitude, name, region }} {#each visitedRegions as region}
{#if showGeo}
<Marker <Marker
lngLat={[longitude, latitude]} lngLat={[region.latitude, region.longitude]}
on:click={() => (clickedName = name)}
class="grid h-8 w-8 place-items-center rounded-full border border-gray-200 bg-green-300 text-black shadow-md" class="grid h-8 w-8 place-items-center rounded-full border border-gray-200 bg-green-300 text-black shadow-md"
> >
<svg <svg
@ -246,14 +177,14 @@
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<!-- green circle -->
<circle cx="12" cy="12" r="10" stroke="green" stroke-width="2" fill="green" /> <circle cx="12" cy="12" r="10" stroke="green" stroke-width="2" fill="green" />
</svg> </svg>
<Popup openOn="click" offset={[0, -10]}> <Popup openOn="click" offset={[0, -10]}>
<div class="text-lg text-black font-bold">{name}</div> <div class="text-lg text-black font-bold">{name}</div>
<p class="font-semibold text-black text-md">{region}</p> <p class="font-semibold text-black text-md">{region.name}</p>
</Popup> </Popup>
</Marker> </Marker>
{/if}
{/each} {/each}
</MapLibre> </MapLibre>