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:
parent
e6c5bc9ca8
commit
07263c5697
7 changed files with 127 additions and 182 deletions
|
@ -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})
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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[] = [];
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue