Added search markers component

Functionnality is not present for now and will probably rely on nanostores
This commit is contained in:
Alexis
2023-10-13 11:48:01 +02:00
parent 73cc730a5c
commit 4e1927bf02
11 changed files with 820 additions and 51 deletions

View File

@@ -23,8 +23,13 @@ const { markers, players } = Astro.props
minZoom: 2.5,
zoom: 2.5,
zoomSnap: 0,
zoomControl: false
});
L.control.zoom({
position: 'bottomright',
}).addTo(map);
// Layers
const layer = L.tileLayer.zoomify('/zoomify/alliance-kaldelienne/{g}/{z}-{x}-{y}.jpg', {
width: mapWidth,
@@ -152,7 +157,7 @@ const { markers, players } = Astro.props
});
const rulerOptions = {
position: 'topleft',
position: 'topright',
primaryLengthUnit: 'kilometers',
secondaryLengthUnit: 'days',
primaryAreaUnit: 'hectares',
@@ -187,6 +192,12 @@ html,
body {
height: 100vh;
margin: 0;
position: relative;
}
.world-wrapper {
position: fixed;
inset: 0;
}
.leaflet-container {

View File

@@ -0,0 +1,32 @@
---
import SearchMarkers from "./overlay/SearchMarkers.vue";
import type { MapMarker } from '@/types/Leaflet';
interface Props {
markers: MapMarker[];
players?: MapMarker;
}
const { markers, players } = Astro.props
---
<div class="world-overlay">
<SearchMarkers markers={markers} players={players} client:visible />
</div>
<script>
</script>
<style lang="scss">
.world-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
width: 100%;
z-index: 10;
padding: 10px;
pointer-events: none;
}
</style>

View File

@@ -0,0 +1,182 @@
<script setup lang="ts">
import type { MapMarker } from '@/types/Leaflet';
import { useFocus, useMagicKeys, whenever } from '@vueuse/core';
import { computed, onMounted, ref } from 'vue';
const props = defineProps<{
markers?: MapMarker[],
players?: MapMarker,
}>()
// Search functions
const qInput = ref()
const { focused: isFocused } = useFocus(qInput)
onMounted(() => {
isFocused.value = true
})
const shouldBeActive = computed(() => {
if (q.value.length > 0) {
return isFocused
}
})
const q = ref<string>("")
const filteredMarkers = computed(() => {
return props.markers?.filter(m => {
const unifier = new RegExp(/[^a-zA-Z0-9\-\'']/g)
const queryString = new String(q.value).replace(unifier, "").toLocaleLowerCase()
const hitTitle = m.title.replace(unifier, "").toLocaleLowerCase().includes(queryString)
const hitDesc = m.description?.replace(unifier, "").toLocaleLowerCase().includes(queryString)
return hitTitle || hitDesc
})
})
// Handle shortcut to focus the main search fields
const keys = useMagicKeys()
const shortcutKeyCombo = keys['Shift+Period']
whenever(shortcutKeyCombo, () => {
isFocused.value = true
q.value = ""
})
// Player geolocation
const searchBar = ref()
function handlePlayerTarget () {
// Emit event to store ? to dom ?
}
</script>
<template>
<div ref="searchBar" class="search-w" :data-focused="shouldBeActive">
<div class="input-w">
<i class="search-icon ph-fill ph-magnifying-glass"></i>
<input ref="qInput" type="text" v-model="q">
<button class="player-btn" @click.native="handlePlayerTarget">
<i class="pin-icon ph-fill ph-map-pin"></i>
</button>
</div>
<ul class="search-results" v-if="shouldBeActive">
<li v-for="m in filteredMarkers?.slice(0, 10)" :key="m.title">
<button class="search-item">
<span class="title">{{ m.title }}</span>
<span class="desc">{{ m.description }}</span>
</button>
</li>
</ul>
</div>
</template>
<style lang="scss" scoped>
.search-w {
margin-bottom: 1em;
padding: .5rem 1.2rem;
width: 25%;
min-width: 20rem;
background: #fff;
border: 1px solid var(--slate-400);
border-radius: 25px;
pointer-events: all;
.input-w {
$search-items-gap: .5rem;
$search-icon-size: 1.2em;
position: relative;
padding-block: .2rem;
display: flex;
align-items: center;
gap: $search-items-gap;
input {
display: block;
min-width: 10rem;
padding-inline-start: calc($search-icon-size + $search-items-gap);
flex-grow: 1;
}
.search-icon {
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 0;
font-size: $search-icon-size;
color: var(--slate-400);
pointer-events: none;
}
.player-btn {
aspect-ratio: 1 / 1;
line-height: 1;
font-size: 1.5em;
color: #dc2626;
border-radius: 50%;
cursor: pointer;
}
}
.search-results {
max-height: 50vh;
overflow-y: auto;
scrollbar-gutter: stable;
&::-webkit-scrollbar,
&::-webkit-scrollbar-thumb {
width: 26px;
border-radius: 13px;
background-clip: padding-box;
border: 10px solid transparent;
}
&::-webkit-scrollbar-thumb {
background-color: var(--slate-300);
}
> * + * {
margin-bottom: .33rem;
}
.search-item {
cursor: pointer;
padding: .4rem .25em;
width: 100%;
&:hover {
background-color: var(--slate-100);
}
.title,
.desc {
display: block;
font-size: .85em;
}
.title {
font-weight: 500;
}
.desc {
color: var(--slate-500);
}
}
}
&[data-focused="true"] {
.input-w {
border-bottom: 1px solid var(--slate-200);
margin-bottom: .5rem;
padding-bottom: .5rem;
input {
padding-block-start: .2rem;
}
}
}
}
</style>