1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-08-02 19:55:18 +02:00

feat: add OverpassViewSet and implement osmTagToEmoji function; update requirements and aria-labels

This commit is contained in:
Sean Morley 2025-01-15 15:39:21 -05:00
parent 0588555707
commit 62efa2478e
6 changed files with 512 additions and 5 deletions

View file

@ -347,3 +347,120 @@ export let themes = [
{ name: 'aestheticDark', label: 'Aesthetic Dark' },
{ name: 'northernLights', label: 'Northern Lights' }
];
export function osmTagToEmoji(tag: string) {
switch (tag) {
case 'camp_site':
return '🏕️';
case 'slipway':
return '🛳️';
case 'playground':
return '🛝';
case 'viewpoint':
return '👀';
case 'cape':
return '🏞️';
case 'beach':
return '🏖️';
case 'park':
return '🌳';
case 'museum':
return '🏛️';
case 'theme_park':
return '🎢';
case 'nature_reserve':
return '🌲';
case 'memorial':
return '🕊️';
case 'monument':
return '🗿';
case 'wood':
return '🌲';
case 'zoo':
return '🦁';
case 'attraction':
return '🎡';
case 'ruins':
return '🏚️';
case 'bay':
return '🌊';
case 'hotel':
return '🏨';
case 'motel':
return '🏩';
case 'pub':
return '🍺';
case 'restaurant':
return '🍽️';
case 'cafe':
return '☕';
case 'bakery':
return '🥐';
case 'archaeological_site':
return '🏺';
case 'lighthouse':
return '🗼';
case 'tree':
return '🌳';
case 'cliff':
return '⛰️';
case 'water':
return '💧';
case 'fishing':
return '🎣';
case 'golf_course':
return '⛳';
case 'swimming_pool':
return '🏊';
case 'stadium':
return '🏟️';
case 'cave_entrance':
return '🕳️';
case 'anchor':
return '⚓';
case 'garden':
return '🌼';
case 'disc_golf_course':
return '🥏';
case 'natural':
return '🌿';
case 'ice_rink':
return '⛸️';
case 'horse_riding':
return '🐎';
case 'wreck':
return '🚢';
case 'water_park':
return '💦';
case 'picnic_site':
return '🧺';
case 'axe_throwing':
return '🪓';
case 'fort':
return '🏰';
case 'amusement_arcade':
return '🕹️';
case 'tepee':
return '🏕️';
case 'track':
return '🏃';
case 'trampoline_park':
return '🤸';
case 'dojo':
return '🥋';
case 'tree_stump':
return '🪵';
case 'peak':
return '🏔️';
case 'fitness_centre':
return '🏋️';
case 'artwork':
return '🎨';
case 'fast_food':
return '🍔';
case 'ice_cream':
return '🍦';
default:
return '📍'; // Default placeholder emoji for unknown tags
}
}

View file

@ -26,7 +26,8 @@
groupAdventuresByDate,
groupNotesByDate,
groupTransportationsByDate,
groupChecklistsByDate
groupChecklistsByDate,
osmTagToEmoji
} from '$lib';
import ChecklistCard from '$lib/components/ChecklistCard.svelte';
import ChecklistModal from '$lib/components/ChecklistModal.svelte';
@ -240,6 +241,42 @@
isAdventureModalOpen = false;
}
let isPopupOpen = false;
function togglePopup() {
isPopupOpen = !isPopupOpen;
}
let recomendationsData: any;
let loadingRecomendations: boolean = false;
let recomendationsRange: number = 1600;
let recomendationType: string = 'tourism';
let recomendationTags: string[] = [];
let selectedRecomendationTag: string = '';
async function getRecomendations(adventure: Adventure) {
recomendationsData = null;
loadingRecomendations = true;
let res = await fetch(
`/api/overpass/query/?lat=${adventure.latitude}&lon=${adventure.longitude}&radius=${recomendationsRange}&category=${recomendationType}`
);
if (!res.ok) {
console.log('Error fetching recommendations');
return;
}
let data = await res.json();
recomendationsData = data;
console.log(data);
if (recomendationsData) {
recomendationTags = [
...new Set(recomendationsData.map((r: any) => r.tag).filter(Boolean))
] as string[];
}
loadingRecomendations = false;
console.log(recomendationTags);
}
function saveOrCreateTransportation(event: CustomEvent<Transportation>) {
if (transportations.find((transportation) => transportation.id === event.detail.id)) {
// Update existing transportation
@ -476,7 +513,7 @@
{#if collection.id}
<div class="flex justify-center mx-auto">
<!-- svelte-ignore a11y-missing-attribute -->
<div role="tablist" class="tabs tabs-boxed tabs-lg max-w-xl">
<div role="tablist" class="tabs tabs-boxed tabs-lg max-w-full">
<!-- svelte-ignore a11y-missing-attribute -->
{#if collection.start_date}
<a
@ -508,6 +545,14 @@
on:click={() => (currentView = 'map')}
on:keydown={(e) => e.key === 'Enter' && (currentView = 'map')}>Map</a
>
<a
role="tab"
class="tab {currentView === 'recommendations' ? 'tab-active' : ''}"
tabindex="0"
on:click={() => (currentView = 'recommendations')}
on:keydown={(e) => e.key === 'Enter' && (currentView = 'recommendations')}
>Recommendations</a
>
</div>
</div>
{/if}
@ -800,6 +845,167 @@
</div>
</div>
{/if}
{#if currentView == 'recommendations'}
<div class="card bg-base-200 shadow-xl my-8 mx-auto w-10/12">
<div class="card-body">
<h2 class="card-title text-3xl justify-center mb-4">Adventure Recommendations</h2>
{#each adventures as adventure}
<button on:click={() => getRecomendations(adventure)} class="btn btn-neutral"
>{adventure.name}</button
>
{/each}
<div class="mt-4">
<input
type="range"
min="1600"
max="80467"
class="range"
step="1600"
bind:value={recomendationsRange}
/>
<div class="flex w-full justify-between px-2">
<span class="text-lg"
>{Math.round(recomendationsRange / 1600)} mile ({(
(recomendationsRange / 1600) *
1.6
).toFixed(1)} km)</span
>
</div>
<div class="join flex items-center justify-center mt-4">
<input
class="join-item btn"
type="radio"
name="options"
aria-label="Tourism"
checked={recomendationType == 'tourism'}
on:click={() => (recomendationType = 'tourism')}
/>
<input
class="join-item btn"
type="radio"
name="options"
aria-label="Food"
checked={recomendationType == 'food'}
on:click={() => (recomendationType = 'food')}
/>
<input
class="join-item btn"
type="radio"
name="options"
aria-label="Lodging"
checked={recomendationType == 'lodging'}
on:click={() => (recomendationType = 'lodging')}
/>
</div>
{#if recomendationTags.length > 0}
<select class="select select-bordered w-full max-w-xs">
<option disabled selected>Select a tag</option>
{#each recomendationTags as tag}
<option on:click={() => (selectedRecomendationTag = tag)}>{tag}</option>
{/each}
</select>
{/if}
</div>
{#if recomendationsData}
<MapLibre
style="https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json"
class="aspect-[9/16] max-h-[70vh] sm:aspect-video sm:max
-h-full w-full rounded-lg"
standardControls
center={{ lng: recomendationsData[0].longitude, lat: recomendationsData[0].latitude }}
zoom={12}
>
{#each recomendationsData as recomendation}
{#if recomendation.longitude && recomendation.latitude && recomendation.name}
<Marker
lngLat={[recomendation.longitude, recomendation.latitude]}
class="grid h-8 w-8 place-items-center rounded-full border border-gray-200 bg-blue-300 text-black focus:outline-6 focus:outline-black"
on:click={togglePopup}
>
<span class="text-xl">
{osmTagToEmoji(recomendation.tag)}
</span>
{#if isPopupOpen}
<Popup openOn="click" offset={[0, -10]} on:close={() => (isPopupOpen = false)}>
<div class="text-lg text-black font-bold">{recomendation.name}</div>
<p class="font-semibold text-black text-md">
{`${recomendation.tag} ${osmTagToEmoji(recomendation.tag)}`}
</p>
<button
class="btn btn-neutral btn-wide btn-sm mt-4"
on:click={() =>
window.open(
`https://www.openstreetmap.org/node/${recomendation.id}`,
'_blank'
)}>{$t('map.view_details')}</button
>
</Popup>
{/if}
</Marker>
{/if}
{/each}
</MapLibre>
{#each recomendationsData as recomendation}
{#if recomendation.name && recomendation.longitude && recomendation.latitude}
<div class="card bg-base-100 shadow-xl my-4 w-full">
<div class="card-body">
<h2 class="card-title text-xl font-bold">
{recomendation.name || 'Recommendation'}
</h2>
<div class="badge badge-primary">{recomendation.tag}</div>
<p class="text-md">{recomendation.description || 'No description available.'}</p>
{#if recomendation.address}
<p class="text-md">
<strong>Address:</strong>
{recomendation.address.housenumber}
{recomendation.address.street}, {recomendation.address.city}, {recomendation
.address.state}
{recomendation.address.postcode}
</p>
{/if}
{#if recomendation.contact}
<p class="text-md">
<strong>Contact:</strong>
{#if recomendation.contact.phone}
Phone: {recomendation.contact.phone}
{/if}
{#if recomendation.contact.email}
Email: {recomendation.contact.email}
{/if}
{#if recomendation.contact.website}
Website: <a
href={recomendation.contact.website}
target="_blank"
rel="noopener noreferrer">{recomendation.contact.website}</a
>
{/if}
</p>
{/if}
</div>
</div>
{/if}
{/each}
{/if}
{#if loadingRecomendations}
<div class="card bg-base-100 shadow-xl my-4 w-full">
<div class="card-body">
<div class="flex flex-col items-center justify-center">
<span class="loading loading-ring loading-lg"></span>
<div class="mt-2">
<p class="text-center text-lg">
Discovering hidden gems for your next adventure...
</p>
</div>
</div>
</div>
</div>
{/if}
</div>
</div>
{/if}
{/if}
<svelte:head>

View file

@ -130,7 +130,7 @@
class="join-item btn"
type="radio"
name="filter"
aria-label={$t('adventures.activity_types')}
aria-label={$t('adventures.tags')}
id="activity_types"
on:change={() => (property = 'activity_types')}
/>