Refactored marker into component

This commit is contained in:
Alexis
2025-08-23 22:34:18 +02:00
parent 3188148b06
commit ce162c9ea6
6 changed files with 135 additions and 88 deletions

View File

@@ -5,20 +5,17 @@ import 'vue-leaflet-markercluster/dist/style.css'
import L, { type PointTuple } from 'leaflet'
globalThis.L = L
import { LMap, LTileLayer, LMarker, LPopup, LIcon, LControlZoom } from '@vue-leaflet/vue-leaflet'
import { LMap, LTileLayer, LControlZoom } from '@vue-leaflet/vue-leaflet'
import { LMarkerClusterGroup } from 'vue-leaflet-markercluster'
import { computed, ref } from 'vue'
import { ref } from 'vue'
import { useReviewStore } from './stores/reviews'
import ReviewMarker from './components/ReviewMarker.vue'
const { markers } = useReviewStore()
const zoom = ref(7)
const minZoom = 4
const center = ref<PointTuple>([47.809376, -0.637207])
const iconSize = ref<PointTuple>([38, 38])
const iconAnchor = computed<PointTuple>(() => [iconSize.value[0] / 2, iconSize.value[1]])
const popupOffset = [0, iconSize.value[0] * -0.66]
</script>
<template>
@@ -40,66 +37,7 @@ const popupOffset = [0, iconSize.value[0] * -0.66]
/>
<LMarkerClusterGroup :max-cluster-radius="18" :disable-clustering-at-zoom="14">
<LMarker v-for="marker in markers" :key="marker.title" :lat-lng="marker.coords">
<LPopup :options="{ offset: popupOffset, maxWidth: 380, minWidth: 180 }">
<div class="spacing">
<h2 class="font-bold text-[1.2em] leading-5">
{{ marker.title }}
</h2>
<p v-html="marker.reviews?.[0].text" />
</div>
</LPopup>
<LIcon
:icon-size
:icon-anchor
:class-name="marker.closed ? 'grayscale' : ''"
>
<img v-if="marker.type === 'bar'" src="/map/icons/bar.svg" />
<img v-if="marker.type === 'bakery'" src="/map/icons/bakery.svg" />
<img v-if="marker.type === 'tea-shop'" src="/map/icons/tea-shop.svg" />
<img v-if="marker.type === 'coffee-shop'" src="/map/icons/coffee-shop.svg" />
<img v-if="marker.type === 'ice-cream'" src="/map/icons/ice-cream.svg" />
<template v-else-if="marker.type === 'restaurant'">
<img v-if="marker.food === 'asian'" src="/map/icons/asian.svg" />
<img v-else-if="marker.food === 'burgers'" src="/map/icons/burgers.svg" />
<!-- <img v-else-if="marker.food === 'creperie'" src="/map/icons/creperie.svg" /> -->
<!-- <img v-else-if="marker.food === 'eastern'" src="/map/icons/eastern.svg" /> -->
<img v-else-if="marker.food === 'fast-food'" src="/map/icons/fast-food.svg" />
<img v-else-if="marker.food === 'italian'" src="/map/icons/italian.svg" />
<img v-else-if="marker.food === 'seafood'" src="/map/icons/seafood.svg" />
<!-- <img v-else-if="marker.food === 'street-food'" src="/map/icons/street-food.svg" /> -->
<img v-else-if="marker.food === 'vegetarian'" src="/map/icons/vegetarian.svg" />
<img v-else src="/map/icons/restaurant.svg" />
</template>
<div
class="absolute -top-2 -right-2 size-5 text-center grid place-items-center rounded-full text-[0.65rem] font-bold tracking-[-0.075em]"
:class="{
'text-white bg-red-500': marker.reviews?.[0].grade! <= 1.5,
'text-black bg-amber-400': marker.reviews?.[0].grade! > 1.5 && marker.reviews?.[0].grade! <= 2.5,
'text-black bg-lime-400': marker.reviews?.[0].grade! > 2.5 && marker.reviews?.[0].grade! < 4.0,
'text-black bg-green-400': marker.reviews?.[0].grade! > 2.5 && marker.reviews?.[0].grade! < 4.7,
'text-black bg-sky-400': marker.reviews?.[0].grade! >= 4.7
}"
>
{{ marker.reviews?.[0].grade }}
</div>
<div
class="absolute -z-10 -inset-0.5 rounded-full"
:class="{
'bg-red-500': marker.reviews?.[0].grade! <= 1.5,
'bg-amber-400': marker.reviews?.[0].grade! > 1.5 && marker.reviews?.[0].grade! <= 2.5,
'bg-lime-400': marker.reviews?.[0].grade! > 2.5 && marker.reviews?.[0].grade! < 4.0,
'bg-green-400': marker.reviews?.[0].grade! > 2.5 && marker.reviews?.[0].grade! < 4.7,
'bg-sky-400': marker.reviews?.[0].grade! >= 4.7
}"
/>
</LIcon>
</LMarker>
<ReviewMarker v-once v-for="marker in markers" :key="marker.title" :marker />
</LMarkerClusterGroup>
</LMap>
</main>
@@ -128,6 +66,10 @@ main {
}
}
.marker-cluster span {
font-weight: var(--font-weight-medium);
}
.marker-cluster-small {
background-color: color-mix(in srgb, var(--color-green-500) 25%, transparent);

View File

@@ -0,0 +1,69 @@
<script setup lang="ts">
import type { ReviewMarker } from '@/models/Markers';
import { LMarker, LPopup, LIcon } from '@vue-leaflet/vue-leaflet'
import type { PointTuple } from 'leaflet';
import { computed, ref } from 'vue';
defineProps<{
marker: ReviewMarker
}>()
const iconSize = ref<PointTuple>([38, 38])
const iconAnchor = computed<PointTuple>(() => [iconSize.value[0] / 2, iconSize.value[1]])
const popupOffset = [0, iconSize.value[0] * -0.66]
</script>
<template>
<LMarker :lat-lng="marker.coords">
<LPopup :options="{ offset: popupOffset, maxWidth: 380, minWidth: 180 }">
<div class="spacing">
<h2 class="font-bold text-[1.2em] leading-5">
{{ marker.title }}
</h2>
<p v-html="marker.reviews?.[0].text" />
</div>
</LPopup>
<LIcon :icon-size :icon-anchor :class-name="marker.closed ? 'grayscale' : ''">
<img v-if="marker.type === 'bar'" src="/map/icons/bar.svg" />
<img v-if="marker.type === 'bakery'" src="/map/icons/bakery.svg" />
<img v-if="marker.type === 'tea-shop'" src="/map/icons/tea-shop.svg" />
<img v-if="marker.type === 'coffee-shop'" src="/map/icons/coffee-shop.svg" />
<img v-if="marker.type === 'ice-cream'" src="/map/icons/ice-cream.svg" />
<template v-else-if="marker.type === 'restaurant'">
<img v-if="marker.food === 'asian'" src="/map/icons/asian.svg" />
<img v-else-if="marker.food === 'burgers'" src="/map/icons/burgers.svg" />
<!-- <img v-else-if="marker.food === 'creperie'" src="/map/icons/creperie.svg" /> -->
<!-- <img v-else-if="marker.food === 'eastern'" src="/map/icons/eastern.svg" /> -->
<img v-else-if="marker.food === 'fast-food'" src="/map/icons/fast-food.svg" />
<img v-else-if="marker.food === 'italian'" src="/map/icons/italian.svg" />
<img v-else-if="marker.food === 'seafood'" src="/map/icons/seafood.svg" />
<!-- <img v-else-if="marker.food === 'street-food'" src="/map/icons/street-food.svg" /> -->
<img v-else-if="marker.food === 'vegetarian'" src="/map/icons/vegetarian.svg" />
<img v-else src="/map/icons/restaurant.svg" />
</template>
<div
class="absolute -top-2 -right-2 size-5 text-center grid place-items-center rounded-full text-[0.65rem] font-bold tracking-[-0.075em]"
:class="{
'text-white bg-red-500': marker.reviews?.[0].grade! <= 1.5,
'text-black bg-amber-400': marker.reviews?.[0].grade! > 1.5 && marker.reviews?.[0].grade! <= 2.5,
'text-black bg-lime-400': marker.reviews?.[0].grade! > 2.5 && marker.reviews?.[0].grade! < 4.0,
'text-black bg-green-400': marker.reviews?.[0].grade! > 2.5 && marker.reviews?.[0].grade! < 4.7,
'text-black bg-sky-400': marker.reviews?.[0].grade! >= 4.7
}">
{{ marker.reviews?.[0].grade }}
</div>
<div class="absolute -z-10 -inset-0.5 rounded-full" :class="{
'bg-red-500': marker.reviews?.[0].grade! <= 1.5,
'bg-amber-400': marker.reviews?.[0].grade! > 1.5 && marker.reviews?.[0].grade! <= 2.5,
'bg-lime-400': marker.reviews?.[0].grade! > 2.5 && marker.reviews?.[0].grade! < 4.0,
'bg-green-400': marker.reviews?.[0].grade! > 2.5 && marker.reviews?.[0].grade! < 4.7,
'bg-sky-400': marker.reviews?.[0].grade! >= 4.7
}" />
</LIcon>
</LMarker>
</template>

18
src/models/Markers.ts Normal file
View File

@@ -0,0 +1,18 @@
import type { PointTuple } from "leaflet"
export type MarkerType = "restaurant" | "bar" | "bakery" | "tea-shop" | "coffee-shop" | "ice-cream"
export type FoodType = "mixed" | "asian" | "vegetarian" | "italian" | "fast-food" | "street-food" | "burgers" | "seafood" | "creperie" | "eastern"
export interface ReviewMarker {
coords: PointTuple
title?: string
type?: MarkerType
food?: FoodType
reviews?: Review[]
closed?: boolean
}
export interface Review {
text?: string
grade: number
}

View File

@@ -1,23 +1,6 @@
import type { PointTuple } from 'leaflet'
import type { ReviewMarker } from '@/models/Markers'
import { defineStore } from 'pinia'
type MarkerType = "restaurant" | "bar" | "bakery" | "tea-shop" | "coffee-shop" | "ice-cream"
type FoodType = "mixed" | "asian" | "vegetarian" | "italian" | "fast-food" | "street-food" | "burgers" | "seafood" | "creperie" | "eastern"
interface ReviewMarker {
coords: PointTuple
title?: string
type?: MarkerType
food?: FoodType
reviews?: Review[]
closed?: boolean
}
interface Review {
text?: string
grade: number
}
export const useReviewStore = defineStore('reviews', () => {
const markers: ReviewMarker[] = [
{
@@ -475,6 +458,18 @@ const markers: ReviewMarker[] = [
}
]
},
{
title: "La Belle Normande",
coords: [48.63542201422258, -1.5098922028521766],
type: "restaurant",
food: "creperie",
reviews: [
{
grade: 0.8,
text: "De la bouffe industrielle dans un attrape-touriste avec une marge multipliée par trois ? C'est ici."
}
]
},
{
title: "Saïgon Cà phê",
coords: [49.254911553043485, 4.027036477700138],
@@ -487,6 +482,18 @@ const markers: ReviewMarker[] = [
}
]
},
{
title: "Tacos Kebab 51",
coords: [48.72660636228893, 4.584080588216441],
type: "restaurant",
food: "fast-food",
reviews: [
{
grade: 1.8,
text: "Un kebab correct, rien de plus."
}
]
},
{
title: "La Calabraise d'Erlon",
coords: [49.256709879454554, 4.025919584640479],
@@ -523,6 +530,17 @@ const markers: ReviewMarker[] = [
}
]
},
{
title: "Roadside",
coords: [48.646920384150576, -2.0086050666279425],
type: "restaurant",
food: "street-food",
reviews: [
{
grade: 2.9
}
]
},
{
title: "Pitaya Les Lices",
coords: [48.11275152309647, -1.681332848723036],