Added layer controls
This commit is contained in:
24
src/App.vue
24
src/App.vue
@@ -2,25 +2,27 @@
|
|||||||
import 'leaflet/dist/leaflet.css'
|
import 'leaflet/dist/leaflet.css'
|
||||||
import 'vue-leaflet-markercluster/dist/style.css'
|
import 'vue-leaflet-markercluster/dist/style.css'
|
||||||
|
|
||||||
import L, { type PointTuple } from 'leaflet'
|
import L from 'leaflet'
|
||||||
globalThis.L = L
|
globalThis.L = L
|
||||||
|
|
||||||
import { LMap, LTileLayer, LControlZoom, LLayerGroup } from '@vue-leaflet/vue-leaflet'
|
import { LMap, LTileLayer, LControlZoom, LLayerGroup } from '@vue-leaflet/vue-leaflet'
|
||||||
import { LMarkerClusterGroup } from 'vue-leaflet-markercluster'
|
import { LMarkerClusterGroup } from 'vue-leaflet-markercluster'
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
import ReviewMarker from './components/ReviewMarker.vue'
|
import ReviewMarker from './components/map/ReviewMarker.vue'
|
||||||
import TokenMarker from './components/TokenMarker.vue'
|
import TokenMarker from './components/map/TokenMarker.vue'
|
||||||
|
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
import { useReviewStore } from './stores/reviews'
|
import { useReviewStore } from './stores/reviews'
|
||||||
import { useParisTokensStore } from './stores/parisTokens'
|
import { useParisTokensStore } from './stores/parisTokens'
|
||||||
|
import { useMap } from './stores/map'
|
||||||
|
import MapSidebar from './components/map/MapSidebar.vue'
|
||||||
|
|
||||||
const zoom = ref(7)
|
const { zoom, minZoom, center } = useMap()
|
||||||
const minZoom = 4
|
|
||||||
const center = ref<PointTuple>([47.809376, -0.637207])
|
|
||||||
|
|
||||||
const { markers: reviews } = useReviewStore()
|
const { markers: reviews } = useReviewStore()
|
||||||
const { markers: parisTokens } = useParisTokensStore()
|
const { markers: parisTokens } = useParisTokensStore()
|
||||||
|
|
||||||
|
const { activeLayers } = storeToRefs(useMap())
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -41,22 +43,26 @@ const { markers: parisTokens } = useParisTokensStore()
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- "Monnaie de Paris" CSGroup -->
|
<!-- "Monnaie de Paris" CSGroup -->
|
||||||
<LLayerGroup v-if="parisTokens.length > 0" :max-cluster-radius="18" :disable-clustering-at-zoom="14">
|
<LLayerGroup v-if="activeLayers.tokens && parisTokens.length > 0" :max-cluster-radius="18" :disable-clustering-at-zoom="14">
|
||||||
<TokenMarker v-once v-for="marker in parisTokens" :key="marker.title" :marker />
|
<TokenMarker v-once v-for="marker in parisTokens" :key="marker.title" :marker />
|
||||||
</LLayerGroup>
|
</LLayerGroup>
|
||||||
|
|
||||||
<!-- Review CSGroup -->
|
<!-- Review CSGroup -->
|
||||||
<LMarkerClusterGroup v-if="reviews.length > 0" :max-cluster-radius="18" :disable-clustering-at-zoom="14">
|
<LMarkerClusterGroup v-if="activeLayers.food && reviews.length > 0" :max-cluster-radius="18" :disable-clustering-at-zoom="14">
|
||||||
<ReviewMarker v-once v-for="marker in reviews" :key="marker.title" :marker />
|
<ReviewMarker v-once v-for="marker in reviews" :key="marker.title" :marker />
|
||||||
</LMarkerClusterGroup>
|
</LMarkerClusterGroup>
|
||||||
</LMap>
|
</LMap>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<MapSidebar />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
main {
|
main {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaflet-marker-icon:not(.marker-cluster) {
|
.leaflet-marker-icon:not(.marker-cluster) {
|
||||||
|
|||||||
@@ -1,5 +1,25 @@
|
|||||||
@import 'tailwindcss';
|
@import 'tailwindcss';
|
||||||
|
|
||||||
|
@custom-variant dark (&:where(.dark, .dark *));
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--color-foreground: var(--color-slate-950);
|
||||||
|
--color-background: var(--color-slate-100);
|
||||||
|
--color-border: var(--color-slate-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer theme {
|
||||||
|
.dark {
|
||||||
|
--color-foreground: var(--color-slate-100);
|
||||||
|
--color-background: var(--color-slate-900);
|
||||||
|
--color-border: var(--color-slate-800);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--body-padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
74
src/components/map/MapSidebar.vue
Normal file
74
src/components/map/MapSidebar.vue
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { useMap } from '@/stores/map';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
|
||||||
|
const { activeLayers } = storeToRefs(useMap())
|
||||||
|
|
||||||
|
function handleTokenClick() {
|
||||||
|
activeLayers.value.tokens = !activeLayers.value.tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFoodClick() {
|
||||||
|
activeLayers.value.food = !activeLayers.value.food
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<aside class="rounded bg-background border border-border shadow-sm">
|
||||||
|
<menu class="first:pt-0.5 last:pb-0.5">
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
class="pl-2 pr-3 py-1.5 text-xs cursor-pointer flex items-start gap-1"
|
||||||
|
:class="{
|
||||||
|
'text-foreground *:fill-foreground': !activeLayers.food,
|
||||||
|
'text-emerald-600 *:fill-emerald-600': activeLayers.food
|
||||||
|
}"
|
||||||
|
@click="handleFoodClick"
|
||||||
|
>
|
||||||
|
<svg v-if="activeLayers.food" xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 256 256">
|
||||||
|
<path d="M208,32H48A16,16,0,0,0,32,48V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32Zm-34.34,77.66-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35a8,8,0,0,1,11.32,11.32Z" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<svg v-else xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 256 256">
|
||||||
|
<path d="M216,40V224a8,8,0,0,1-16,0V176H152a8,8,0,0,1-8-8,268.75,268.75,0,0,1,7.22-56.88c9.78-40.49,28.32-67.63,53.63-78.47A8,8,0,0,1,216,40Zm-96.11-1.31a8,8,0,1,0-15.78,2.63L111.89,88H88V40a8,8,0,0,0-16,0V88H48.11l7.78-46.68a8,8,0,1,0-15.78-2.63l-8,48A8.17,8.17,0,0,0,32,88a48.07,48.07,0,0,0,40,47.32V224a8,8,0,0,0,16,0V135.32A48.07,48.07,0,0,0,128,88a8.17,8.17,0,0,0-.11-1.31Z" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
Restaurants
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
class="pl-2 pr-3 py-1.5 text-xs cursor-pointer flex items-start gap-1"
|
||||||
|
:class="{
|
||||||
|
'text-foreground *:fill-foreground': !activeLayers.tokens,
|
||||||
|
'text-emerald-600 *:fill-emerald-600': activeLayers.tokens
|
||||||
|
}"
|
||||||
|
@click="handleTokenClick"
|
||||||
|
>
|
||||||
|
<svg v-if="activeLayers.tokens" xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 256 256">
|
||||||
|
<path d="M208,32H48A16,16,0,0,0,32,48V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32Zm-34.34,77.66-56,56a8,8,0,0,1-11.32,0l-24-24a8,8,0,0,1,11.32-11.32L112,148.69l50.34-50.35a8,8,0,0,1,11.32,11.32Z" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<svg v-else xmlns="http://www.w3.org/2000/svg" width="1.2em" height="1.2em" viewBox="0 0 256 256">
|
||||||
|
<path d="M198.51,56.09C186.44,35.4,169.92,24,152,24H104C86.08,24,69.56,35.4,57.49,56.09,46.21,75.42,40,101,40,128s6.21,52.58,17.49,71.91C69.56,220.6,86.08,232,104,232h48c17.92,0,34.44-11.4,46.51-32.09C209.79,180.58,216,155,216,128S209.79,75.42,198.51,56.09ZM199.79,120h-32a152.78,152.78,0,0,0-9.68-48H188.7C194.82,85.38,198.86,102,199.79,120Zm-20.6-64H150.46a83.13,83.13,0,0,0-12-16H152C162,40,171.4,46,179.19,56ZM152,216H138.49a83.13,83.13,0,0,0,12-16h28.73C171.4,210,162,216,152,216Zm36.7-32H158.12a152.78,152.78,0,0,0,9.68-48h32C198.86,154,194.82,170.62,188.7,184Z" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
Monnaie de Paris
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</menu>
|
||||||
|
</aside>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
aside {
|
||||||
|
position: fixed;
|
||||||
|
top: var(--body-padding);
|
||||||
|
left: var(--body-padding);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
21
src/stores/map.ts
Normal file
21
src/stores/map.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import type { PointTuple } from 'leaflet'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
export const useMap = defineStore('map', () => {
|
||||||
|
const zoom = ref(7)
|
||||||
|
const minZoom = 4
|
||||||
|
const center = ref<PointTuple>([47.809376, -0.637207])
|
||||||
|
|
||||||
|
const activeLayers = ref({
|
||||||
|
tokens: false,
|
||||||
|
food: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
zoom,
|
||||||
|
minZoom,
|
||||||
|
center,
|
||||||
|
activeLayers
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user