feat: Add and Edit places through Leaflet.contextmenu (#4)
This commit is contained in:
parent
02ef910880
commit
b00e4b1aab
9 changed files with 385 additions and 32 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -654,7 +654,7 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "huellas"
|
name = "huellas"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rocket",
|
"rocket",
|
||||||
"rocket_db_pools",
|
"rocket_db_pools",
|
||||||
|
|
|
||||||
5
build.rs
Normal file
5
build.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
// generated by `sqlx migrate build-script`
|
||||||
|
fn main() {
|
||||||
|
// trigger recompilation when a new migration is added
|
||||||
|
println!("cargo:rerun-if-changed=migrations");
|
||||||
|
}
|
||||||
1
migrations/20221121211314_swap_lat_long.sql
Normal file
1
migrations/20221121211314_swap_lat_long.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
UPDATE places SET longitude=latitude, latitude=longitude;
|
||||||
|
|
@ -43,7 +43,7 @@ enum UpsertResponse {
|
||||||
NotFound(NotFound<Json<Place>>),
|
NotFound(NotFound<Json<Place>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/places", format = "json", data = "<place>")]
|
#[put("/places", format = "json", data = "<place>")]
|
||||||
async fn upsert_place(db: Connection<Db>, place: Json<Place>) -> Result<UpsertResponse> {
|
async fn upsert_place(db: Connection<Db>, place: Json<Place>) -> Result<UpsertResponse> {
|
||||||
if place.id.is_some() {
|
if place.id.is_some() {
|
||||||
update_place(db, place).await
|
update_place(db, place).await
|
||||||
|
|
@ -79,14 +79,15 @@ async fn insert_place(mut db: Connection<Db>, mut place: Json<Place>) -> Result<
|
||||||
|
|
||||||
async fn update_place(mut db: Connection<Db>, place: Json<Place>) -> Result<UpsertResponse> {
|
async fn update_place(mut db: Connection<Db>, place: Json<Place>) -> Result<UpsertResponse> {
|
||||||
let result = ::sqlx::query!(
|
let result = ::sqlx::query!(
|
||||||
"UPDATE places SET (name, address, open_hours, icon, description, longitude, latitude) = (?, ?, ?, ?, ?, ?, ?)",
|
"UPDATE places SET (name, address, open_hours, icon, description, longitude, latitude) = (?, ?, ?, ?, ?, ?, ?) WHERE id = ?",
|
||||||
place.name,
|
place.name,
|
||||||
place.address,
|
place.address,
|
||||||
place.open_hours,
|
place.open_hours,
|
||||||
place.icon,
|
place.icon,
|
||||||
place.description,
|
place.description,
|
||||||
place.longitude,
|
place.longitude,
|
||||||
place.latitude
|
place.latitude,
|
||||||
|
place.id,
|
||||||
)
|
)
|
||||||
.execute(&mut *db)
|
.execute(&mut *db)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,18 @@
|
||||||
<meta charset=utf-8>
|
<meta charset=utf-8>
|
||||||
<title>👣 Huellas 🐾</title>
|
<title>👣 Huellas 🐾</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
<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"
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css"
|
||||||
integrity="sha512-hoalWLoI8r4UszCkZ5kL8vayOGVae1oxXe/2A4AO6J9+580uKHDO3JdHb7NzwwzK5xr/Fs0W40kiNHxM9vyTtQ=="
|
integrity="sha256-kLaT2GOSpHechhsozzB+flnD+zUyjE2LlfWPgU04xyI="
|
||||||
crossorigin=""/>
|
crossorigin=""/>
|
||||||
<script src="https://unpkg.com/leaflet@1.8.0/dist/leaflet.js"
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet-contextmenu/1.4.0/leaflet.contextmenu.min.css"
|
||||||
integrity="sha512-BB3hKbKWOc9Ez/TAwyWxNXeoV9c1v6FIeYiBieIWkpLjauysF18NzgR1MBNBXf8/KABdlkX68nAhlwcDFLGPCQ=="
|
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>
|
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">
|
<style type="text/css" media="screen">
|
||||||
body {
|
body {
|
||||||
|
|
@ -31,10 +37,115 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 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;
|
||||||
|
color: #333;
|
||||||
|
background-color: white;
|
||||||
|
padding: 2em;
|
||||||
|
border: 1px solid rgb(25, 25, 25);
|
||||||
|
width: 80vw;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
#close {
|
||||||
|
color: #333;
|
||||||
|
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 {
|
||||||
|
font-weight: bold;
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: top;
|
||||||
|
padding-right: 2em;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="map"></div>
|
<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>
|
||||||
|
<p>
|
||||||
|
<button type="button" id="button">Enviar </button>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<script src="client.js" onload="setupMap()"></script>
|
<script src="client.js" onload="setupMap()"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
3
ts-client/Makefile
Normal file
3
ts-client/Makefile
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
all: client.ts
|
||||||
|
tsc
|
||||||
|
sed -i '1d' build/client.js
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import * as L from 'leaflet';
|
import * as L from 'leaflet-contextmenu';
|
||||||
import { Feature, FeatureCollection, GeoJSON } from 'geojson';
|
import { Feature, FeatureCollection } from 'geojson';
|
||||||
|
|
||||||
interface PlaceModel {
|
interface PlaceModel {
|
||||||
|
id: number | null;
|
||||||
name: string;
|
name: string;
|
||||||
address: string;
|
address: string;
|
||||||
open_hours: string;
|
open_hours: string;
|
||||||
|
|
@ -9,43 +10,253 @@ interface PlaceModel {
|
||||||
description: string;
|
description: string;
|
||||||
latitude: number;
|
latitude: number;
|
||||||
longitude: number;
|
longitude: number;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadPlaces(): Promise<Array<PlaceModel>> {
|
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 {
|
function toFeature(place: PlaceModel): Feature {
|
||||||
|
return {
|
||||||
|
"type": "Feature",
|
||||||
|
"properties": {
|
||||||
|
"name": place.name,
|
||||||
|
"address": place.address,
|
||||||
|
"open_hours": place.open_hours,
|
||||||
|
"icon": place.icon,
|
||||||
|
"description": place.description
|
||||||
|
},
|
||||||
|
"geometry": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [place.longitude, place.latitude]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toLeafletPlaces(backendPlaces: Array<PlaceModel>): L.GeoJSON {
|
||||||
let result: FeatureCollection = {
|
let result: FeatureCollection = {
|
||||||
type: "FeatureCollection",
|
type: "FeatureCollection",
|
||||||
features: new Array<Feature>(),
|
features: new Array<Feature>(),
|
||||||
}
|
}
|
||||||
for (const place of backendPlaces) {
|
for (const place of backendPlaces) {
|
||||||
result.features.push({
|
result.features.push(toFeature(place));
|
||||||
"type": "Feature",
|
|
||||||
"properties": {
|
|
||||||
"name": place.name,
|
|
||||||
"address": place.address,
|
|
||||||
"open_hours": place.open_hours,
|
|
||||||
"icon": place.icon,
|
|
||||||
"description": place.description
|
|
||||||
},
|
|
||||||
"geometry": {
|
|
||||||
"type": "Point",
|
|
||||||
"coordinates": [place.latitude, place.longitude]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let placesLayer: L.GeoJSON;
|
||||||
|
let places = new Map<string, PlaceModel>();
|
||||||
|
|
||||||
|
function toStr(latlng: L.LatLngLiteral) {
|
||||||
|
return latlng.lng + "|" + latlng.lat;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPlaceFromForm(): PlaceModel {
|
||||||
|
const idStr = (document.getElementById("id") as HTMLInputElement).value;
|
||||||
|
const id = idStr == "" ? null : parseInt(idStr);
|
||||||
|
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);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
name: name,
|
||||||
|
address: address,
|
||||||
|
open_hours: open_hours,
|
||||||
|
icon: icon,
|
||||||
|
description: description,
|
||||||
|
latitude: latitude,
|
||||||
|
longitude: longitude,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearForm(): void {
|
||||||
|
/* Get the form elements*/
|
||||||
|
const idInput = (document.getElementById("id") as HTMLInputElement);
|
||||||
|
const latInput = (document.getElementById("lat") as HTMLInputElement);
|
||||||
|
const longInput = (document.getElementById("long") as HTMLInputElement);
|
||||||
|
const nameInput = (document.getElementById("name") as HTMLInputElement);
|
||||||
|
const addressInput = (document.getElementById("address") as HTMLInputElement);
|
||||||
|
const openHoursArea = (document.getElementById("open_hours") as HTMLTextAreaElement);
|
||||||
|
const descriptionArea = (document.getElementById("description") as HTMLTextAreaElement);
|
||||||
|
|
||||||
|
/* Clear them */
|
||||||
|
idInput.value = "";
|
||||||
|
latInput.value = "";
|
||||||
|
longInput.value = "";
|
||||||
|
nameInput.value = "";
|
||||||
|
addressInput.value = "";
|
||||||
|
openHoursArea.value = "";
|
||||||
|
descriptionArea.value = "";
|
||||||
|
|
||||||
|
/* Clear the callback */
|
||||||
|
document.getElementById("button").onclick = null;
|
||||||
|
|
||||||
|
/* Now you see it, now you don't*/
|
||||||
|
document.getElementById("modal").style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function createPlace(): Promise<void> {
|
||||||
|
const newPlace = getPlaceFromForm();
|
||||||
|
|
||||||
|
await fetch('places', {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(newPlace),
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((place: PlaceModel) => {
|
||||||
|
places.set(
|
||||||
|
toStr({ lat: place.latitude, lng: place.longitude }),
|
||||||
|
place
|
||||||
|
);
|
||||||
|
placesLayer.addData(toFeature(place));
|
||||||
|
clearForm();
|
||||||
|
alert("Lugar añadido exitosamente");
|
||||||
|
})
|
||||||
|
.catch((error) => alert(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function editPlace(): Promise<void> {
|
||||||
|
const newPlace = getPlaceFromForm();
|
||||||
|
|
||||||
|
await fetch('places', {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(newPlace),
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((place: PlaceModel) => {
|
||||||
|
places.set(
|
||||||
|
toStr({ lat: place.latitude, lng: place.longitude }),
|
||||||
|
place
|
||||||
|
);
|
||||||
|
clearForm();
|
||||||
|
alert("Lugar guardado exitosamente. Para ver los cambios, recargue la página.");
|
||||||
|
})
|
||||||
|
.catch((error) => alert(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
const enum Operation {
|
||||||
|
Create = "create",
|
||||||
|
Edit = "edit",
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MapEvent {
|
||||||
|
latlng: L.LatLng;
|
||||||
|
relatedTarget: L.Marker | L.Path | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
async function setupMap(): Promise<void> {
|
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(op: Operation, lat: number, long: number): void {
|
||||||
|
/* Fill the form for us */
|
||||||
|
const h1 = modal.getElementsByTagName("h1")[0];
|
||||||
|
if (op == Operation.Create) {
|
||||||
|
clearForm()
|
||||||
|
h1.innerText = "Añadir lugar nuevo";
|
||||||
|
} else {
|
||||||
|
h1.innerText = "Editar lugar";
|
||||||
|
}
|
||||||
|
const latInput = (document.getElementById("lat") as HTMLInputElement);
|
||||||
|
const longInput = (document.getElementById("long") as HTMLInputElement);
|
||||||
|
latInput.value = lat.toString();
|
||||||
|
longInput.value = long.toString();
|
||||||
|
|
||||||
|
if (op == Operation.Edit) {
|
||||||
|
const place = places.get(toStr({ lat: lat, lng: long }));
|
||||||
|
console.log(toStr({ lat: lat, lng: long }));
|
||||||
|
/*Get the form elements*/
|
||||||
|
const idInput = (document.getElementById("id") as HTMLInputElement);
|
||||||
|
const nameInput = (document.getElementById("name") as HTMLInputElement);
|
||||||
|
const addressInput = (document.getElementById("address") as HTMLInputElement);
|
||||||
|
const openHoursArea = (document.getElementById("open_hours") as HTMLTextAreaElement);
|
||||||
|
const iconSelect = (document.getElementById("icon") as HTMLSelectElement);
|
||||||
|
const descriptionArea = (document.getElementById("description") as HTMLTextAreaElement);
|
||||||
|
|
||||||
|
/* And set them */
|
||||||
|
idInput.value = place.id.toString();
|
||||||
|
nameInput.value = place.name;
|
||||||
|
addressInput.value = place.address;
|
||||||
|
openHoursArea.value = place.open_hours;
|
||||||
|
iconSelect.value = place.icon;
|
||||||
|
descriptionArea.value = place.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plug callbacks */
|
||||||
|
if (op == Operation.Create) {
|
||||||
|
document.getElementById("button").onclick = createPlace;
|
||||||
|
} else {
|
||||||
|
document.getElementById("button").onclick = editPlace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make it appear */
|
||||||
|
modal.style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
function openCreateForm(e: MapEvent) {
|
||||||
|
const lat = e.latlng.lat;
|
||||||
|
const long = e.latlng.lng;
|
||||||
|
openForm(Operation.Create, lat, long);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEditForm(e: MapEvent) {
|
||||||
|
const marker = (e.relatedTarget as L.Marker);
|
||||||
|
const lat = marker.getLatLng().lat;
|
||||||
|
const long = marker.getLatLng().lng;
|
||||||
|
openForm(Operation.Edit, lat, long);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get places from backend */
|
||||||
const backendPlaces = await loadPlaces();
|
const backendPlaces = await loadPlaces();
|
||||||
const leafletPlaces = toLeafletPlaces(backendPlaces);
|
const leafletPlaces = toLeafletPlaces(backendPlaces);
|
||||||
|
for (const place of backendPlaces) {
|
||||||
|
places.set(
|
||||||
|
toStr({ lat: place.latitude, lng: place.longitude }),
|
||||||
|
place
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/* Set up the map*/
|
/* 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*/
|
/* Create the tile layer with correct attribution*/
|
||||||
const osmUrl = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
|
const osmUrl = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||||
|
|
@ -74,6 +285,14 @@ async function setupMap(): Promise<void> {
|
||||||
popupStr += "</ul>";
|
popupStr += "</ul>";
|
||||||
|
|
||||||
layer.bindPopup(popupStr);
|
layer.bindPopup(popupStr);
|
||||||
|
layer.bindContextMenu({
|
||||||
|
contextmenu: true,
|
||||||
|
contextmenuInheritItems: false,
|
||||||
|
contextmenuItems: [{
|
||||||
|
text: 'Editar',
|
||||||
|
callback: openEditForm,
|
||||||
|
}]
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,8 +326,9 @@ async function setupMap(): Promise<void> {
|
||||||
return L.marker(latlng);
|
return L.marker(latlng);
|
||||||
}
|
}
|
||||||
|
|
||||||
map.addLayer(L.geoJSON(leafletPlaces, {
|
placesLayer = L.geoJSON(leafletPlaces, {
|
||||||
onEachFeature: onEachFeature,
|
onEachFeature: onEachFeature,
|
||||||
pointToLayer: pointToLayer
|
pointToLayer: pointToLayer
|
||||||
}));
|
})
|
||||||
|
map.addLayer(placesLayer);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
ts-client/package-lock.json
generated
13
ts-client/package-lock.json
generated
|
|
@ -6,7 +6,8 @@
|
||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"geojson": "^0.5.0",
|
"geojson": "^0.5.0",
|
||||||
"leaflet": "^1.8.0"
|
"leaflet": "^1.8.0",
|
||||||
|
"leaflet-contextmenu": "^1.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/leaflet": "^1.7.11"
|
"@types/leaflet": "^1.7.11"
|
||||||
|
|
@ -39,6 +40,11 @@
|
||||||
"version": "1.8.0",
|
"version": "1.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.8.0.tgz",
|
||||||
"integrity": "sha512-gwhMjFCQiYs3x/Sf+d49f10ERXaEFCPr+nVTryhAW8DWbMGqJqt9G4XuIaHmFW08zYvhgdzqXGr8AlW8v8dQkA=="
|
"integrity": "sha512-gwhMjFCQiYs3x/Sf+d49f10ERXaEFCPr+nVTryhAW8DWbMGqJqt9G4XuIaHmFW08zYvhgdzqXGr8AlW8v8dQkA=="
|
||||||
|
},
|
||||||
|
"node_modules/leaflet-contextmenu": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/leaflet-contextmenu/-/leaflet-contextmenu-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-BXASCmJ5bLkuJGDCpWmvGqhZi5AzeOY0IbQalfkgBcMAMfAOFSvD4y0gIQxF/XzEyLkjXaRiUpibVj4+Cf3tUA=="
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -66,6 +72,11 @@
|
||||||
"version": "1.8.0",
|
"version": "1.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.8.0.tgz",
|
||||||
"integrity": "sha512-gwhMjFCQiYs3x/Sf+d49f10ERXaEFCPr+nVTryhAW8DWbMGqJqt9G4XuIaHmFW08zYvhgdzqXGr8AlW8v8dQkA=="
|
"integrity": "sha512-gwhMjFCQiYs3x/Sf+d49f10ERXaEFCPr+nVTryhAW8DWbMGqJqt9G4XuIaHmFW08zYvhgdzqXGr8AlW8v8dQkA=="
|
||||||
|
},
|
||||||
|
"leaflet-contextmenu": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/leaflet-contextmenu/-/leaflet-contextmenu-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-BXASCmJ5bLkuJGDCpWmvGqhZi5AzeOY0IbQalfkgBcMAMfAOFSvD4y0gIQxF/XzEyLkjXaRiUpibVj4+Cf3tUA=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"geojson": "^0.5.0",
|
"geojson": "^0.5.0",
|
||||||
"leaflet": "^1.8.0"
|
"leaflet": "^1.8.0",
|
||||||
|
"leaflet-contextmenu": "^1.4.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue