feat: sort by location
This commit is contained in:
@@ -3,8 +3,16 @@
|
||||
import defaultImg from "$lib/assets/study_space.png";
|
||||
import crossUrl from "$lib/assets/cross.svg";
|
||||
import Navbar from "$lib/components/Navbar.svelte";
|
||||
import { allTags, volumeTags, wifiTags, powerOutletTags } from "$lib";
|
||||
import {
|
||||
allTags,
|
||||
volumeTags,
|
||||
wifiTags,
|
||||
powerOutletTags,
|
||||
gmapsLoader,
|
||||
haversineDistance
|
||||
} from "$lib";
|
||||
import Button from "$lib/components/Button.svelte";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
const { data } = $props();
|
||||
const {
|
||||
@@ -137,6 +145,60 @@
|
||||
tagFilter = "";
|
||||
};
|
||||
}
|
||||
|
||||
let sortMapElem = $state<HTMLDivElement>();
|
||||
let sortNear = $state<{ lat: number; lng: number }>();
|
||||
const sortedStudySpaces = $derived(
|
||||
sortNear
|
||||
? studySpaces.toSorted((a, b) => {
|
||||
if (!sortNear) return 0;
|
||||
type DBLatLng = { lat: number; lng: number } | undefined;
|
||||
const aLoc = a.building_location as unknown as google.maps.places.PlaceResult;
|
||||
const bLoc = b.building_location as unknown as google.maps.places.PlaceResult;
|
||||
const aLatLng = aLoc.geometry?.location as DBLatLng;
|
||||
const bLatLng = bLoc.geometry?.location as DBLatLng;
|
||||
const aDistance = haversineDistance(
|
||||
sortNear.lat,
|
||||
sortNear.lng,
|
||||
aLatLng?.lat || sortNear.lat,
|
||||
aLatLng?.lng || sortNear.lng
|
||||
);
|
||||
const bDistance = haversineDistance(
|
||||
sortNear.lat,
|
||||
sortNear.lng,
|
||||
bLatLng?.lat || sortNear.lat,
|
||||
bLatLng?.lng || sortNear.lng
|
||||
);
|
||||
return aDistance - bDistance;
|
||||
})
|
||||
: filteredStudySpaces
|
||||
);
|
||||
let marker: google.maps.marker.AdvancedMarkerElement | undefined;
|
||||
onMount(async () => {
|
||||
if (!sortMapElem) return console.error("sortMapElem is not defined");
|
||||
const loader = await gmapsLoader();
|
||||
const { Map } = await loader.importLibrary("maps");
|
||||
const { AdvancedMarkerElement } = await loader.importLibrary("marker");
|
||||
const map = new Map(sortMapElem, {
|
||||
center: { lat: 53.6, lng: -1.56 },
|
||||
zoom: 5,
|
||||
mapId: "9f4993cd3fb1504d495821a5"
|
||||
});
|
||||
marker = new AdvancedMarkerElement({
|
||||
map,
|
||||
title: "Find near here"
|
||||
});
|
||||
map.addListener("click", (e: google.maps.MapMouseEvent) => {
|
||||
console.log("Clicked map at", e.latLng);
|
||||
sortNear = e.latLng
|
||||
? {
|
||||
lat: e.latLng.lat(),
|
||||
lng: e.latLng.lng()
|
||||
}
|
||||
: sortNear;
|
||||
if (marker) marker.position = e.latLng;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<Navbar>
|
||||
@@ -169,7 +231,7 @@
|
||||
<div class="tag-filter-container">
|
||||
<form>
|
||||
<div class="tagDisplay">
|
||||
{#each selectedTags as tagName (tagName)}
|
||||
{#each selectedTags as tagName, idx (tagName + idx)}
|
||||
<button class="tag" onclick={deleteTag(tagName)} type="button">
|
||||
{tagName}
|
||||
<img src={crossUrl} alt="delete" /></button
|
||||
@@ -198,7 +260,7 @@
|
||||
/>
|
||||
{#if dropdownVisible}
|
||||
<div class="tagDropdown">
|
||||
{#each filteredTags as avaliableTag (avaliableTag)}
|
||||
{#each filteredTags as avaliableTag, idx (avaliableTag + idx)}
|
||||
<button
|
||||
class="avaliableTag"
|
||||
onclick={addTag(avaliableTag)}
|
||||
@@ -217,8 +279,25 @@
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="location-filter-container">
|
||||
<h3>Find nearby:</h3>
|
||||
<Button
|
||||
onclick={() => {
|
||||
navigator.geolocation.getCurrentPosition((position) => {
|
||||
if (marker)
|
||||
sortNear = marker.position = {
|
||||
lat: position.coords.latitude,
|
||||
lng: position.coords.longitude
|
||||
};
|
||||
});
|
||||
}}
|
||||
>
|
||||
Use current location
|
||||
</Button>
|
||||
<div class="sortMap" bind:this={sortMapElem}></div>
|
||||
</div>
|
||||
|
||||
{#each filteredStudySpaces as studySpace (studySpace.id)}
|
||||
{#each sortedStudySpaces as studySpace (studySpace.id)}
|
||||
{@const imgUrl =
|
||||
studySpace.study_space_images.length > 0
|
||||
? supabase.storage
|
||||
@@ -284,6 +363,13 @@
|
||||
justify-content: center;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.location-filter-container {
|
||||
grid-column: 1 / -1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.time-filter-container label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -403,6 +489,11 @@
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.sortMap {
|
||||
aspect-ratio: 1 / 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 20rem) {
|
||||
main {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
<div class="compulsoryContainer"><CompulsoryTags {space} /></div>
|
||||
{#if space.tags.length > 0}
|
||||
<div class="tagContainer">
|
||||
{#each space.tags as tag (tag)}
|
||||
{#each space.tags as tag, idx (tag + idx)}
|
||||
<span class="tag">
|
||||
{tag}
|
||||
</span>
|
||||
@@ -172,7 +172,7 @@
|
||||
{#if place.name}
|
||||
{place.name} <br />
|
||||
{/if}
|
||||
{#each place.formatted_address?.split(",") || [] as line (line)}
|
||||
{#each place.formatted_address?.split(",") || [] as line, idx (line + idx)}
|
||||
{line.trim()} <br />
|
||||
{/each}
|
||||
</p>
|
||||
|
||||
@@ -370,7 +370,7 @@
|
||||
</div>
|
||||
<label for="openingTimes">Opening times (Optional):</label>
|
||||
<div class="allDaysTiming">
|
||||
{#each studySpaceData.opening_times as opening_time, index (index)}
|
||||
{#each studySpaceData.opening_times as opening_time, index (opening_time)}
|
||||
<OpeningTimesDay
|
||||
{index}
|
||||
bind:openingValue={opening_time.opens_at}
|
||||
|
||||
Reference in New Issue
Block a user