Added copy coords function

This commit is contained in:
Alexis
2025-05-04 18:33:42 +02:00
parent f003df7d58
commit 4cf2f66d42
6 changed files with 93 additions and 16 deletions

View File

@@ -2,12 +2,12 @@
--viewport-padding: 25px; --viewport-padding: 25px;
position: fixed; position: fixed;
bottom: 0; bottom: 0;
right: 0; right: 2rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: var(--viewport-padding); padding: var(--viewport-padding);
gap: 10px; gap: 10px;
width: 390px; width: 480px;
max-width: 100vw; max-width: 100vw;
margin: 0; margin: 0;
list-style: none; list-style: none;
@@ -16,15 +16,23 @@
} }
.toast-root { .toast-root {
background-color: white;
border-radius: 6px;
box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px;
padding: 15px; padding: 15px;
display: grid; display: grid;
grid-template-areas: 'title action' 'description action'; grid-template-areas: 'title action' 'description action';
grid-template-columns: auto max-content; grid-template-columns: auto max-content;
column-gap: 15px; column-gap: 15px;
align-items: center; align-items: center;
color: var(--slate-800);
border: 1px solid var(--slate-400);
background-color: var(--white);
border-radius: 6px;
box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px;
&.toast-success {
color: var(--green-700);
border: 1px solid var(--green-700);
background-color: color-mix(in srgb, var(--green-500) 17.5%, var(--white));
}
} }
.toast-root[data-state='open'] { .toast-root[data-state='open'] {
animation: slideIn 150ms cubic-bezier(0.16, 1, 0.3, 1); animation: slideIn 150ms cubic-bezier(0.16, 1, 0.3, 1);

View File

@@ -602,6 +602,19 @@ map.on('moveend', () => {
setupToURL() setupToURL()
}) })
/**
* Listener for querying a copy of the current coordinates
*/
window.addEventListener('on-copy-coords', () => {
const lon = convertScaleToX(Number(localStorage.getItem('lastHeldXPosition')))
const lat = convertScaleToY(Number(localStorage.getItem('lastHeldYPosition')))
const coordsText = JSON.stringify({ x: lon, y: lat })
navigator.clipboard.writeText(coordsText)
window.dispatchEvent(new CustomEvent('on-toast-push', { bubbles: true, detail: { preset: 'copied-coords' }}))
})
/** /**
* UTILITIES * UTILITIES
*/ */
@@ -609,7 +622,7 @@ map.on('moveend', () => {
* Convert x coords to scale with ratio, zoom and offset * Convert x coords to scale with ratio, zoom and offset
* @param {number} x * @param {number} x
*/ */
function convertXToScale(x) { function convertXToScale(x) {
return (x * xRatio * zoomRatio) + xOffset return (x * xRatio * zoomRatio) + xOffset
} }
/** /**

View File

@@ -36,6 +36,7 @@ function switchMenuMode(newMode: MenuMode) {
onMounted(() => { onMounted(() => {
const mapRef = document.getElementById('world') const mapRef = document.getElementById('world')
if (mapRef) { if (mapRef) {
mapRef.addEventListener('contextmenu', handleContextMenu) mapRef.addEventListener('contextmenu', handleContextMenu)
mapRef.addEventListener('mousedown', hideMenu) mapRef.addEventListener('mousedown', hideMenu)
@@ -188,6 +189,11 @@ function hideMarkerModal() {
function setTitleError(error: Error | null) { function setTitleError(error: Error | null) {
markerTitleInputError.value = error markerTitleInputError.value = error
} }
function handleCoordsCopy() {
markerMenu.value?.dispatchEvent(new CustomEvent('on-copy-coords', { bubbles: true }))
hideMenu()
}
</script> </script>
<template> <template>
@@ -196,7 +202,16 @@ function setTitleError(error: Error | null) {
<menu v-show="shouldBeShown" ref="markerMenu"> <menu v-show="shouldBeShown" ref="markerMenu">
<li> <li>
<button @click="switchMenuMode('editing-marker')"> <button @click="switchMenuMode('editing-marker')">
{{ t('maps.markers.new') }} <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#000000" viewBox="0 0 256 256"><path d="M128,16a88.1,88.1,0,0,0-88,88c0,31.4,14.51,64.68,42,96.25a254.19,254.19,0,0,0,41.45,38.3,8,8,0,0,0,9.18,0A254.19,254.19,0,0,0,174,200.25c27.45-31.57,42-64.85,42-96.25A88.1,88.1,0,0,0,128,16Zm32,96H136v24a8,8,0,0,1-16,0V112H96a8,8,0,0,1,0-16h24V72a8,8,0,0,1,16,0V96h24a8,8,0,0,1,0,16Z"></path></svg>
<span>{{ t('maps.markers.new') }}</span>
</button>
</li>
<li>
<button @click="handleCoordsCopy">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#000000" viewBox="0 0 256 256"><path d="M216,32H88a8,8,0,0,0-8,8V80H40a8,8,0,0,0-8,8V216a8,8,0,0,0,8,8H168a8,8,0,0,0,8-8V176h40a8,8,0,0,0,8-8V40A8,8,0,0,0,216,32Zm-8,128H176V88a8,8,0,0,0-8-8H96V48H208Z"></path></svg>
<span>{{ t('maps.copyCoords') }}</span>
</button> </button>
</li> </li>
</menu> </menu>
@@ -271,14 +286,23 @@ menu {
li { li {
a, button { a, button {
display: block; display: flex;
gap: .75ch;
align-items: center;
padding: .33rem 1rem; padding: .33rem 1rem;
padding-left: .66rem;
font-size: .77em; font-size: .77em;
cursor: pointer; cursor: pointer;
width: 100%;
&:hover, &:hover,
&:focus-within { &:focus-within {
color: var(--blue-700);
background-color: var(--slate-100); background-color: var(--slate-100);
svg {
fill: var(--blue-700);
}
} }
} }
} }

View File

@@ -1,9 +1,35 @@
<script setup lang="ts"> <script setup lang="ts">
import { isVNode } from "vue" import { isVNode, onMounted } from "vue"
import { useToast } from './useToast.ts' import { ToastLifetime, useToast, type Toast } from './useToast.ts'
import { ToastDescription, ToastProvider, ToastRoot, ToastTitle, ToastViewport } from 'radix-vue' import { ToastDescription, ToastProvider, ToastRoot, ToastTitle, ToastViewport } from 'radix-vue'
const { toasts } = useToast() const { toast, toasts } = useToast()
import { t } from '@/i18n/store';
onMounted(() => {
window.addEventListener('on-toast-push', (e) => handleInserted(e as CustomEvent))
})
function handleInserted(e: CustomEvent) {
if ( e.detail.preset) {
switch (e.detail.preset) {
case "copied-coords":
default:
toast({
description: t('toast.copyCoords.description'),
duration: ToastLifetime.SHORT,
variant: 'success'
})
break
}
return
}
const toastData = e.detail.toast as Toast
toast(toastData)
}
function handleUpdate(id: string) { function handleUpdate(id: string) {
toasts.value.forEach((t, i) => { toasts.value.forEach((t, i) => {
@@ -12,7 +38,7 @@ function handleUpdate(id: string) {
setTimeout(() => { setTimeout(() => {
toasts.value.splice(i, 1) toasts.value.splice(i, 1)
}, 3000) }, 5000)
} }
}) })
} }
@@ -22,6 +48,7 @@ function handleUpdate(id: string) {
<ToastProvider> <ToastProvider>
<ToastRoot <ToastRoot
class="toast-root" class="toast-root"
:class="toast.variant ? `toast-${toast.variant}` : ''"
v-for="toast in toasts" v-for="toast in toasts"
:key="toast.id" :key="toast.id"
v-bind="toast" v-bind="toast"

View File

@@ -24,7 +24,8 @@ type ToasterToast = ToastProps & {
id: string id: string
title?: string title?: string
description?: StringOrVNode description?: StringOrVNode
action?: Component action?: Component,
variant?: "default" | "success" | "destructive"
} }
const actionTypes = { const actionTypes = {
@@ -139,7 +140,7 @@ function useToast() {
} }
} }
type Toast = Omit<ToasterToast, "id"> export type Toast = Omit<ToasterToast, "id">
function toast(props: Toast) { function toast(props: Toast) {
const id = genId() const id = genId()

View File

@@ -31,9 +31,11 @@ export const translations: LanguageDict = {
'maps.search': 'Rechercher la carte', 'maps.search': 'Rechercher la carte',
'maps.searchPlaceholder': "Ville, point d'intérêt…", 'maps.searchPlaceholder': "Ville, point d'intérêt…",
'maps.closeSearch': 'Enlever le filtre', 'maps.closeSearch': 'Enlever le filtre',
'maps.copyCoords': 'Copier les coordonnées',
'maps.markers.new': 'Nouveau marqueur', 'maps.markers.new': 'Nouveau marqueur',
'maps.markers.addPersonal': 'Ajouter un marqueur personnel', 'maps.markers.addPersonal': 'Ajouter un marqueur personnel',
'maps.markers.newNotice': "Le marqueur sera sauvegardé mais n'apparaîtra que sur votre carte !" 'maps.markers.newNotice': "Le marqueur sera sauvegardé mais n'apparaîtra que sur votre carte !",
'toast.copyCoords.description': 'Les coordonnées ont été copiées dans le presse-papiers !'
}, },
'en': { 'en': {
'lang.fr': 'Français', 'lang.fr': 'Français',
@@ -61,8 +63,10 @@ export const translations: LanguageDict = {
'maps.search': 'Search the map', 'maps.search': 'Search the map',
'maps.searchPlaceholder': "City, point of interest…", 'maps.searchPlaceholder': "City, point of interest…",
'maps.closeSearch': 'Remove filter', 'maps.closeSearch': 'Remove filter',
'maps.copyCoords': 'Copy coordinates',
'maps.markers.new': 'New marker', 'maps.markers.new': 'New marker',
'maps.markers.addPersonal': 'Add a personal marker', 'maps.markers.addPersonal': 'Add a personal marker',
'maps.markers.newNotice': "The marker will be saved but will only appear on your map!" 'maps.markers.newNotice': "The marker will be saved but will only appear on your map!",
'toast.copyCoords.description': 'Coordinates have been copied to your clipboard !'
} }
} as const; } as const;