Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99e66f6feb | ||
|
|
cfe3fe2dcc | ||
|
|
42b3cfd725 | ||
|
|
d3c0a87770 | ||
|
|
3e1d7c5200 | ||
|
|
e893e133cc | ||
|
|
4cf2f66d42 | ||
|
|
f003df7d58 | ||
|
|
355e9ae009 | ||
|
|
ca689c23af | ||
|
|
c282c7ec7a | ||
|
|
0bc65e96c8 | ||
|
|
c6bf77462e |
@@ -1,15 +1,21 @@
|
||||
import { defineConfig } from 'astro/config';
|
||||
|
||||
import { availableLangs, defaultLang } from './src/i18n/ui';
|
||||
import { defineConfig, envField } from 'astro/config';
|
||||
|
||||
import vue from "@astrojs/vue";
|
||||
import sitemap from "@astrojs/sitemap";
|
||||
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
integrations: [vue(), sitemap()],
|
||||
output: 'static',
|
||||
site: 'https://maps.alexcreates.fr',
|
||||
prefetch: true
|
||||
prefetch: true,
|
||||
env: {
|
||||
schema: {
|
||||
ENABLE_TOOLBAR: envField.boolean({ context: "server", access: "secret", default: true }),
|
||||
ENABLE_TOOLBAR_TAGS: envField.boolean({ context: "server", access: "secret", default: true }),
|
||||
ENABLE_CUSTOM_MARKERS: envField.boolean({ context: "server", access: "secret", default: true }),
|
||||
ENABLE_BREADCRUMB: envField.boolean({ context: "server", access: "secret", default: true }),
|
||||
ENABLE_I18N: envField.boolean({ context: "server", access: "secret", default: true }),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
22
package.json
22
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "leim-maps",
|
||||
"type": "module",
|
||||
"version": "1.4.7",
|
||||
"version": "1.4.9",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
@@ -10,18 +10,18 @@
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/sitemap": "^3.3.0",
|
||||
"@astrojs/vue": "^5.0.9",
|
||||
"@nanostores/persistent": "^0.10.2",
|
||||
"@nanostores/vue": "^0.11.0",
|
||||
"@types/leaflet": "^1.9.17",
|
||||
"@vueuse/core": "^13.0.0",
|
||||
"astro": "^5.6.1",
|
||||
"nanostores": "^0.11.4",
|
||||
"@astrojs/sitemap": "^3.4.0",
|
||||
"@astrojs/vue": "^5.1.0",
|
||||
"@nanostores/persistent": "1.0.0",
|
||||
"@nanostores/vue": "^1.0.0",
|
||||
"@types/leaflet": "^1.9.18",
|
||||
"@vueuse/core": "^13.2.0",
|
||||
"astro": "^5.8.0",
|
||||
"nanostores": "^1.0.1",
|
||||
"radix-vue": "^1.9.17",
|
||||
"vue": "^3.5.13"
|
||||
"vue": "^3.5.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"sass": "^1.86.3"
|
||||
"sass": "^1.89.0"
|
||||
}
|
||||
}
|
||||
|
||||
1806
pnpm-lock.yaml
generated
1806
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 'map';
|
||||
|
||||
@use 'radix/toast';
|
||||
|
||||
:root {
|
||||
--white: #fff;
|
||||
--black: #111;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
---
|
||||
import { ENABLE_CUSTOM_MARKERS } from "astro:env/server";
|
||||
|
||||
import type { MapMarker, PlayerMarker } from '@/types/Leaflet';
|
||||
import type { MapProps } from '@/types/Map';
|
||||
import MarkerCreator from './overlay/MarkerCreator.vue';
|
||||
@@ -24,7 +26,9 @@ const {
|
||||
<main class="world-wrapper">
|
||||
<div id="world"></div>
|
||||
|
||||
<MarkerCreator client:only="vue" mapKey={mapKey} />
|
||||
{ENABLE_CUSTOM_MARKERS &&
|
||||
<MarkerCreator client:only="vue" mapKey={mapKey} />
|
||||
}
|
||||
</main>
|
||||
|
||||
<div style="height: 0; width: 0; overflow: hidden; display: none;">
|
||||
@@ -598,6 +602,19 @@ map.on('moveend', () => {
|
||||
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
|
||||
*/
|
||||
@@ -605,7 +622,7 @@ map.on('moveend', () => {
|
||||
* Convert x coords to scale with ratio, zoom and offset
|
||||
* @param {number} x
|
||||
*/
|
||||
function convertXToScale(x) {
|
||||
function convertXToScale(x) {
|
||||
return (x * xRatio * zoomRatio) + xOffset
|
||||
}
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
---
|
||||
import { ENABLE_BREADCRUMB, ENABLE_I18N, ENABLE_TOOLBAR, ENABLE_TOOLBAR_TAGS } from "astro:env/server";
|
||||
|
||||
import type { MapMarker, PlayerMarker } from "@/types/Leaflet";
|
||||
import type { MapOverlayProps } from "@/types/Map";
|
||||
|
||||
import SearchMarkers from "./overlay/SearchMarkers.vue";
|
||||
import LangSwitcher from "./overlay/LangSwitcher.vue";
|
||||
import MapOverlayBreadcrumbs from "./MapOverlayBreadcrumbs.astro";
|
||||
import ToastService from "./overlay/ToastService.vue";
|
||||
|
||||
interface Props extends MapOverlayProps {}
|
||||
|
||||
@@ -25,16 +28,31 @@ const currentUrl = Astro.url
|
||||
|
||||
<div class="world-overlay">
|
||||
<div class="top-bar">
|
||||
<SearchMarkers client:only="vue" mapKey={mapKey} markers={markers} players={players} searchConfig={searchConfig} >
|
||||
<div slot="fallback" style="height: 45px;"></div>
|
||||
</SearchMarkers>
|
||||
{ENABLE_TOOLBAR &&
|
||||
<SearchMarkers
|
||||
client:only="vue"
|
||||
mapKey={mapKey}
|
||||
markers={markers}
|
||||
players={players}
|
||||
searchConfig={searchConfig}
|
||||
disableTags={!ENABLE_TOOLBAR_TAGS}
|
||||
>
|
||||
<div slot="fallback" style="height: 45px;"></div>
|
||||
</SearchMarkers>
|
||||
}
|
||||
|
||||
<LangSwitcher currentUrl={currentUrl} client:only="vue" />
|
||||
{ENABLE_I18N &&
|
||||
<LangSwitcher currentUrl={currentUrl} client:only="vue" />
|
||||
}
|
||||
</div>
|
||||
|
||||
<MapOverlayBreadcrumbs breadcrumbs={breadcrumbs} />
|
||||
{ENABLE_BREADCRUMB &&
|
||||
<MapOverlayBreadcrumbs breadcrumbs={breadcrumbs} />
|
||||
}
|
||||
</div>
|
||||
|
||||
<ToastService client:only="vue" />
|
||||
|
||||
<style lang="scss">
|
||||
.world-overlay {
|
||||
position: absolute;
|
||||
|
||||
@@ -36,6 +36,7 @@ function switchMenuMode(newMode: MenuMode) {
|
||||
|
||||
onMounted(() => {
|
||||
const mapRef = document.getElementById('world')
|
||||
|
||||
if (mapRef) {
|
||||
mapRef.addEventListener('contextmenu', handleContextMenu)
|
||||
mapRef.addEventListener('mousedown', hideMenu)
|
||||
@@ -188,6 +189,11 @@ function hideMarkerModal() {
|
||||
function setTitleError(error: Error | null) {
|
||||
markerTitleInputError.value = error
|
||||
}
|
||||
|
||||
function handleCoordsCopy() {
|
||||
markerMenu.value?.dispatchEvent(new CustomEvent('on-copy-coords', { bubbles: true }))
|
||||
hideMenu()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -196,7 +202,16 @@ function setTitleError(error: Error | null) {
|
||||
<menu v-show="shouldBeShown" ref="markerMenu">
|
||||
<li>
|
||||
<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>
|
||||
</li>
|
||||
</menu>
|
||||
@@ -271,14 +286,23 @@ menu {
|
||||
|
||||
li {
|
||||
a, button {
|
||||
display: block;
|
||||
display: flex;
|
||||
gap: .75ch;
|
||||
align-items: center;
|
||||
padding: .33rem 1rem;
|
||||
padding-left: .66rem;
|
||||
font-size: .77em;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
|
||||
&:hover,
|
||||
&:focus-within {
|
||||
color: var(--blue-700);
|
||||
background-color: var(--slate-100);
|
||||
|
||||
svg {
|
||||
fill: var(--blue-700);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ const props = defineProps<{
|
||||
players: PlayerMarker,
|
||||
mapKey?: string,
|
||||
searchConfig?: SearchConfig
|
||||
disableTags?: boolean
|
||||
}>()
|
||||
|
||||
const customMarkersKey = props.mapKey ? `custom-markers-${props.mapKey}` : 'custom-markers'
|
||||
@@ -286,6 +287,7 @@ function resetAllFields(actionAfter?: "focusAfter") {
|
||||
</div>
|
||||
|
||||
<SearchMarkersTags
|
||||
v-if="!props.disableTags"
|
||||
:current-search-mode="currentSearchMode"
|
||||
:custom-markers="customMarkersData"
|
||||
:search-config="props.searchConfig"
|
||||
|
||||
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 }
|
||||
12
src/env.d.ts
vendored
12
src/env.d.ts
vendored
@@ -1,2 +1,14 @@
|
||||
/// <reference path="../.astro/types.d.ts" />
|
||||
/// <reference types="astro/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly ENABLE_TOOLBAR: boolean
|
||||
readonly ENABLE_TOOLBAR_TAGS: boolean
|
||||
readonly ENABLE_CUSTOM_MARKERS: boolean
|
||||
readonly ENABLE_BREADCRUMB: boolean
|
||||
readonly ENABLE_I18N: boolean
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
@@ -31,9 +31,11 @@ export const translations: LanguageDict = {
|
||||
'maps.search': 'Rechercher la carte',
|
||||
'maps.searchPlaceholder': "Ville, point d'intérêt…",
|
||||
'maps.closeSearch': 'Enlever le filtre',
|
||||
'maps.copyCoords': 'Copier les coordonnées',
|
||||
'maps.markers.new': 'Nouveau marqueur',
|
||||
'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': {
|
||||
'lang.fr': 'Français',
|
||||
@@ -61,8 +63,10 @@ export const translations: LanguageDict = {
|
||||
'maps.search': 'Search the map',
|
||||
'maps.searchPlaceholder': "City, point of interest…",
|
||||
'maps.closeSearch': 'Remove filter',
|
||||
'maps.copyCoords': 'Copy coordinates',
|
||||
'maps.markers.new': 'New 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;
|
||||
|
||||
Reference in New Issue
Block a user