26 Commits

Author SHA1 Message Date
Alexis
2f58f45954 Merge branch 'dev' 2024-05-07 23:09:01 +02:00
Alexis
d695e60a2f Added URL params to communicate with the map 2024-05-07 23:08:54 +02:00
Alexis
f35d0e23b8 Updated dependencies 2024-05-07 23:08:39 +02:00
Alexis
54a4f8bdf5 Merge branch 'dev' 2024-02-26 23:26:14 +01:00
Alexis
c8e8f61f8a Updated version 2024-02-26 23:26:09 +01:00
Alexis
4e347e7d7d Fixed reset button not working properly 2024-02-26 23:16:17 +01:00
Alexis
6086161a78 Added school markers to map 2024-02-26 23:02:37 +01:00
Alexis
3a6abdaf93 Updated packages to latest 2024-02-26 20:52:21 +01:00
Alexis
27b3dfa52d Merge branch 'dev' 2024-02-26 20:47:25 +01:00
Alexis
92c7846247 Fixed overflow of search tags 2024-01-16 13:41:26 +01:00
Alexis
2136ebf1d4 Merge branch 'dev' 2024-01-16 00:31:51 +01:00
Alexis
0819f1063a Updated dependencies 2024-01-16 00:31:42 +01:00
Alexis
b2351240a5 Added og:image 2024-01-16 00:18:43 +01:00
Alexis
be9b2d5a7f Added sitemap generator 2024-01-16 00:12:07 +01:00
Alexis
b5f9a77e40 Bigger input hitbox 2024-01-16 00:07:36 +01:00
Alexis
a9fa3d2f9a Added extra meta on <head> 2024-01-16 00:03:38 +01:00
Alexis
45ead84c20 Optimized library css files 2024-01-15 22:58:58 +01:00
Alexis
21b3aca968 Removed phosphor icons font files 2024-01-15 22:55:00 +01:00
Alexis
89e81093cc Changed icons to svg on SearchMarkersTags 2024-01-15 22:47:44 +01:00
Alexis
ebce56aeff Changed icons to svg on SearchMarkers 2024-01-15 22:36:34 +01:00
Alexis
075bf3e948 Changed icons to svg on MarkerCreator 2024-01-15 22:03:13 +01:00
Alexis
cc9b7704c4 Merge branch 'dev' 2024-01-15 17:49:57 +01:00
Alexis
93fa674b15 Moved ruler to bottom left 2024-01-15 17:49:46 +01:00
Alexis
fd018ba4c3 Keep value on clickoutside 2024-01-15 17:41:17 +01:00
Alexis
721f0d3d44 Added clickoutside handler for marker search 2024-01-15 17:29:51 +01:00
Alexis
aaad18e07f Hotfix : Removed useless imports 2024-01-13 16:59:59 +01:00
36 changed files with 3315 additions and 18677 deletions

View File

@@ -2,4 +2,4 @@
Repository for interactive maps using Leim's Wiki data Repository for interactive maps using Leim's Wiki data
## Demo ## Demo
Live demo available at https://maps.alexcreates.fr/ Live demo available at https://maps.alexcreates.fr

View File

@@ -1,10 +1,11 @@
import { defineConfig } from 'astro/config'; import { defineConfig } from 'astro/config';
import vue from "@astrojs/vue"; import vue from "@astrojs/vue";
import sitemap from "@astrojs/sitemap";
// https://astro.build/config // https://astro.build/config
export default defineConfig({ export default defineConfig({
integrations: [vue()], integrations: [vue(), sitemap()],
output: 'static', output: 'static',
site: 'https://maps.alexcreates.fr' site: 'https://maps.alexcreates.fr'
}); });

5769
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "leim-maps", "name": "leim-maps",
"type": "module", "type": "module",
"version": "2.0.0", "version": "1.2.4",
"scripts": { "scripts": {
"dev": "astro dev", "dev": "astro dev",
"start": "astro dev", "start": "astro dev",
@@ -10,16 +10,14 @@
"astro": "astro" "astro": "astro"
}, },
"dependencies": { "dependencies": {
"@astrojs/node": "^6.0.3", "@astrojs/sitemap": "^3.1.4",
"@astrojs/vue": "^3.0.1", "@astrojs/vue": "^4.1.0",
"@nanostores/vue": "^0.10.0", "@types/leaflet": "^1.9.12",
"@types/leaflet": "^1.9.6", "@vueuse/core": "^10.9.0",
"@vueuse/core": "^10.7.1", "astro": "^4.7.1",
"astro": "^3.2.0", "vue": "^3.4.27"
"nanostores": "^0.9.3",
"vue": "^3.3.4"
}, },
"devDependencies": { "devDependencies": {
"sass": "^1.68.0" "sass": "^1.77.0"
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><path d="M224,104a8,8,0,0,1-13.66,5.66L184,83.31l-42.34,42.35a8,8,0,0,1-11.32-11.32L172.69,72,146.34,45.66A8,8,0,0,1,152,32h64a8,8,0,0,1,8,8Zm-40,24a8,8,0,0,0-8,8v72H48V80h72a8,8,0,0,0,0-16H48A16,16,0,0,0,32,80V208a16,16,0,0,0,16,16H176a16,16,0,0,0,16-16V136A8,8,0,0,0,184,128Z"/></svg>

After

Width:  |  Height:  |  Size: 392 B

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 1.1 MiB

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 1.0 MiB

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -69,6 +69,10 @@
font-size: .85em; font-size: .85em;
width: 1.5rem; width: 1.5rem;
} }
.icon {
width: 1em;
}
} }
&.btn-shadow { &.btn-shadow {

View File

@@ -43,10 +43,13 @@ a[href^='http'] {
a[href^='http']::after { a[href^='http']::after {
position: absolute; position: absolute;
content: "\e972"; content: url('/icon/square-arrow-out.svg');
font-family: 'Phosphor-Fill' !important;
display: inline-block; display: inline-block;
width: 1em; width: 1em;
height: 1em; height: 1em;
margin-left: .25em; margin-left: .25em;
} }
.icon {
width: 1em;
}

View File

@@ -106,6 +106,7 @@ for (let i = 0; i < markers.length; i++) {
const coords = [convertYToScale(m.markerCoords.y), convertXToScale(m.markerCoords.x)] const coords = [convertYToScale(m.markerCoords.y), convertXToScale(m.markerCoords.x)]
let markerIcon let markerIcon
let markerIconOverride
switch (m.group) { switch (m.group) {
case "capitals": case "capitals":
@@ -154,7 +155,20 @@ for (let i = 0; i < markers.length; i++) {
break break
} }
const marker = L.marker(coords, { icon: markerIcon }).addTo(map) // Handle icon override if it's specified
markerIconOverride = markerIcon
if (m.icon) {
markerIconOverride = L.icon({
iconUrl: `icon/${m.icon}.png`,
shadowUrl: `icon/${m.icon}-shadow.png`,
iconSize: [25, 25],
shadowSize: [35, 30],
iconAnchor: [12.5, 8],
shadowAnchor: [11, 12]
})
}
const marker = L.marker(coords, { icon: markerIconOverride }).addTo(map)
let popupContent = "" let popupContent = ""
if (m.link) { if (m.link) {
popupContent = ` popupContent = `
@@ -256,7 +270,7 @@ L.Control.Measure.include({
const rulerDistanceRatio = 2 const rulerDistanceRatio = 2
const rulerOptions = { const rulerOptions = {
position: 'topright', position: 'bottomleft',
primaryLengthUnit: 'kilometers', primaryLengthUnit: 'kilometers',
secondaryLengthUnit: 'days', secondaryLengthUnit: 'days',
primaryAreaUnit: 'hectares', primaryAreaUnit: 'hectares',
@@ -443,6 +457,84 @@ function removeCustomMarker(marker, markerTitle) {
saveCustomMarkers() saveCustomMarkers()
} }
/**
* URL PARAMETERS
*/
/**
* From the leaflet setup, get a working param config
*
* @returns Latitude, Longitude and zoom level of the map
*/
function getURLFromSetup() {
const centerOfMap = map.getCenter()
const lat = centerOfMap.lat
const lon = centerOfMap.lng
const zoom = parseFloat(map.getZoom().toFixed(1))
return { lat, lon, zoom }
}
/**
* Get leaflet coords from URL params
*
* @returns URLSearchParams
*/
function getCoordsSetupFromURL() {
const params = new URL(document.location).searchParams
return params
}
/**
* Check if the params in the URL can be parsed as leaflet coordinates
*
* @return boolean
*/
function hasInitialSetup() {
const params = new URL(document.location).searchParams
return params.has('lat') && params.has('lon')
}
/**
* Function to refresh the URL params
*
* @returns void
*/
function setupToURL() {
const setup = getURLFromSetup()
const newParams = new URLSearchParams(setup)
const newUrl = new URL(window.location.href)
newUrl.search = newParams.toString()
window.history.replaceState(null, '', newUrl.toString())
}
/**
* Uses the URL params to setup the leaflet map
*/
function setupFromURL() {
const setup = getCoordsSetupFromURL()
map.setView({ lat: setup.get('lat'), lng: setup.get('lon') }, setup.get('zoom') | minZoom, { animate: false })
}
// Check if the map has initial params in the URL
// If not, just setup the default URL
if (hasInitialSetup()) {
setupFromURL()
} else {
setupToURL()
}
// When the map stops moving, update the URL to keep it up to date
map.on('moveend', () => {
setupToURL()
})
/** /**
* UTILITIES * UTILITIES
*/ */

View File

@@ -784,6 +784,94 @@
"x": 105.79962111804268, "x": 105.79962111804268,
"y": -82.40552057309549 "y": -82.40552057309549
} }
},
{
"group": "landmarks",
"title": "Faculté de Magie Appliquée",
"description": "Établissement d'enseignements arcaniques tout public, mais à la qualité lacunaire.",
"link": "https://alexcreates.fr/leim/index.php/Facult%C3%A9_de_Magie_Appliqu%C3%A9e",
"markerCoords": {
"x": 103.96761286182101,
"y": -79.90785827537563
},
"icon": "graduation-cap"
},
{
"group": "landmarks",
"title": "Académie Artistique Arcanique",
"description": "Institut d'apprentissage public centré sur les arts.",
"link": "https://alexcreates.fr/leim/index.php/Acad%C3%A9mie_Artistique_Arcanique",
"markerCoords": {
"x": 75.84445789595449,
"y": -199.14437153165204
},
"icon": "graduation-cap"
},
{
"group": "landmarks",
"title": "Académie Centrale Kaldélienne",
"description": "Une des plus grandes académies dispensant des cours de magie de l'Alliance. L'examen d'entrée est public, mais les coûts de scolarité restent à la charge des étudiants.",
"link": "https://alexcreates.fr/leim/index.php/Acad%C3%A9mie_Centrale_Kald%C3%A9lienne",
"markerCoords": {
"x": 83.80737652386473,
"y": -184.52963682119173
},
"icon": "graduation-cap"
},
{
"group": "landmarks",
"title": "Grande Maison de Flamberge",
"description": "La plus prestigieuse école des Terres de Syaltha. La Grande Maison est une des rares entités à certifier les sal'kayas.",
"link": "https://alexcreates.fr/leim/index.php/Grande_Maison_de_Flamberge",
"markerCoords": {
"x": 302.23491236190273,
"y": -110.07337124956246
},
"icon": "graduation-cap"
},
{
"group": "landmarks",
"title": "Haute-École de Magistrature Cordouenne",
"description": "Institut de l'Alliance formant les magistrats, juges et politiciens de demain.",
"link": "https://alexcreates.fr/leim/index.php/Haute-%C3%89cole_de_Magistrature_Cordouenne",
"markerCoords": {
"x": 131.07900289783518,
"y": -148.801890020031
},
"icon": "graduation-cap"
},
{
"group": "landmarks",
"title": "Institut du Fer Blanc",
"description": "Première école militaire de l'Alliance Kaldélienne.",
"link": "https://alexcreates.fr/leim/index.php/Institut_du_Fer_Blanc",
"markerCoords": {
"x": 140.3159943633407,
"y": -191.78102982328744
},
"icon": "graduation-cap"
},
{
"group": "landmarks",
"title": "Maison d'Études de Tourgrise",
"description": "Établissement pluridisciplinaires axé vers les études de législations mais possèdant un cursus d'études arcaniques général.",
"link": "https://alexcreates.fr/leim/index.php/Maison_d%27%C3%89tudes_de_Tourgrise",
"markerCoords": {
"x": 132.82148271354282,
"y": -98.7924395350658
},
"icon": "graduation-cap"
},
{
"group": "landmarks",
"title": "École des Arts Spirituels",
"description": "Institut privé spécialisé dans les maîtrises des arcanes spirituelles.",
"link": "https://alexcreates.fr/leim/index.php/%C3%89cole_des_Arts_Spirituels",
"markerCoords": {
"x": 124.8585640856326,
"y": -95.86995303475213
},
"icon": "graduation-cap"
} }
] ]
} }

View File

@@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { MapMarker } from '@/types/Leaflet'; import type { MapMarker } from '@/types/Leaflet';
import { onClickOutside, onLongPress, useCssVar, useFocus, useLocalStorage, useMouse, useTimeout, useTimeoutFn } from '@vueuse/core' import { onClickOutside, useCssVar, useLocalStorage, useMouse } from '@vueuse/core'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue' import { computed, onMounted, ref, watch } from 'vue'
const markerMenu = ref<HTMLMenuElement>() const markerMenu = ref<HTMLMenuElement>()
@@ -207,7 +207,7 @@ function setTitleError(error: Error | null) {
Ajouter un marqueur personnel Ajouter un marqueur personnel
</h2> </h2>
<p class="modal-notice"> <p class="modal-notice">
<i class="icon ph-light ph-warning"></i> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" class="icon"><rect width="256" height="256" fill="none"/><path d="M142.41,40.22l87.46,151.87C236,202.79,228.08,216,215.46,216H40.54C27.92,216,20,202.79,26.13,192.09L113.59,40.22C119.89,29.26,136.11,29.26,142.41,40.22Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"/><line x1="128" y1="144" x2="128" y2="104" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"/><circle cx="128" cy="180" r="10"/></svg>
<span>Le marqueur sera sauvegardé mais n'apparaîtra que sur votre carte !</span> <span>Le marqueur sera sauvegardé mais n'apparaîtra que sur votre carte !</span>
</p> </p>
</div> </div>
@@ -242,7 +242,7 @@ function setTitleError(error: Error | null) {
<div class="modal-actions"> <div class="modal-actions">
<button class="btn btn-danger btn-icon btn-shadow" @click="hideMarkerModal"> <button class="btn btn-danger btn-icon btn-shadow" @click="hideMarkerModal">
<i class="ph-light ph-x"></i> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" class="icon"><rect width="256" height="256" fill="none"/><line x1="200" y1="56" x2="56" y2="200" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="200" y1="200" x2="56" y2="56" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>
</button> </button>
</div> </div>
</dialog> </dialog>
@@ -340,7 +340,7 @@ dialog {
font-size: .8em; font-size: .8em;
.icon { .icon {
font-size: 1.3em; width: 1.3em;
} }
} }
} }

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { MapMarker, MapMarkerGroup, PlayerMarker } from '@/types/Leaflet'; import type { MapMarker, MapMarkerGroup, PlayerMarker } from '@/types/Leaflet';
import type { SearchMode } from '@/types/Search'; import type { SearchMode } from '@/types/Search';
import { useFocus, useLocalStorage, useMagicKeys, whenever } from '@vueuse/core'; import { onClickOutside, useFocus, useFocusWithin, useLocalStorage, useMagicKeys, whenever } from '@vueuse/core';
import { computed, onMounted, ref, onUpdated, watch } from 'vue'; import { computed, onMounted, ref, onUpdated, watch } from 'vue';
import SearchMarkersTags from './SearchMarkersTags.vue'; import SearchMarkersTags from './SearchMarkersTags.vue';
@@ -19,13 +19,15 @@ const allMarkers = computed(() => {
// Search functions // Search functions
const searchBar = ref<HTMLDivElement>() const searchBar = ref<HTMLDivElement>()
const searchBarWrapper = ref<HTMLDivElement>()
const qInput = ref() const qInput = ref()
const { focused: isFocused } = useFocus(qInput) const { focused: isComponentFocused } = useFocusWithin(searchBarWrapper)
const { focused: isSearchFocused } = useFocus(qInput)
const currentSearchMode = ref<SearchMode>('query') const currentSearchMode = ref<SearchMode>('query')
onMounted(() => { onMounted(() => {
isFocused.value = true isSearchFocused.value = true
}) })
const hasQuery = computed(() => q.value.length > 0) const hasQuery = computed(() => q.value.length > 0)
@@ -57,8 +59,8 @@ const filteredMarkers = computed(() => {
// Limit of menu list dropdown // Limit of menu list dropdown
const currentLimit = computed(() => { const currentLimit = computed(() => {
if (currentSearchMode.value === "query") return 10 if (currentSearchMode.value === "query") return 50
return 100 return 500
}) })
/** /**
@@ -70,11 +72,10 @@ const shortcutKeyCombo = keys['Shift+Period']
const eraseKeyCombo = keys['Escape'] const eraseKeyCombo = keys['Escape']
whenever(shortcutKeyCombo, () => { whenever(shortcutKeyCombo, () => {
isFocused.value = true isSearchFocused.value = true
}) })
whenever(eraseKeyCombo, () => { whenever(eraseKeyCombo, () => {
resetAllFields() resetAllFields('focusAfter')
isFocused.value = true
}) })
// If query changes and has new value... // If query changes and has new value...
@@ -82,6 +83,9 @@ watch(q, (n, o) => {
if (n) setSearchMode('query') if (n) setSearchMode('query')
}) })
// Toggle menu if clicked outside
onClickOutside(searchBarWrapper, () => setSearchMode('query'))
/** /**
* Player geolocation * Player geolocation
* Uses native JS to avoid changing leaflet library * Uses native JS to avoid changing leaflet library
@@ -133,6 +137,26 @@ function handleCategorySwitch(g: MapMarkerGroup) {
} }
} }
/**
* Custom Markers handling
*/
onMounted(() => {
customMarkersData.value = useLocalStorage('custom-markers', []).value
document.addEventListener('refresh-custom-markers', () => {
customMarkersData.value = useLocalStorage('custom-markers', []).value
})
})
/**
* Emit event to delete custom marker
*/
function emitDeleteCustomMarker(markerTitle: string) {
const deleteCMarkerEvent = new CustomEvent(`delete-${markerTitle}`, { bubbles: true })
searchBar.value?.dispatchEvent(deleteCMarkerEvent)
}
/** /**
* EVENTS * EVENTS
*/ */
@@ -163,45 +187,27 @@ function resetQueryValue() {
*/ */
function resetAllFields(actionAfter?: "focusAfter") { function resetAllFields(actionAfter?: "focusAfter") {
setSearchMode('query') setSearchMode('query')
resetQueryValue() resetQueryValue()
if (actionAfter === "focusAfter") isFocused.value = true if (actionAfter === "focusAfter") isSearchFocused.value = true
}
/**
* CUSTOM MARKERS HANDLING
*/
onMounted(() => {
customMarkersData.value = useLocalStorage('custom-markers', []).value
document.addEventListener('refresh-custom-markers', () => {
customMarkersData.value = useLocalStorage('custom-markers', []).value
})
})
/**
* Emit event to delete custom marker
*/
function emitDeleteCustomMarker(markerTitle: string) {
const deleteCMarkerEvent = new CustomEvent(`delete-${markerTitle}`, { bubbles: true })
searchBar.value?.dispatchEvent(deleteCMarkerEvent)
} }
</script> </script>
<template> <template>
<nav class="toolbar"> <nav ref="searchBarWrapper" class="toolbar">
<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> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" class="search-icon"><rect width="256" height="256" fill="none"/><path d="M168,112a56,56,0,1,1-56-56A56,56,0,0,1,168,112Zm61.66,117.66a8,8,0,0,1-11.32,0l-50.06-50.07a88,88,0,1,1,11.32-11.31l50.06,50.06A8,8,0,0,1,229.66,229.66ZM112,184a72,72,0,1,0-72-72A72.08,72.08,0,0,0,112,184Z"/></svg>
<input ref="qInput" class="" 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> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" class="icon"><rect width="256" height="256" fill="none"/><line x1="200" y1="56" x2="56" y2="200" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"/><line x1="200" y1="200" x2="56" y2="56" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="12"/></svg>
</button> </button>
<button v-if="hasPlayers" data-to-players class="player-btn" :tabindex="shouldBeActive ? 1 : 0" title="Aller à la position actuelle des joueurs"> <button v-if="hasPlayers" data-to-players class="player-btn" :tabindex="shouldBeActive ? 1 : 0" title="Aller à la position actuelle des joueurs">
<i class="pin-icon ph-fill ph-map-pin"></i> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" class="icon"><rect width="256" height="256" fill="none"/><path d="M128,16a88.1,88.1,0,0,0-88,88c0,75.3,80,132.17,83.41,134.55a8,8,0,0,0,9.18,0C136,236.17,216,179.3,216,104A88.1,88.1,0,0,0,128,16Zm0,56a32,32,0,1,1-32,32A32,32,0,0,1,128,72Z"/></svg>
</button> </button>
</div> </div>
@@ -218,20 +224,24 @@ function emitDeleteCustomMarker(markerTitle: string) {
<div class="desc" v-html="m.description"></div> <div class="desc" v-html="m.description"></div>
<div class="icon"> <div v-if="m.icon">
<i v-if="m.group === 'quests'" class="ph-fill ph-flag-banner"></i> <svg v-if="m.icon === 'graduation-cap'" xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 256 256"><path d="M251.76,88.94l-120-64a8,8,0,0,0-7.52,0l-120,64a8,8,0,0,0,0,14.12L32,117.87v48.42a15.91,15.91,0,0,0,4.06,10.65C49.16,191.53,78.51,216,128,216a130,130,0,0,0,48-8.76V240a8,8,0,0,0,16,0V199.51a115.63,115.63,0,0,0,27.94-22.57A15.91,15.91,0,0,0,224,166.29V117.87l27.76-14.81a8,8,0,0,0,0-14.12ZM128,200c-43.27,0-68.72-21.14-80-33.71V126.4l76.24,40.66a8,8,0,0,0,7.52,0L176,143.47v46.34C163.4,195.69,147.52,200,128,200Zm80-33.75a97.83,97.83,0,0,1-16,14.25V134.93l16-8.53ZM188,118.94l-.22-.13-56-29.87a8,8,0,0,0-7.52,14.12L171,128l-43,22.93L25,96,128,41.07,231,96Z"></path></svg>
<i v-else-if="m.group === 'landmarks'" class="ph-fill ph-castle-turret"></i> </div>
<div v-else>
<svg v-if="m.group === 'quests'" class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><path d="M231.22,59.44l-80,168a8,8,0,1,1-14.44-6.88L165.62,160H32a8,8,0,0,1-5.88-13.43l42.56-46.1L26.59,61.9A8,8,0,0,1,32,48H224a8,8,0,0,1,7.22,11.44Z"/></svg>
<svg v-else-if="m.group === 'capitals'" class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><path d="M200,24h-8a16,16,0,0,0-16,16V56H148V40a16,16,0,0,0-16-16h-8a16,16,0,0,0-16,16V56H80V40A16,16,0,0,0,64,24H56A16,16,0,0,0,40,40V84.69A15.86,15.86,0,0,0,44.69,96L56,107.31V216a16,16,0,0,0,16,16H184a16,16,0,0,0,16-16V107.31L211.31,96A15.86,15.86,0,0,0,216,84.69V40A16,16,0,0,0,200,24ZM152,216H104V152a24,24,0,0,1,48,0Z"/></svg>
<svg v-else-if="m.group === 'landmarks'" class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><path d="M208,80a8,8,0,0,0-8,8v16H188.85L184,55.2A8,8,0,0,0,181.31,50h0L138.44,11.88l-.2-.17a16,16,0,0,0-20.48,0l-.2.17L74.68,50v0A7.93,7.93,0,0,0,72,55.2L67.15,104H56V88a8,8,0,0,0-16,0v24a8,8,0,0,0,8,8H65.54l-9.47,94.48A16,16,0,0,0,72,232H184a16,16,0,0,0,15.92-17.56L190.46,120H208a8,8,0,0,0,8-8V88A8,8,0,0,0,208,80ZM87.24,64h81.52l4,40H136V88a8,8,0,0,0-16,0v16H83.23ZM72,216l4.81-48H179.19L184,216Z"/></svg>
</div> </div>
</button> </button>
<!-- Custom marker actions --> <!-- Custom marker actions -->
<div class="search-item-actions" v-if="m.group === 'custom'"> <div class="search-item-actions" v-if="m.group === 'custom'">
<!-- <button class="btn btn-info btn-icon btn-sm"> <!-- <button class="btn btn-info btn-icon btn-sm">
<i class="ph-light ph-pencil-simple-line"></i> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" class="icon"><rect width="256" height="256" fill="none"/><path d="M92.69,216H48a8,8,0,0,1-8-8V163.31a8,8,0,0,1,2.34-5.65L165.66,34.34a8,8,0,0,1,11.31,0L221.66,79a8,8,0,0,1,0,11.31L98.34,213.66A8,8,0,0,1,92.69,216Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="136" y1="64" x2="192" y2="120" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>
</button> --> </button> -->
<button class="btn btn-danger btn-icon btn-sm" @click="emitDeleteCustomMarker(m.title)"> <button class="btn btn-danger btn-icon btn-sm" @click="emitDeleteCustomMarker(m.title)">
<i class="ph-light ph-trash"></i> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" class="icon"><rect width="256" height="256" fill="none"/><line x1="216" y1="56" x2="40" y2="56" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="104" y1="104" x2="104" y2="168" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="152" y1="104" x2="152" y2="168" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path d="M200,56V208a8,8,0,0,1-8,8H64a8,8,0,0,1-8-8V56" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path d="M168,56V40a16,16,0,0,0-16-16H104A16,16,0,0,0,88,40V56" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>
</button> </button>
</div> </div>
</li> </li>
@@ -272,12 +282,12 @@ function emitDeleteCustomMarker(markerTitle: string) {
$search-icon-size: 1.2em; $search-icon-size: 1.2em;
position: relative; position: relative;
padding-block: .2rem;
display: flex; display: flex;
align-items: center; align-items: center;
gap: $search-items-gap; gap: $search-items-gap;
input { input {
padding-block: .2rem;
display: block; display: block;
min-width: 10rem; min-width: 10rem;
padding-inline-start: calc($search-icon-size + $search-items-gap); padding-inline-start: calc($search-icon-size + $search-items-gap);
@@ -293,8 +303,8 @@ function emitDeleteCustomMarker(markerTitle: string) {
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
left: 0; left: 0;
font-size: $search-icon-size; width: $search-icon-size;
color: var(--slate-400); fill: var(--slate-400);
pointer-events: none; pointer-events: none;
} }
@@ -337,8 +347,11 @@ function emitDeleteCustomMarker(markerTitle: string) {
.player-btn { .player-btn {
margin-left: .25rem; margin-left: .25rem;
font-size: 1.5em;
color: var(--red-500); .icon {
font-size: 1.5em;
fill: var(--red-500);
}
&:hover { &:hover {
&::before { &::before {
@@ -362,10 +375,14 @@ function emitDeleteCustomMarker(markerTitle: string) {
} }
.search-results { .search-results {
max-height: 50vh; max-height: 20vh;
overflow-y: auto; overflow-y: auto;
scrollbar-gutter: stable; scrollbar-gutter: stable;
@media screen and (width >= 900px) {
max-height: 50vh;
}
&::-webkit-scrollbar, &::-webkit-scrollbar,
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
width: 26px; width: 26px;
@@ -429,7 +446,7 @@ function emitDeleteCustomMarker(markerTitle: string) {
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
right: .75rem; right: .75rem;
color: var(--slate-400); fill: var(--slate-400);
} }
&:focus-visible { &:focus-visible {
@@ -457,6 +474,12 @@ function emitDeleteCustomMarker(markerTitle: string) {
} }
} }
} }
&.group-custom {
.title {
font-style: italic;
}
}
} }
/** /**
@@ -467,6 +490,9 @@ function emitDeleteCustomMarker(markerTitle: string) {
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
right: .25em; right: .25em;
display: flex;
align-items: center;
gap: .3rem;
transition-property: visibility, opacity; transition-property: visibility, opacity;
transition-duration: .2s; transition-duration: .2s;
transition-timing-function: cubic-bezier(0.785, 0.135, 0.15, 0.86); transition-timing-function: cubic-bezier(0.785, 0.135, 0.15, 0.86);

View File

@@ -26,10 +26,9 @@ function emitCategorySwitch(newCategory: MapMarkerGroup) {
'active': currentSearchMode === 'quests' 'active': currentSearchMode === 'quests'
}" }"
> >
<span class="icon"> <svg v-if="currentSearchMode === 'quests'" class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><polyline points="40 144 96 200 224 72" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>
<i v-if="currentSearchMode === 'quests'" class="ph-bold ph-check"></i> <svg v-else class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><path d="M231.22,59.44l-80,168a8,8,0,1,1-14.44-6.88L165.62,160H32a8,8,0,0,1-5.88-13.43l42.56-46.1L26.59,61.9A8,8,0,0,1,32,48H224a8,8,0,0,1,7.22,11.44Z"/></svg>
<i v-else class="ph-fill ph-flag-banner"></i>
</span>
<span class="label"> <span class="label">
Quêtes Quêtes
</span> </span>
@@ -44,10 +43,9 @@ function emitCategorySwitch(newCategory: MapMarkerGroup) {
'active': currentSearchMode === 'capitals' 'active': currentSearchMode === 'capitals'
}" }"
> >
<span class="icon"> <svg v-if="currentSearchMode === 'capitals'" class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><polyline points="40 144 96 200 224 72" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>
<i v-if="currentSearchMode === 'capitals'" class="ph-bold ph-check"></i> <svg v-else class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><path d="M200,24h-8a16,16,0,0,0-16,16V56H148V40a16,16,0,0,0-16-16h-8a16,16,0,0,0-16,16V56H80V40A16,16,0,0,0,64,24H56A16,16,0,0,0,40,40V84.69A15.86,15.86,0,0,0,44.69,96L56,107.31V216a16,16,0,0,0,16,16H184a16,16,0,0,0,16-16V107.31L211.31,96A15.86,15.86,0,0,0,216,84.69V40A16,16,0,0,0,200,24ZM152,216H104V152a24,24,0,0,1,48,0Z"/></svg>
<i v-else class="ph-fill ph-castle-turret"></i>
</span>
<span class="label"> <span class="label">
Capitales Capitales
</span> </span>
@@ -62,10 +60,9 @@ function emitCategorySwitch(newCategory: MapMarkerGroup) {
'active': currentSearchMode === 'landmarks' 'active': currentSearchMode === 'landmarks'
}" }"
> >
<span class="icon"> <svg v-if="currentSearchMode === 'landmarks'" class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><polyline points="40 144 96 200 224 72" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>
<i v-if="currentSearchMode === 'landmarks'" class="ph-bold ph-check"></i> <svg v-else class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><path d="M208,80a8,8,0,0,0-8,8v16H188.85L184,55.2A8,8,0,0,0,181.31,50h0L138.44,11.88l-.2-.17a16,16,0,0,0-20.48,0l-.2.17L74.68,50v0A7.93,7.93,0,0,0,72,55.2L67.15,104H56V88a8,8,0,0,0-16,0v24a8,8,0,0,0,8,8H65.54l-9.47,94.48A16,16,0,0,0,72,232H184a16,16,0,0,0,15.92-17.56L190.46,120H208a8,8,0,0,0,8-8V88A8,8,0,0,0,208,80ZM87.24,64h81.52l4,40H136V88a8,8,0,0,0-16,0v16H83.23ZM72,216l4.81-48H179.19L184,216Z"/></svg>
<i v-else class="ph-fill ph-lighthouse"></i>
</span>
<span class="label"> <span class="label">
Points d'intérêt Points d'intérêt
</span> </span>
@@ -80,10 +77,9 @@ function emitCategorySwitch(newCategory: MapMarkerGroup) {
'active': currentSearchMode === 'custom' 'active': currentSearchMode === 'custom'
}" }"
> >
<span class="icon"> <svg v-if="currentSearchMode === 'custom'" class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><polyline points="40 144 96 200 224 72" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/></svg>
<i v-if="currentSearchMode === 'custom'" class="ph-bold ph-check"></i> <svg v-else xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 256 256"><rect width="256" height="256" fill="none"/><path d="M172,120a44,44,0,1,1-44-44A44.05,44.05,0,0,1,172,120Zm60,8A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88.09,88.09,0,0,0-91.47-87.93C77.43,41.89,39.87,81.12,40,128.25a87.65,87.65,0,0,0,22.24,58.16A79.71,79.71,0,0,1,84,165.1a4,4,0,0,1,4.83.32,59.83,59.83,0,0,0,78.28,0,4,4,0,0,1,4.83-.32,79.71,79.71,0,0,1,21.79,21.31A87.62,87.62,0,0,0,216,128Z"/></svg>
<i v-else class="ph-fill ph-user-circle"></i>
</span>
<span class="label"> <span class="label">
Personnels Personnels
</span> </span>
@@ -95,6 +91,7 @@ function emitCategorySwitch(newCategory: MapMarkerGroup) {
<style lang="scss" scoped> <style lang="scss" scoped>
.tag-list { .tag-list {
display: flex; display: flex;
flex-wrap: wrap;
gap: .5rem; gap: .5rem;
margin-top: .5rem; margin-top: .5rem;
@@ -128,6 +125,7 @@ function emitCategorySwitch(newCategory: MapMarkerGroup) {
border: 1px solid var(--slate-400); border: 1px solid var(--slate-400);
border-radius: 100vmax; border-radius: 100vmax;
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px; box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
white-space: nowrap;
pointer-events: all; pointer-events: all;
cursor: pointer; cursor: pointer;
outline: .2rem solid transparent; outline: .2rem solid transparent;
@@ -140,7 +138,7 @@ function emitCategorySwitch(newCategory: MapMarkerGroup) {
} }
.icon { .icon {
font-size: 1.1em; width: 1.1em;
} }
.label { .label {
@@ -155,12 +153,20 @@ function emitCategorySwitch(newCategory: MapMarkerGroup) {
color: var(--white); color: var(--white);
background-color: var(--#{$c}-500); background-color: var(--#{$c}-500);
border-color: var(--#{$c}-700); border-color: var(--#{$c}-700);
.icon {
fill: var(--white);
}
} }
&:not(.active) { &:not(.active) {
&:hover, &:hover,
&:focus-visible { &:focus-visible {
color: var(--#{$c}-500); color: var(--#{$c}-500);
.icon {
fill: var(--#{$c}-500);
}
} }
} }

1
src/env.d.ts vendored
View File

@@ -1 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" /> /// <reference types="astro/client" />

View File

@@ -12,10 +12,12 @@ const { title } = Astro.props;
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="author" content="Alexis Pelé" /> <meta name="author" content="Alexis Pelé" />
<meta name="description" content="Carte du monde connu de Léim, centrée sur l'Alliance Kaldélienne" /> <meta name="description" content="Carte du monde connu de Léim, centrée sur l'Alliance Kaldélienne" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width; initial-scale=1" />
<meta name="generator" content={Astro.generator} /> <meta name="generator" content={Astro.generator} />
<title>{title} — Leim Wiki</title> <title>{title} — Leim Wiki</title>
<link rel="canonical" href={Astro.url}>
<link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon.png"> <link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
@@ -24,16 +26,17 @@ const { title } = Astro.props;
<meta name="msapplication-TileColor" content="#00aba9"> <meta name="msapplication-TileColor" content="#00aba9">
<meta name="theme-color" content="#a9d1eb"> <meta name="theme-color" content="#a9d1eb">
<link rel="stylesheet" href="phicons/bold/style.css" /> <meta property="og:title" content={title}/>
<link rel="stylesheet" href="phicons/fill/style.css" /> <meta property="og:type" content="map" />
<link rel="stylesheet" href="phicons/light/style.css" /> <meta property="og:url" content={Astro.url} />
<meta property="og:image" content="/bailey-zindel-NRQV-hBF10M-unsplash.jpg" />
<link rel="stylesheet" href="leaflet/leaflet.css" rel="preload" /> <link rel="stylesheet" href="/leaflet/leaflet.css" rel="preload" />
<script is:inline src="leaflet/leaflet.js"></script> <script is:inline src="/leaflet/leaflet.js"></script>
<script is:inline type="text/javascript" src="/js/leaflet-zoomify.min.js"></script> <script is:inline type="text/javascript" src="/js/leaflet-zoomify.min.js"></script>
<link rel="stylesheet" href="leaflet-measure/leaflet-measure.css" rel="preload" /> <link rel="stylesheet" href="/leaflet-measure/leaflet-measure.css" rel="preload" />
<script is:inline type="text/javascript" src="/js/leaflet-measure.min.js"></script> <script is:inline type="text/javascript" src="/js/leaflet-measure.min.js"></script>
</head> </head>
<body> <body>

View File

@@ -1,4 +1,5 @@
export type MapMarkerGroup = "capitals" | "cities" | "towns" | "landmarks" | "quests" | "custom"; export type MapMarkerGroup = "capitals" | "cities" | "towns" | "landmarks" | "quests" | "custom";
export type MapMarkerIcon = "castle" | "flag" | "graduation-cap" | "location-pin" | "monument" | "push-pin"
export type MapCoords = { export type MapCoords = {
x: number, x: number,
@@ -11,6 +12,7 @@ export type MapMarker = {
description?: string, description?: string,
link?: string, link?: string,
group?: MapMarkerGroup, group?: MapMarkerGroup,
icon?: MapMarkerIcon,
} }
export type PlayerMarker = { export type PlayerMarker = {