Added flyTo support ; targets players

The implementation forces Vue to attach vanilla event handlers, because their VDom doesn't bubble up and there's no way for it to easily communicate with Astro components.
Nanostores aren't an option because .astro files are rendered on the server, so the store can't be used client side (would be nice if it did tho!!!)
This commit is contained in:
Alexis
2023-10-13 17:44:52 +02:00
parent e654d3f96a
commit 085887615a
3 changed files with 179 additions and 176 deletions

View File

@@ -12,177 +12,185 @@ const { markers, players } = $world.get()
</div>
<script lang="ts" define:vars={{ markers, players }}>
const mapHeight = 15168;
const mapWidth = 14658;
const mapHeight = 15168;
const mapWidth = 14658;
const map = L.map('world', {
crs: L.CRS.Simple,
maxZoom: 12,
minZoom: 2.5,
zoom: 2.5,
zoomSnap: 0,
zoomControl: false
const map = L.map('world', {
crs: L.CRS.Simple,
maxZoom: 12,
minZoom: 2.5,
zoom: 2.5,
zoomSnap: 0,
zoomControl: false
});
L.control.zoom({
position: 'bottomright',
}).addTo(map);
// Layers
const layer = L.tileLayer.zoomify('/zoomify/alliance-kaldelienne/{g}/{z}-{x}-{y}.jpg', {
width: mapWidth,
height: mapHeight,
}).addTo(map);
const layerGroups = {
players: L.layerGroup(),
capitals: L.layerGroup(),
cities: L.layerGroup(),
towns: L.layerGroup(),
landmarks: L.layerGroup(),
};
/**
* Build all markers and their related info
*/
for (let i = 0; i < markers.length; i++) {
const m = markers[i];
const coords = [m.markerCoords.y, m.markerCoords.x];
let markerIcon
switch (m.group) {
case "capitals":
markerIcon = L.icon({
iconUrl: `icons/castle.png`,
shadowUrl: `icons/castle-shadow.png`,
iconSize: [25, 25],
shadowSize: [35, 30],
iconAnchor: [12.5, 8],
shadowAnchor: [11, 12]
});
break;
case "cities":
markerIcon = L.icon({
iconUrl: `icons/circle.png`,
iconSize: [12, 12],
});
break;
case "landmarks":
markerIcon = L.icon({
iconUrl: `icons/monument.png`,
shadowUrl: `icons/monument-shadow.png`,
iconSize: [18, 18],
shadowSize: [32, 14],
iconAnchor: [12.5, 8],
shadowAnchor: [15, 6]
});
break;
case "towns":
default:
iconKey = "house"
break;
}
const marker = L.marker(coords, { icon: markerIcon }).addTo(map);
const popupContent = `
<b>
<a href="${m.link}" target="_blank">
${m.title}
</a>
</b>
<br>
${m.description}
`;
marker.bindPopup(popupContent).openPopup();
const displayTooltip = m.group === 'capitals';
if (displayTooltip) {
L.tooltip({ permanent: true, direction: 'top', offset: [0, 2], content: m.title, className: "capital-name", opacity: 1 }).setLatLng(coords).addTo(map);
}
marker.addTo(layerGroups[m.group])
}
/**
* Add player's position marker
*/
if ( players ) {
const playersPosition = [players.markerCoords.y, players.markerCoords.x];
const playerIcon = L.icon({
iconUrl: `icons/location-pin.png`,
shadowUrl: `icons/location-pin-shadow.png`,
iconSize: [18, 24],
shadowSize: [20, 16],
iconAnchor: [11, 8],
shadowAnchor: [3, 0],
});
L.control.zoom({
position: 'bottomright',
}).addTo(map);
const playerMarker = L.marker(playersPosition, { icon: playerIcon }).addTo(map)
playerMarker.addTo(layerGroups['players'])
}
// Layers
const layer = L.tileLayer.zoomify('/zoomify/alliance-kaldelienne/{g}/{z}-{x}-{y}.jpg', {
width: mapWidth,
height: mapHeight,
}).addTo(map);
map.fitBounds(layer.getBounds());
const layerGroups = {
players: L.layerGroup(),
capitals: L.layerGroup(),
cities: L.layerGroup(),
towns: L.layerGroup(),
landmarks: L.layerGroup(),
};
// L.control.layers({ "base": layer }, layerGroups).addTo(map);
/**
* Build all markers and their related info
*/
for (let i = 0; i < markers.length; i++) {
const m = markers[i];
/**
* RULER
*/
/**
* Workaround specific to leaflet version
* See https://github.com/ljagis/leaflet-measure/issues/171 for details
*/
L.Control.Measure.include({
// set icon on the capture marker
_setCaptureMarkerIcon: function () {
// disable autopan
this._captureMarker.options.autoPanOnFocus = false;
const coords = [m.markerCoords.y, m.markerCoords.x];
// default function
this._captureMarker.setIcon(
L.divIcon({
iconSize: this._map.getSize().multiplyBy(2)
})
);
},
});
let markerIcon
switch (m.group) {
case "capitals":
markerIcon = L.icon({
iconUrl: `icons/castle.png`,
shadowUrl: `icons/castle-shadow.png`,
iconSize: [25, 25],
shadowSize: [35, 30],
iconAnchor: [12.5, 8],
shadowAnchor: [11, 12]
});
break;
case "cities":
markerIcon = L.icon({
iconUrl: `icons/circle.png`,
iconSize: [12, 12],
});
break;
case "landmarks":
markerIcon = L.icon({
iconUrl: `icons/monument.png`,
shadowUrl: `icons/monument-shadow.png`,
iconSize: [18, 18],
shadowSize: [32, 14],
iconAnchor: [12.5, 8],
shadowAnchor: [15, 6]
});
break;
case "towns":
default:
iconKey = "house"
break;
}
const marker = L.marker(coords, { icon: markerIcon }).addTo(map);
const popupContent = `
<b>
<a href="${m.link}" target="_blank">
${m.title}
</a>
</b>
<br>
${m.description}
`;
marker.bindPopup(popupContent).openPopup();
const displayTooltip = m.group === 'capitals';
if (displayTooltip) {
L.tooltip({ permanent: true, direction: 'top', offset: [0, 2], content: m.title, className: "capital-name", opacity: 1 }).setLatLng(coords).addTo(map);
}
marker.addTo(layerGroups[m.group])
}
/**
* Add player's position marker
*/
if ( players ) {
const playersPosition = [players.markerCoords.y, players.markerCoords.x];
const playerIcon = L.icon({
iconUrl: `icons/location-pin.png`,
shadowUrl: `icons/location-pin-shadow.png`,
iconSize: [18, 24],
shadowSize: [20, 16],
iconAnchor: [11, 8],
shadowAnchor: [3, 0],
});
const playerMarker = L.marker(playersPosition, { icon: playerIcon }).addTo(map)
playerMarker.addTo(layerGroups['players'])
}
map.fitBounds(layer.getBounds());
// L.control.layers({ "base": layer }, layerGroups).addTo(map);
/**
* RULER
*/
/**
* Workaround specific to leaflet version
* See https://github.com/ljagis/leaflet-measure/issues/171 for details
*/
L.Control.Measure.include({
// set icon on the capture marker
_setCaptureMarkerIcon: function () {
// disable autopan
this._captureMarker.options.autoPanOnFocus = false;
// default function
this._captureMarker.setIcon(
L.divIcon({
iconSize: this._map.getSize().multiplyBy(2)
})
);
const rulerOptions = {
position: 'topright',
primaryLengthUnit: 'kilometers',
secondaryLengthUnit: 'days',
primaryAreaUnit: 'hectares',
units: {
kilometers: {
factor: 0.00013,
display: 'Kilomètres',
},
});
const rulerOptions = {
position: 'topright',
primaryLengthUnit: 'kilometers',
secondaryLengthUnit: 'days',
primaryAreaUnit: 'hectares',
units: {
kilometers: {
factor: 0.00013,
display: 'Kilomètres',
},
days: {
factor: 0.000002,
display: 'Jours de marche',
decimals: 1
}
days: {
factor: 0.000002,
display: 'Jours de marche',
decimals: 1
}
};
}
};
const measureControl = L.control.measure(rulerOptions);
measureControl.addTo(map);
const measureControl = L.control.measure(rulerOptions);
measureControl.addTo(map);
/**
* DEBUGS
*/
map.addEventListener('click', (event) => {
let lat = Math.round(event.latlng.lat * 100000) / 100000;
let lng = Math.round(event.latlng.lng * 100000) / 100000;
console.log(lat, lng);
})
/**
* Flying to points of interests
*/
document.addEventListener('fly-to', (e) => {
const targetCoords = e.detail
map.flyTo([targetCoords.y, targetCoords.x], 5)
})
/**
* DEBUGS
*/
map.addEventListener('click', (event) => {
let lat = Math.round(event.latlng.lat * 100000) / 100000;
let lng = Math.round(event.latlng.lng * 100000) / 100000;
console.log(lat, lng);
})
</script>
<style lang="scss">

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { allTasks } from 'nanostores';
import { useStore } from '@nanostores/vue'
import { $world, flyTo } from '../worldStore';
import { $world } from '../worldStore';
import { useFocus, useMagicKeys, whenever } from '@vueuse/core';
import { computed, onMounted, ref } from 'vue';
@@ -9,7 +9,7 @@ import { computed, onMounted, ref } from 'vue';
$world.listen(() => {})
await allTasks()
const { markers, players, lastCoords } = useStore($world).value
const { markers, players } = useStore($world).value
// Search functions
const qInput = ref()
@@ -48,20 +48,24 @@ whenever(shortcutKeyCombo, () => {
q.value = ""
})
// Player geolocation
function handlePlayerTarget () {
flyTo(players.markerCoords)
}
// Player geolocation (uses native JS to avoid changing leaflet library)
onMounted(() => {
const playerBtnRef = document.querySelector('[data-to-players]')
const flyToPlayers = new CustomEvent('fly-to', { bubbles: true, detail: players.markerCoords })
playerBtnRef?.addEventListener('click', () => {
playerBtnRef.dispatchEvent(flyToPlayers)
})
})
</script>
<template>
{{ lastCoords }}
<div ref="searchBar" class="search-w" :data-focused="shouldBeActive">
<div class="input-w">
<i class="search-icon ph-fill ph-magnifying-glass"></i>
<input ref="qInput" type="text" v-model="q">
<button class="player-btn" @click.native="handlePlayerTarget">
<button data-to-players class="player-btn">
<i class="pin-icon ph-fill ph-map-pin"></i>
</button>
</div>

View File

@@ -4,15 +4,6 @@ import type { MapCoords, MapMarker } from '../../types/Leaflet';
export const $world = deepMap({
markers: [] as MapMarker[],
players: {} as MapMarker,
lastCoords: {
y: 0,
x: 0,
}
})
export const flyTo = action($world, 'fly-to', (store, coords: MapCoords) => {
store.setKey('lastCoords', coords)
return store.get()
})
// Fetch initial data