Added marker add modal

This commit is contained in:
Alexis
2024-01-07 00:31:20 +01:00
parent 2878652158
commit da8bec44b0
8 changed files with 302 additions and 28 deletions

View File

@@ -14,7 +14,7 @@
"@astrojs/vue": "^3.0.1", "@astrojs/vue": "^3.0.1",
"@nanostores/vue": "^0.10.0", "@nanostores/vue": "^0.10.0",
"@types/leaflet": "^1.9.6", "@types/leaflet": "^1.9.6",
"@vueuse/core": "^10.5.0", "@vueuse/core": "^10.7.1",
"astro": "^3.2.0", "astro": "^3.2.0",
"nanostores": "^0.9.3", "nanostores": "^0.9.3",
"vue": "^3.3.4" "vue": "^3.3.4"

View File

@@ -0,0 +1,47 @@
.btn {
padding: .3rem .7rem;
display: inline-flex;
align-items: center;
gap: .5rem;
font-size: .9em;
font-weight: 500;
border: 1px solid transparent;
border-radius: .2rem;
cursor: pointer;
transition-property: color, background-color, border-color;
transition-duration: .2s;
transition-timing-function: cubic-bezier(0.785, 0.135, 0.15, 0.86);
&-secondary {
&:not(:disabled) {
background-color: var(--slate-200);
&:hover,
&:focus-visible {
color: var(--white);
background-color: var(--blue-500);
}
}
}
&-danger {
&:not(:disabled) {
background-color: var(--slate-200);
&:hover,
&:focus-visible {
color: var(--white);
background-color: var(--red-500);
}
}
}
&-icon {
width: 1.75rem;
padding: 0;
display: grid;
place-items: center;
aspect-ratio: 1;
border-radius: 50%;
}
}

View File

@@ -0,0 +1,29 @@
.form-group {
label {
display: block;
margin-bottom: .35rem;
font-size: .9em;
font-weight: 500;
}
.form-input {
input[type=text],
input[type=tel],
input[type=email],
textarea {
display: block;
width: 100%;
padding: .4rem .5rem;
border: 1px solid var(--slate-300);
font-size: .8em;
border-radius: .2rem;
transition-property: box-shadow;
transition-duration: .2s;
transition-timing-function: cubic-bezier(0.785, 0.135, 0.15, 0.86);
&:focus-visible {
box-shadow: 0 0 0 .2rem color-mix(in srgb, var(--slate-300) 20%, transparent);
}
}
}
}

View File

@@ -13,6 +13,8 @@
border-radius: .25rem; border-radius: .25rem;
.leaflet-popup-content { .leaflet-popup-content {
margin: .65rem 1.3rem .65rem .9rem;
.title { .title {
display: block; display: block;
font-weight: 600; font-weight: 600;
@@ -38,6 +40,11 @@
} }
} }
} }
.leaflet-popup-tip {
width: .8rem;
height: .8rem;
}
} }
.leaflet-tooltip-pane { .leaflet-tooltip-pane {
.leaflet-tooltip { .leaflet-tooltip {

View File

@@ -3,6 +3,9 @@
@use 'reset'; @use 'reset';
@use 'leaflet'; @use 'leaflet';
@use 'forms';
@use 'buttons';
:root { :root {
--white: #fff; --white: #fff;
--black: #111; --black: #111;
@@ -44,4 +47,4 @@ a[href^='http']::after {
width: 1em; width: 1em;
height: 1em; height: 1em;
margin-left: .25em; margin-left: .25em;
} }

View File

@@ -16,6 +16,7 @@ const players = playersData as PlayerMarker
</div> </div>
<script lang="ts" define:vars={{ markers, players }} defer> <script lang="ts" define:vars={{ markers, players }} defer>
const mapHeight = 21896; // const mapHeight = 21896; //
const mapWidth = 30000; // Find these in the zoomify / original image data const mapWidth = 30000; // Find these in the zoomify / original image data
@@ -308,8 +309,9 @@ function storeLastHeldPositions(e) {
/** /**
* Function that creates custom markers and places them on the map * Function that creates custom markers and places them on the map
* @param {string?} markerTitle
*/ */
function addCustomMarker() { function addCustomMarker(markerTitle) {
const lon = Number(localStorage.getItem('lastHeldXPosition')) const lon = Number(localStorage.getItem('lastHeldXPosition'))
const lat = Number(localStorage.getItem('lastHeldYPosition')) const lat = Number(localStorage.getItem('lastHeldYPosition'))
@@ -326,9 +328,18 @@ function addCustomMarker() {
// Sticks the custom marker on the map // Sticks the custom marker on the map
const customMarker = L.marker([lat, lon], { icon: customMarkerIcon }).addTo(map) const customMarker = L.marker([lat, lon], { icon: customMarkerIcon }).addTo(map)
if (markerTitle) {
const popupContent = `
${markerTitle}
`
customMarker.bindPopup(popupContent, { offset: [5, -5] }).openPopup()
}
customMarkersList.push({ customMarkersList.push({
lat, lat,
lon, lon,
title: markerTitle,
icon: customMarkerIconOptions icon: customMarkerIconOptions
}) })
@@ -337,7 +348,9 @@ function addCustomMarker() {
// Listeners for the custom add marker event // Listeners for the custom add marker event
// See MarkerCreator.vue component for more details // See MarkerCreator.vue component for more details
document.addEventListener('add-custom-marker', addCustomMarker) document.addEventListener('add-custom-marker', (e) => {
addCustomMarker(e.detail.title)
})
/** /**
* Load all custom markers onto the map * Load all custom markers onto the map
@@ -352,7 +365,14 @@ function loadCustomMarkersFromStorage() {
for (let i = 0; i < customMarkersData.length; i++) { for (let i = 0; i < customMarkersData.length; i++) {
const m = customMarkersData[i]; const m = customMarkersData[i];
L.marker([m.lat, m.lon], { icon: L.icon(m.icon) }).addTo(map) const savedMarker = L.marker([m.lat, m.lon], { icon: L.icon(m.icon) }).addTo(map)
if (m.title) {
const popupContent = `
${m.title}
`
savedMarker.bindPopup(popupContent, { offset: [5, -5] })
}
} }
} }
loadCustomMarkersFromStorage() loadCustomMarkersFromStorage()

View File

@@ -1,13 +1,21 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onClickOutside, onLongPress, useCssVar, useLocalStorage, useMouse, useTimeout, useTimeoutFn } from '@vueuse/core'; import { onClickOutside, onLongPress, useCssVar, useFocus, useLocalStorage, useMouse, useTimeout, useTimeoutFn } from '@vueuse/core';
import { onMounted, onUnmounted, ref, watch } from 'vue'; import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
const markerMenu = ref<HTMLMenuElement>(); const markerMenu = ref<HTMLMenuElement>();
type MenuMode = "editing-marker" | "default";
/** /**
* Appearing behaviour * Appearing behaviour
*/ */
const isActive = ref<Boolean>(false) const isActive = ref<Boolean>(false)
const shouldBeShown = computed(() => isActive.value)
const currentMode = ref<MenuMode>("default");
function switchMenuMode(newMode: MenuMode) {
currentMode.value = newMode
}
// const hasBeenLongPressed = ref<Boolean>(false) // const hasBeenLongPressed = ref<Boolean>(false)
// const longPressDelay = 500 // const longPressDelay = 500
@@ -18,9 +26,9 @@ const isActive = ref<Boolean>(false)
// }, longPressOver) // }, longPressOver)
onMounted(() => { onMounted(() => {
window.addEventListener('contextmenu', handleContextMenu) const mapRef = document.getElementById('world')
mapRef?.addEventListener('contextmenu', handleContextMenu)
// onLongPress(document.documentElement, handleLongPress, { delay: longPressDelay }) // onLongPress(document.documentElement, handleLongPress, { delay: longPressDelay })
onClickOutside(markerMenu, handleClickOutside)
}) })
// function handleLongPress(e: Event) { // function handleLongPress(e: Event) {
@@ -77,30 +85,111 @@ function updateCSSVars() {
/** /**
* Adding custom marker * Adding custom marker
*/ */
const lastHeldXPosition = useLocalStorage('lastHeldXPosition', 0); const markerTitle = ref<string>()
const lastHeldYPosition = useLocalStorage('lastHeldYPosition', 0); const markerTitleInput = ref<HTMLInputElement>()
const addCustomMarker = ref<HTMLButtonElement>(); const addCustomMarker = ref<HTMLButtonElement>();
onMounted(() => { const markerModal = ref<HTMLDialogElement>()
const addCustomMarkerEvent = new CustomEvent(`add-custom-marker`, { bubbles: true }) const markerModalOpen = ref<boolean>(false)
addCustomMarker.value?.addEventListener('click', () => { // Shows modal if mode changes to editing-marker
addCustomMarker.value?.dispatchEvent(addCustomMarkerEvent) watch(currentMode, (n, o) => {
}) if (n === 'editing-marker') {
showMarkerModal()
} else {
hideMarkerModal()
}
}) })
// Closes the menu if clicked outside
onClickOutside(markerMenu, handleClickOutside, { ignore: [markerModal] })
function handleAddCustomMarker() {
const addCustomMarkerEvent = new CustomEvent(`add-custom-marker`, { bubbles: true, detail: { title: markerTitle.value }})
addCustomMarker.value?.dispatchEvent(addCustomMarkerEvent)
hideMenu()
hideMarkerModal()
switchMenuMode('default')
markerTitle.value = ""
}
function handleMarkerModalCancel() {
showMenu()
hideMarkerModal()
switchMenuMode('default')
}
function showMarkerModal() {
markerModal.value?.showModal()
markerModalOpen.value = true
switchMenuMode('editing-marker')
}
function hideMarkerModal() {
markerModal.value?.close()
markerModalOpen.value = false
switchMenuMode('default')
}
</script> </script>
<template> <template>
<Transition> <div>
<menu v-show="isActive" ref="markerMenu"> <Transition>
<li> <menu v-show="shouldBeShown" ref="markerMenu">
<button ref="addCustomMarker"> <li>
Ajouter un marqueur personnel <button @click="switchMenuMode('editing-marker')">
Nouveau marqueur
</button>
</li>
</menu>
</Transition>
<dialog ref="markerModal" :class="{ 'open': markerModalOpen }" @cancel.prevent="handleMarkerModalCancel" @click="console.log">
<!-- <button autofocus @click="switchMenuMode('default')">Close</button> -->
<form @submit.prevent="handleAddCustomMarker">
<div class="modal-head">
<h2>
Ajouter un marqueur personnel
</h2>
<p class="modal-notice">
<i class="icon ph-light ph-warning"></i>
<span>Le marqueur sera sauvegardé mais n'apparaîtra que sur votre carte !</span>
</p>
</div>
<div class="modal-body">
<div class="form-group">
<label for="marker-name">
Titre
</label>
<div class="form-input">
<input ref="markerTitleInput" type="text" name="marker-name" id="marker-name" v-model="markerTitle">
</div>
</div>
</div>
<div class="modal-footer">
<button
ref="addCustomMarker"
type="submit"
class="btn btn-secondary"
>
<span>Créer</span>
</button>
</div>
</form>
<div class="modal-actions">
<button class="btn btn-danger btn-icon" @click="hideMarkerModal">
<i class="ph-light ph-x"></i>
</button> </button>
</li> </div>
</menu> </dialog>
</Transition> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -109,6 +198,7 @@ menu {
--mouse-x: 0px; --mouse-x: 0px;
position: absolute; position: absolute;
padding-block: .25rem;
top: calc(var(--mouse-y) - .5rem); top: calc(var(--mouse-y) - .5rem);
left: calc(var(--mouse-x) + .75rem); left: calc(var(--mouse-x) + .75rem);
background-color: var(--white); background-color: var(--white);
@@ -123,18 +213,96 @@ menu {
li { li {
a, button { a, button {
display: block; display: block;
padding: .5rem 1.2rem; padding: .33rem 1rem;
font-size: .8em; font-size: .77em;
cursor: pointer; cursor: pointer;
&:hover, &:hover,
&:focus-within { &:focus-within {
background-color: var(--slate-100);
} }
} }
} }
} }
dialog {
position: fixed;
width: 30%;
padding: 1.5rem 2rem;
z-index: 9999;
background-color: var(--white);
border: 1px solid var(--slate-400);
border-radius: .75rem;
overflow: visible;
transform: translateY(1rem);
transition: visibility 0s ease-out .3s, opacity .3s ease-out, transform .3s ease-out;
&,
&::backdrop {
visibility: hidden;
opacity: 0;
}
&::backdrop {
background-color: rgba(0, 0, 0, 0.15);
}
&.open {
transform: translateY(0);
}
&.open,
&.open::backdrop {
transition-delay: 0s;
}
&.open,
&.open::backdrop {
visibility: visible;
opacity: 1;
}
.modal-head {
margin-bottom: .75rem;
h2 {
font-size: 1.4em;
}
.modal-notice {
margin-top: .2rem;
display: flex;
align-items: center;
gap: .15rem;
opacity: .85;
font-size: .8em;
.icon {
font-size: 1.3em;
}
}
}
.modal-body {
> * + * {
margin-top: .5em;
}
}
.modal-footer {
margin-top: 1rem;
}
.modal-actions {
display: flex;
gap: 1rem;
position: absolute;
top: -0.8rem;
right: 0;
padding-right: .75rem;
}
}
/** /**
* ANIMATIONS * ANIMATIONS
*/ */

View File

@@ -173,7 +173,7 @@ function resetAllFields(actionAfter?: "focusAfter") {
<div ref="searchBar" class="search-w" :data-focused="shouldBeActive"> <div ref="searchBar" class="search-w" :data-focused="shouldBeActive">
<div class="input-w"> <div class="input-w">
<i class="search-icon ph-fill ph-magnifying-glass"></i> <i class="search-icon ph-fill ph-magnifying-glass"></i>
<input ref="qInput" name="recherche" type="text" v-model="q" title="Rechercher le monde" placeholder="Ville, point d'intérêt…"> <input ref="qInput" class="" name="recherche" type="text" v-model="q" title="Rechercher le monde" placeholder="Ville, point d'intérêt…">
<button v-if="hasGroupFilter || hasQuery" @click="onCloseQuery" class="close-btn" title="Enlever le filtre"> <button v-if="hasGroupFilter || hasQuery" @click="onCloseQuery" class="close-btn" title="Enlever le filtre">
<i class="ph-light ph-x"></i> <i class="ph-light ph-x"></i>