Merge branch 'dev'
This commit is contained in:
16
package.json
16
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "leim-maps",
|
"name": "leim-maps",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "1.4.8",
|
"version": "1.4.9",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
"start": "astro dev",
|
"start": "astro dev",
|
||||||
@@ -10,18 +10,18 @@
|
|||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/sitemap": "^3.3.1",
|
"@astrojs/sitemap": "^3.4.0",
|
||||||
"@astrojs/vue": "^5.0.13",
|
"@astrojs/vue": "^5.1.0",
|
||||||
"@nanostores/persistent": "1.0.0",
|
"@nanostores/persistent": "1.0.0",
|
||||||
"@nanostores/vue": "^1.0.0",
|
"@nanostores/vue": "^1.0.0",
|
||||||
"@types/leaflet": "^1.9.17",
|
"@types/leaflet": "^1.9.18",
|
||||||
"@vueuse/core": "^13.1.0",
|
"@vueuse/core": "^13.2.0",
|
||||||
"astro": "^5.7.10",
|
"astro": "^5.8.0",
|
||||||
"nanostores": "^1.0.1",
|
"nanostores": "^1.0.1",
|
||||||
"radix-vue": "^1.9.17",
|
"radix-vue": "^1.9.17",
|
||||||
"vue": "^3.5.13"
|
"vue": "^3.5.14"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"sass": "^1.87.0"
|
"sass": "^1.89.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1074
pnpm-lock.yaml
generated
1074
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
49
public/robots.txt
Normal file
49
public/robots.txt
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
User-agent: GPTBot
|
||||||
|
Disallow: /
|
||||||
|
User-agent: ChatGPT-User
|
||||||
|
Disallow: /
|
||||||
|
User-agent: Google-Extended
|
||||||
|
Disallow: /
|
||||||
|
User-agent: PerplexityBot
|
||||||
|
Disallow: /
|
||||||
|
User-agent: Amazonbot
|
||||||
|
Disallow: /
|
||||||
|
User-agent: ClaudeBot
|
||||||
|
Disallow: /
|
||||||
|
User-agent: Omgilibot
|
||||||
|
Disallow: /
|
||||||
|
User-Agent: FacebookBot
|
||||||
|
Disallow: /
|
||||||
|
User-Agent: Applebot
|
||||||
|
Disallow: /
|
||||||
|
User-agent: anthropic-ai
|
||||||
|
Disallow: /
|
||||||
|
User-agent: Bytespider
|
||||||
|
Disallow: /
|
||||||
|
User-agent: Claude-Web
|
||||||
|
Disallow: /
|
||||||
|
User-agent: Diffbot
|
||||||
|
Disallow: /
|
||||||
|
User-agent: ImagesiftBot
|
||||||
|
Disallow: /
|
||||||
|
User-agent: Omgilibot
|
||||||
|
Disallow: /
|
||||||
|
User-agent: Omgili
|
||||||
|
Disallow: /
|
||||||
|
User-agent: YouBot
|
||||||
|
Disallow: /
|
||||||
|
|
||||||
|
User-agent: AhrefsBot
|
||||||
|
Disallow: /
|
||||||
|
User-agent: AhrefsSiteAudit
|
||||||
|
Disallow: /
|
||||||
|
User-agent: SemrushBot
|
||||||
|
Disallow: /
|
||||||
|
User-agent: YandexMarket
|
||||||
|
Disallow: /
|
||||||
|
User-agent: YandexBot
|
||||||
|
Disallow: /
|
||||||
|
User-agent: DataForSeoBot
|
||||||
|
Disallow: /
|
||||||
|
|
||||||
|
Sitemap: https://maps.alexcreates.fr/sitemap-index.xml
|
||||||
115
src/assets/scss/radix/_toast.scss
Normal file
115
src/assets/scss/radix/_toast.scss
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
.toast-viewport {
|
||||||
|
--viewport-padding: 25px;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
right: 2rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: var(--viewport-padding);
|
||||||
|
gap: 10px;
|
||||||
|
width: 480px;
|
||||||
|
max-width: 100vw;
|
||||||
|
margin: 0;
|
||||||
|
list-style: none;
|
||||||
|
z-index: 2147483647;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-root {
|
||||||
|
position: relative;
|
||||||
|
padding: 15px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas: 'title action' 'description action';
|
||||||
|
grid-template-columns: auto max-content;
|
||||||
|
column-gap: 15px;
|
||||||
|
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-icon svg {
|
||||||
|
fill: var(--green-700);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:has(.toast-icon) {
|
||||||
|
padding-left: 2.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
left: .66rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toast-root[data-state='open'] {
|
||||||
|
animation: slideIn 150ms cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
}
|
||||||
|
.toast-root[data-state='closed'] {
|
||||||
|
animation: hide 100ms ease-in;
|
||||||
|
}
|
||||||
|
.toast-root[data-swipe='move'] {
|
||||||
|
transform: translateX(var(--radix-toast-swipe-move-x));
|
||||||
|
}
|
||||||
|
.toast-root[data-swipe='cancel'] {
|
||||||
|
transform: translateX(0);
|
||||||
|
transition: transform 200ms ease-out;
|
||||||
|
}
|
||||||
|
.toast-root[data-swipe='end'] {
|
||||||
|
animation: swipeOut 100ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes hide {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
transform: translateX(calc(100% + var(--viewport-padding)));
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes swipeOut {
|
||||||
|
from {
|
||||||
|
transform: translateX(var(--radix-toast-swipe-end-x));
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(calc(100% + var(--viewport-padding)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-title {
|
||||||
|
grid-area: title;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--slate-12);
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-description {
|
||||||
|
grid-area: description;
|
||||||
|
margin: 0;
|
||||||
|
color: var(--slate-11);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-action {
|
||||||
|
grid-area: action;
|
||||||
|
}
|
||||||
@@ -9,6 +9,8 @@
|
|||||||
@use 'card';
|
@use 'card';
|
||||||
@use 'map';
|
@use 'map';
|
||||||
|
|
||||||
|
@use 'radix/toast';
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--white: #fff;
|
--white: #fff;
|
||||||
--black: #111;
|
--black: #111;
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import type { MapOverlayProps } from "@/types/Map";
|
|||||||
import SearchMarkers from "./overlay/SearchMarkers.vue";
|
import SearchMarkers from "./overlay/SearchMarkers.vue";
|
||||||
import LangSwitcher from "./overlay/LangSwitcher.vue";
|
import LangSwitcher from "./overlay/LangSwitcher.vue";
|
||||||
import MapOverlayBreadcrumbs from "./MapOverlayBreadcrumbs.astro";
|
import MapOverlayBreadcrumbs from "./MapOverlayBreadcrumbs.astro";
|
||||||
|
import ToastService from "./overlay/ToastService.vue";
|
||||||
|
|
||||||
interface Props extends MapOverlayProps {}
|
interface Props extends MapOverlayProps {}
|
||||||
|
|
||||||
@@ -50,6 +51,8 @@ const currentUrl = Astro.url
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ToastService client:only="vue" />
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.world-overlay {
|
.world-overlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
72
src/components/maps/overlay/ToastService.vue
Normal file
72
src/components/maps/overlay/ToastService.vue
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { isVNode, onMounted } from "vue"
|
||||||
|
import { ToastLifetime, useToast, type Toast } from './useToast.ts'
|
||||||
|
import { ToastDescription, ToastProvider, ToastRoot, ToastTitle, ToastViewport } from 'radix-vue'
|
||||||
|
|
||||||
|
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) {
|
||||||
|
toasts.value.forEach((t, i) => {
|
||||||
|
if (t.id === id) {
|
||||||
|
t.open = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ToastProvider>
|
||||||
|
<ToastRoot
|
||||||
|
class="toast-root"
|
||||||
|
:class="toast.variant ? `toast-${toast.variant}` : ''"
|
||||||
|
v-for="toast in toasts"
|
||||||
|
:key="toast.id"
|
||||||
|
v-bind="toast"
|
||||||
|
@update:open="handleUpdate(toast.id)"
|
||||||
|
>
|
||||||
|
<div v-if="toast.variant === 'success'" class="toast-icon">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#000000" viewBox="0 0 256 256"><path d="M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z"></path></svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ToastTitle class="toast-title" v-if="toast.title">
|
||||||
|
{{ toast.title }}
|
||||||
|
</ToastTitle>
|
||||||
|
<template v-if="toast.description">
|
||||||
|
<ToastDescription class="toast-description" v-if="isVNode(toast.description)">
|
||||||
|
<component :is="toast.description" />
|
||||||
|
</ToastDescription>
|
||||||
|
<ToastDescription v-else class="toast-description">
|
||||||
|
{{ toast.description }}
|
||||||
|
</ToastDescription>
|
||||||
|
</template>
|
||||||
|
</ToastRoot>
|
||||||
|
|
||||||
|
<ToastViewport class="toast-viewport"/>
|
||||||
|
</ToastProvider>
|
||||||
|
</template>
|
||||||
176
src/components/maps/overlay/useToast.ts
Normal file
176
src/components/maps/overlay/useToast.ts
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
import { computed, ref } from "vue"
|
||||||
|
import type { Component, VNode } from "vue"
|
||||||
|
import type { ToastRootProps } from "radix-vue"
|
||||||
|
|
||||||
|
const TOAST_LIMIT = 3
|
||||||
|
const TOAST_REMOVE_DELAY = 1000000
|
||||||
|
|
||||||
|
export interface ToastProps extends ToastRootProps {
|
||||||
|
onOpenChange?: ((value: boolean) => void) | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ToastLifetime {
|
||||||
|
SHORT = 2000,
|
||||||
|
MEDIUM = 3500,
|
||||||
|
LONG = 6000,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StringOrVNode =
|
||||||
|
| string
|
||||||
|
| VNode
|
||||||
|
| (() => VNode)
|
||||||
|
|
||||||
|
type ToasterToast = ToastProps & {
|
||||||
|
id: string
|
||||||
|
title?: string
|
||||||
|
description?: StringOrVNode
|
||||||
|
action?: Component,
|
||||||
|
variant?: "default" | "success" | "destructive"
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionTypes = {
|
||||||
|
ADD_TOAST: "ADD_TOAST",
|
||||||
|
UPDATE_TOAST: "UPDATE_TOAST",
|
||||||
|
DISMISS_TOAST: "DISMISS_TOAST",
|
||||||
|
REMOVE_TOAST: "REMOVE_TOAST",
|
||||||
|
} as const
|
||||||
|
|
||||||
|
let count = 0
|
||||||
|
|
||||||
|
function genId() {
|
||||||
|
count = (count + 1) % Number.MAX_VALUE
|
||||||
|
return count.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActionType = typeof actionTypes
|
||||||
|
|
||||||
|
type Action =
|
||||||
|
| {
|
||||||
|
type: ActionType["ADD_TOAST"]
|
||||||
|
toast: ToasterToast
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: ActionType["UPDATE_TOAST"]
|
||||||
|
toast: Partial<ToasterToast>
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: ActionType["DISMISS_TOAST"]
|
||||||
|
toastId?: ToasterToast["id"]
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: ActionType["REMOVE_TOAST"]
|
||||||
|
toastId?: ToasterToast["id"]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
toasts: ToasterToast[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
|
||||||
|
|
||||||
|
function addToRemoveQueue(toastId: string) {
|
||||||
|
if (toastTimeouts.has(toastId))
|
||||||
|
return
|
||||||
|
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
toastTimeouts.delete(toastId)
|
||||||
|
dispatch({
|
||||||
|
type: actionTypes.REMOVE_TOAST,
|
||||||
|
toastId,
|
||||||
|
})
|
||||||
|
}, TOAST_REMOVE_DELAY)
|
||||||
|
|
||||||
|
toastTimeouts.set(toastId, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = ref<State>({
|
||||||
|
toasts: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
function dispatch(action: Action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case actionTypes.ADD_TOAST:
|
||||||
|
state.value.toasts = [action.toast, ...state.value.toasts].slice(0, TOAST_LIMIT)
|
||||||
|
break
|
||||||
|
|
||||||
|
case actionTypes.UPDATE_TOAST:
|
||||||
|
state.value.toasts = state.value.toasts.map(t =>
|
||||||
|
t.id === action.toast.id ? { ...t, ...action.toast } : t,
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
case actionTypes.DISMISS_TOAST: {
|
||||||
|
const { toastId } = action
|
||||||
|
|
||||||
|
if (toastId) {
|
||||||
|
addToRemoveQueue(toastId)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
state.value.toasts.forEach((toast) => {
|
||||||
|
addToRemoveQueue(toast.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
state.value.toasts = state.value.toasts.map(t =>
|
||||||
|
t.id === toastId || toastId === undefined
|
||||||
|
? {
|
||||||
|
...t,
|
||||||
|
open: false,
|
||||||
|
}
|
||||||
|
: t,
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case actionTypes.REMOVE_TOAST:
|
||||||
|
if (action.toastId === undefined)
|
||||||
|
state.value.toasts = []
|
||||||
|
else
|
||||||
|
state.value.toasts = state.value.toasts.filter(t => t.id !== action.toastId)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function useToast() {
|
||||||
|
return {
|
||||||
|
toasts: computed(() => state.value.toasts),
|
||||||
|
toast,
|
||||||
|
dismiss: (toastId?: string) => dispatch({ type: actionTypes.DISMISS_TOAST, toastId }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Toast = Omit<ToasterToast, "id">
|
||||||
|
|
||||||
|
function toast(props: Toast) {
|
||||||
|
const id = genId()
|
||||||
|
|
||||||
|
const update = (props: ToasterToast) =>
|
||||||
|
dispatch({
|
||||||
|
type: actionTypes.UPDATE_TOAST,
|
||||||
|
toast: { ...props, id },
|
||||||
|
})
|
||||||
|
|
||||||
|
const dismiss = () => dispatch({ type: actionTypes.DISMISS_TOAST, toastId: id })
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: actionTypes.ADD_TOAST,
|
||||||
|
toast: {
|
||||||
|
...props,
|
||||||
|
id,
|
||||||
|
open: true,
|
||||||
|
onOpenChange: (open: boolean) => {
|
||||||
|
if (!open)
|
||||||
|
dismiss()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
dismiss,
|
||||||
|
update,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { toast, useToast }
|
||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user