feat: Add and Edit places through Leaflet.contextmenu #4
3 changed files with 241 additions and 25 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -654,7 +654,7 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
|
|||
|
||||
[[package]]
|
||||
name = "huellas"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"rocket",
|
||||
"rocket_db_pools",
|
||||
|
|
|
|||
|
|
@ -4,12 +4,18 @@
|
|||
<meta charset=utf-8>
|
||||
<title>👣 Huellas 🐾</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.8.0/dist/leaflet.css"
|
||||
integrity="sha512-hoalWLoI8r4UszCkZ5kL8vayOGVae1oxXe/2A4AO6J9+580uKHDO3JdHb7NzwwzK5xr/Fs0W40kiNHxM9vyTtQ=="
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css"
|
||||
integrity="sha256-kLaT2GOSpHechhsozzB+flnD+zUyjE2LlfWPgU04xyI="
|
||||
crossorigin=""/>
|
||||
<script src="https://unpkg.com/leaflet@1.8.0/dist/leaflet.js"
|
||||
integrity="sha512-BB3hKbKWOc9Ez/TAwyWxNXeoV9c1v6FIeYiBieIWkpLjauysF18NzgR1MBNBXf8/KABdlkX68nAhlwcDFLGPCQ=="
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet-contextmenu/1.4.0/leaflet.contextmenu.min.css"
|
||||
integrity="sha512-AiWBM2PiPZkogKBj8Jss3MahJ+bRbSMebXUBwZMg+83vJTnZT/FXoxZrmpL+x9GbAYLWRuBZDqzhDt0Dk73qhw=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
<script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"
|
||||
integrity="sha256-WBkoXOwTeyKclOHuWtc+i2uENFpDZ9YPdf5Hf+D7ewM="
|
||||
crossorigin=""></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet-contextmenu/1.4.0/leaflet.contextmenu.min.js"
|
||||
integrity="sha512-8sfQf8cr0KjCeN32YPfjvLU2cMvyY1lhCXTMfpTZ16CvwIzeVQtwtKlxeSqFs/TpXjKhp1Dcv77LQmn1VFaOZg=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<style type="text/css" media="screen">
|
||||
body {
|
||||
|
|
@ -31,10 +37,109 @@
|
|||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
#modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 400;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: auto;
|
||||
background-color: rgb(0,0,0);
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
#modal-form {
|
||||
margin: 15vh auto;
|
||||
background-color: rgb(245, 245, 245);
|
||||
padding: 2em;
|
||||
border: 1px solid rgb(25, 25, 25);
|
||||
width: 80vw;
|
||||
}
|
||||
#close {
|
||||
color: rgb(80, 80, 80);
|
||||
float: right;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
}
|
||||
#close:hover,
|
||||
#close:focus {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
form {
|
||||
display: table;
|
||||
}
|
||||
p {
|
||||
display: table-row;
|
||||
}
|
||||
label {
|
||||
display: table-cell;
|
||||
vertical-align: top;
|
||||
padding-right: 2em;
|
||||
}
|
||||
input {
|
||||
display: table-cell;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="map"></div>
|
||||
<div id="modal">
|
||||
<div id="modal-form">
|
||||
<span id="close">×</span>
|
||||
<h1>Título</h1>
|
||||
<form>
|
||||
<p>
|
||||
<label for="id"> Id:</label>
|
||||
<input type="text" id="id" name="id" size="30" readonly>
|
||||
</p>
|
||||
<p>
|
||||
<label for="name"> Longitud:</label>
|
||||
<input type="text" id="long" name="long" size="30" readonly>
|
||||
</p>
|
||||
<p>
|
||||
<label for="name"> Latitud:</label>
|
||||
<input type="text" id="lat" name="lat" size="30" readonly>
|
||||
</p>
|
||||
<p>
|
||||
<label for="name"> Nombre:</label>
|
||||
<input type="text" id="name" name="name" size="30">
|
||||
<p>
|
||||
<label for="address"> Dirección:</label>
|
||||
<input type="text" id="address" name="address" size="30">
|
||||
</p>
|
||||
<p>
|
||||
<label for="open_hours"> Horario:</label>
|
||||
<textarea id="open_hours" name="open_hours"
|
||||
cols="30"></textarea>
|
||||
</p>
|
||||
<p>
|
||||
<label for="icon"> Ícono:</label>
|
||||
<select id="icon" name="icon">
|
||||
<option value="bar">Bar</option>
|
||||
<option value="coffee">Café</option>
|
||||
<option value="cinema">Cine</option>
|
||||
<option value="food">Comida</option>
|
||||
<option value="jazz">Jazz</option>
|
||||
<option value="library">Librería</option>
|
||||
<option value="marker" selected>Marcador</option>
|
||||
<option value="museum">Museo</option>
|
||||
<option value="dining">Restaurant</option>
|
||||
<option value="mask">Teatro</option>
|
||||
<option value="shop">Tienda</option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="description"> Descripción:</label>
|
||||
<textarea id="description" name="description"
|
||||
cols="30" rows="5"></textarea>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script src="client.js" onload="setupMap()"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import * as L from 'leaflet';
|
||||
import * as L from 'leaflet-contextmenu';
|
||||
import { Feature, FeatureCollection, GeoJSON } from 'geojson';
|
||||
|
||||
interface PlaceModel {
|
||||
id: number | null;
|
||||
name: string;
|
||||
address: string;
|
||||
open_hours: string;
|
||||
|
|
@ -9,20 +10,14 @@ interface PlaceModel {
|
|||
description: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
|
||||
}
|
||||
|
||||
async function loadPlaces(): Promise<Array<PlaceModel>> {
|
||||
return await fetch('places/').then(response => response.json());
|
||||
return await fetch('places').then(response => response.json());
|
||||
}
|
||||
|
||||
function toLeafletPlaces(backendPlaces: Array<PlaceModel>): GeoJSON {
|
||||
let result: FeatureCollection = {
|
||||
type: "FeatureCollection",
|
||||
features: new Array<Feature>(),
|
||||
}
|
||||
for (const place of backendPlaces) {
|
||||
result.features.push({
|
||||
function toFeature(place: PlaceModel): Feature {
|
||||
return {
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": place.name,
|
||||
|
|
@ -35,17 +30,125 @@ function toLeafletPlaces(backendPlaces: Array<PlaceModel>): GeoJSON {
|
|||
"type": "Point",
|
||||
"coordinates": [place.latitude, place.longitude]
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function toLeafletPlaces(backendPlaces: Array<PlaceModel>): GeoJSON {
|
||||
let result: FeatureCollection = {
|
||||
type: "FeatureCollection",
|
||||
features: new Array<Feature>(),
|
||||
}
|
||||
for (const place of backendPlaces) {
|
||||
result.features.push(toFeature(place));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
let placesLayer: L.GeoJSON;
|
||||
let places = new Map<L.LatLngLiteral, PlaceModel>();
|
||||
|
||||
async function createPlace(): Promise<void> {
|
||||
const name =
|
||||
(document.getElementById("name") as HTMLInputElement).value;
|
||||
const address =
|
||||
(document.getElementById("address") as HTMLInputElement).value;
|
||||
const open_hours =
|
||||
(document.getElementById("open_hours") as HTMLTextAreaElement).value;
|
||||
const icon =
|
||||
(document.getElementById("icon") as HTMLSelectElement).value;
|
||||
const description =
|
||||
(document.getElementById("description") as HTMLTextAreaElement).value;
|
||||
const latitude = parseFloat(
|
||||
(document.getElementById("lat") as HTMLInputElement).value);
|
||||
const longitude = parseFloat(
|
||||
(document.getElementById("long") as HTMLSelectElement).value);
|
||||
|
||||
const newPlace: PlaceModel = {
|
||||
id: null,
|
||||
name: name,
|
||||
address: address,
|
||||
open_hours: open_hours,
|
||||
icon: icon,
|
||||
description: description,
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
};
|
||||
|
||||
await fetch('places', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(newPlace),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((place: PlaceModel) => {
|
||||
places.set(
|
||||
{ lat: place.latitude, lng: place.longitude },
|
||||
place
|
||||
);
|
||||
placesLayer.addData(toFeature(place));
|
||||
})
|
||||
.catch((error) => alert(error));
|
||||
}
|
||||
|
||||
interface MapEvent {
|
||||
latlng: L.latlng;
|
||||
}
|
||||
|
||||
async function setupMap(): Promise<void> {
|
||||
/* Create/Edit form */
|
||||
const modal = document.getElementById("modal");
|
||||
const closeButton = document.getElementById("close");
|
||||
|
||||
closeButton.onclick = function() {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
|
||||
window.onclick = function(e: Event) {
|
||||
if (e.target == modal) {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
function openForm(title: string, lat: number, long: number): void {
|
||||
const h1 = modal.getElementsByTagName("h1")[0];
|
||||
h1.innerText = title;
|
||||
const latInput = (document.getElementById("lat") as HTMLInputElement);
|
||||
latInput.value = lat.toString();
|
||||
const longInput = (document.getElementById("long") as HTMLInputElement);
|
||||
longInput.value = long.toString();
|
||||
|
||||
modal.style.display = "block";
|
||||
}
|
||||
|
||||
function openCreateForm(e: MapEvent) {
|
||||
const lat = e.latlng.lat;
|
||||
const long = e.latlng.lng;
|
||||
openForm("Añadir lugar nuevo", lat, long);
|
||||
}
|
||||
|
||||
/* Get places from backend */
|
||||
const backendPlaces = await loadPlaces();
|
||||
const leafletPlaces = toLeafletPlaces(backendPlaces);
|
||||
for (const place of backendPlaces) {
|
||||
places.set(
|
||||
{ lat: place.latitude, lng: place.longitude },
|
||||
place
|
||||
);
|
||||
}
|
||||
|
||||
/* Set up the map*/
|
||||
const map = new L.Map('map');
|
||||
const map = new L.Map('map', {
|
||||
contextmenu: true,
|
||||
contextmenuWidth: 140,
|
||||
contextmenuItems: [
|
||||
{
|
||||
text: 'Añadir lugar',
|
||||
callback: openCreateForm
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* Create the tile layer with correct attribution*/
|
||||
const osmUrl = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||
|
|
@ -74,6 +177,13 @@ async function setupMap(): Promise<void> {
|
|||
popupStr += "</ul>";
|
||||
|
||||
layer.bindPopup(popupStr);
|
||||
layer.bindContextMenu({
|
||||
contextmenu: true,
|
||||
contextmenuInheritItems: false,
|
||||
contextmenuItems: [{
|
||||
text: 'Marker item'
|
||||
}]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -107,8 +217,9 @@ async function setupMap(): Promise<void> {
|
|||
return L.marker(latlng);
|
||||
}
|
||||
|
||||
map.addLayer(L.geoJSON(leafletPlaces, {
|
||||
placesLayer = L.geoJSON(leafletPlaces, {
|
||||
onEachFeature: onEachFeature,
|
||||
pointToLayer: pointToLayer
|
||||
}));
|
||||
})
|
||||
map.addLayer(placesLayer);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue