Added search markers component
Functionnality is not present for now and will probably rely on nanostores
This commit is contained in:
101
src/assets/scss/_reset.scss
Normal file
101
src/assets/scss/_reset.scss
Normal file
@@ -0,0 +1,101 @@
|
||||
@charset "utf-8";
|
||||
/***
|
||||
The new CSS reset - version 1.8.5 (last updated 14.6.2023)
|
||||
GitHub page: https://github.com/elad2412/the-new-css-reset
|
||||
***/
|
||||
|
||||
/*
|
||||
Remove all the styles of the "User-Agent-Stylesheet", except for the 'display' property
|
||||
- The "symbol *" part is to solve Firefox SVG sprite bug
|
||||
- The "html" attribute is exclud, because otherwise a bug in Chrome breaks the CSS hyphens property (https://github.com/elad2412/the-new-css-reset/issues/36)
|
||||
*/
|
||||
*:where(:not(html, iframe, canvas, img, svg, video, audio):not(svg *, symbol *)) {
|
||||
all: unset;
|
||||
display: revert;
|
||||
}
|
||||
|
||||
/* Preferred box-sizing value */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Reapply the pointer cursor for anchor tags */
|
||||
a, button {
|
||||
cursor: revert;
|
||||
}
|
||||
|
||||
/* Remove list styles (bullets/numbers) */
|
||||
ol, ul, menu {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
/* For images to not be able to exceed their container */
|
||||
img {
|
||||
max-inline-size: 100%;
|
||||
max-block-size: 100%;
|
||||
}
|
||||
|
||||
/* removes spacing between cells in tables */
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
/* Safari - solving issue when using user-select:none on the <body> text input doesn't working */
|
||||
input, textarea {
|
||||
-webkit-user-select: auto;
|
||||
}
|
||||
|
||||
/* revert the 'white-space' property for textarea elements on Safari */
|
||||
textarea {
|
||||
white-space: revert;
|
||||
}
|
||||
|
||||
/* minimum style to allow to style meter element */
|
||||
meter {
|
||||
-webkit-appearance: revert;
|
||||
appearance: revert;
|
||||
}
|
||||
|
||||
/* preformatted text - use only for this feature */
|
||||
:where(pre) {
|
||||
all: revert;
|
||||
}
|
||||
|
||||
/* reset default text opacity of input placeholder */
|
||||
::placeholder {
|
||||
color: unset;
|
||||
}
|
||||
|
||||
/* remove default dot (•) sign */
|
||||
::marker {
|
||||
content: initial;
|
||||
}
|
||||
|
||||
/* fix the feature of 'hidden' attribute.
|
||||
display:revert; revert to element instead of attribute */
|
||||
:where([hidden]) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* revert for bug in Chromium browsers
|
||||
- fix for the content editable attribute will work properly.
|
||||
- webkit-user-select: auto; added for Safari in case of using user-select:none on wrapper element */
|
||||
:where([contenteditable]:not([contenteditable="false"])) {
|
||||
-moz-user-modify: read-write;
|
||||
-webkit-user-modify: read-write;
|
||||
overflow-wrap: break-word;
|
||||
-webkit-line-break: after-white-space;
|
||||
-webkit-user-select: auto;
|
||||
}
|
||||
|
||||
/* apply back the draggable feature - exist only in Chromium and Safari */
|
||||
:where([draggable="true"]) {
|
||||
-webkit-user-drag: element;
|
||||
}
|
||||
|
||||
/* Revert Modal native behavior */
|
||||
:where(dialog:modal) {
|
||||
all: revert;
|
||||
}
|
||||
@@ -1,5 +1,22 @@
|
||||
@use './leaflet';
|
||||
@use 'reset';
|
||||
@use 'leaflet';
|
||||
|
||||
:root {
|
||||
--slate-50: #f8fafc;
|
||||
--slate-100: #f1f5f9;
|
||||
--slate-200: #e2e8f0;
|
||||
--slate-300: #cbd5e1;
|
||||
--slate-400: #94a3b8;
|
||||
--slate-500: #64748b;
|
||||
--slate-600: #475569;
|
||||
--slate-700: #334155;
|
||||
--slate-800: #1e293b;
|
||||
--slate-900: #0f172a;
|
||||
--slate-950: #020617;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
color: var(--slate-900);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
32
src/components/maps/WorldMapOverlay.astro
Normal file
32
src/components/maps/WorldMapOverlay.astro
Normal 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>
|
||||
182
src/components/maps/overlay/SearchMarkers.vue
Normal file
182
src/components/maps/overlay/SearchMarkers.vue
Normal 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>
|
||||
@@ -23,6 +23,8 @@ const { title } = Astro.props;
|
||||
<meta name="msapplication-TileColor" content="#00aba9">
|
||||
<meta name="theme-color" content="#a9d1eb">
|
||||
|
||||
<script is:inline src="https://unpkg.com/@phosphor-icons/web"></script>
|
||||
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
|
||||
<script is:inline src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin="">
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import WorldMap from '../components/maps/WorldMap.astro'
|
||||
import WorldMapOverlay from '../components/maps/WorldMapOverlay.astro'
|
||||
|
||||
const fetchLandMarkers = await fetch('http://localhost:4321/api/markers.json');
|
||||
const fetchPlayersPosition = await fetch('http://localhost:4321/api/players.json');
|
||||
@@ -9,5 +10,6 @@ const playerCoords = await fetchPlayersPosition.json();
|
||||
---
|
||||
|
||||
<Layout title="Cartographie">
|
||||
<WorldMapOverlay markers={landData} players={playerCoords} />
|
||||
<WorldMap markers={landData} players={playerCoords} />
|
||||
</Layout>
|
||||
|
||||
Reference in New Issue
Block a user