feat: Add and Edit places through Leaflet.contextmenu #4
6 changed files with 146 additions and 21 deletions
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>>),
|
||||
}
|
||||
|
||||
#[post("/places", format = "json", data = "<place>")]
|
||||
#[put("/places", format = "json", data = "<place>")]
|
||||
async fn upsert_place(db: Connection<Db>, place: Json<Place>) -> Result<UpsertResponse> {
|
||||
if place.id.is_some() {
|
||||
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> {
|
||||
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.address,
|
||||
place.open_hours,
|
||||
place.icon,
|
||||
place.description,
|
||||
place.longitude,
|
||||
place.latitude
|
||||
place.latitude,
|
||||
place.id,
|
||||
)
|
||||
.execute(&mut *db)
|
||||
.await?;
|
||||
|
|
|
|||
|
|
@ -52,13 +52,15 @@
|
|||
}
|
||||
#modal-form {
|
||||
margin: 15vh auto;
|
||||
background-color: rgb(245, 245, 245);
|
||||
color: #333;
|
||||
background-color: white;
|
||||
padding: 2em;
|
||||
border: 1px solid rgb(25, 25, 25);
|
||||
width: 80vw;
|
||||
border-radius: 12px;
|
||||
}
|
||||
#close {
|
||||
color: rgb(80, 80, 80);
|
||||
color: #333;
|
||||
float: right;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
|
|
@ -76,6 +78,7 @@
|
|||
display: table-row;
|
||||
}
|
||||
label {
|
||||
font-weight: bold;
|
||||
display: table-cell;
|
||||
vertical-align: top;
|
||||
padding-right: 2em;
|
||||
|
|
@ -137,6 +140,9 @@
|
|||
<textarea id="description" name="description"
|
||||
cols="30" rows="5"></textarea>
|
||||
</p>
|
||||
<p>
|
||||
<button type="button" id="button">Enviar </button>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
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,5 +1,5 @@
|
|||
import * as L from 'leaflet-contextmenu';
|
||||
import { Feature, FeatureCollection, GeoJSON } from 'geojson';
|
||||
import { Feature, FeatureCollection } from 'geojson';
|
||||
|
||||
interface PlaceModel {
|
||||
id: number | null;
|
||||
|
|
@ -28,12 +28,12 @@ function toFeature(place: PlaceModel): Feature {
|
|||
},
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [place.latitude, place.longitude]
|
||||
"coordinates": [place.longitude, place.latitude]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toLeafletPlaces(backendPlaces: Array<PlaceModel>): GeoJSON {
|
||||
function toLeafletPlaces(backendPlaces: Array<PlaceModel>): L.GeoJSON {
|
||||
let result: FeatureCollection = {
|
||||
type: "FeatureCollection",
|
||||
features: new Array<Feature>(),
|
||||
|
|
@ -45,9 +45,15 @@ function toLeafletPlaces(backendPlaces: Array<PlaceModel>): GeoJSON {
|
|||
}
|
||||
|
||||
let placesLayer: L.GeoJSON;
|
||||
let places = new Map<L.LatLngLiteral, PlaceModel>();
|
||||
let places = new Map<string, PlaceModel>();
|
||||
|
||||
async function createPlace(): Promise<void> {
|
||||
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 =
|
||||
|
|
@ -63,8 +69,8 @@ async function createPlace(): Promise<void> {
|
|||
const longitude = parseFloat(
|
||||
(document.getElementById("long") as HTMLSelectElement).value);
|
||||
|
||||
const newPlace: PlaceModel = {
|
||||
id: null,
|
||||
return {
|
||||
id: id,
|
||||
name: name,
|
||||
address: address,
|
||||
open_hours: open_hours,
|
||||
|
|
@ -73,6 +79,37 @@ async function createPlace(): Promise<void> {
|
|||
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',
|
||||
|
|
@ -84,16 +121,46 @@ async function createPlace(): Promise<void> {
|
|||
.then((response) => response.json())
|
||||
.then((place: PlaceModel) => {
|
||||
places.set(
|
||||
{ lat: place.latitude, lng: place.longitude },
|
||||
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;
|
||||
latlng: L.LatLng;
|
||||
relatedTarget: L.Marker | L.Path | undefined;
|
||||
}
|
||||
|
||||
async function setupMap(): Promise<void> {
|
||||
|
|
@ -111,21 +178,62 @@ async function setupMap(): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
function openForm(title: string, lat: number, long: number): void {
|
||||
function openForm(op: Operation, lat: number, long: number): void {
|
||||
/* Fill the form for us */
|
||||
const h1 = modal.getElementsByTagName("h1")[0];
|
||||
h1.innerText = title;
|
||||
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();
|
||||
const longInput = (document.getElementById("long") as HTMLInputElement);
|
||||
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("Añadir lugar nuevo", lat, long);
|
||||
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 */
|
||||
|
|
@ -133,13 +241,13 @@ async function setupMap(): Promise<void> {
|
|||
const leafletPlaces = toLeafletPlaces(backendPlaces);
|
||||
for (const place of backendPlaces) {
|
||||
places.set(
|
||||
{ lat: place.latitude, lng: place.longitude },
|
||||
toStr({ 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: [
|
||||
|
|
@ -181,7 +289,8 @@ async function setupMap(): Promise<void> {
|
|||
contextmenu: true,
|
||||
contextmenuInheritItems: false,
|
||||
contextmenuItems: [{
|
||||
text: 'Marker item'
|
||||
text: 'Editar',
|
||||
callback: openEditForm,
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue