From cff0585bd45462b4a4bc2e9cb796c20e892d17d3 Mon Sep 17 00:00:00 2001 From: Felipe Contreras Date: Tue, 22 Nov 2022 21:26:27 -0300 Subject: [PATCH] =?UTF-8?q?A=C3=B1=C3=A1dir=20y=20Editar=20completos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.rs | 5 + migrations/20221121211314_swap_lat_long.sql | 1 + src/routes.rs | 7 +- static/index.html | 10 +- ts-client/Makefile | 3 + ts-client/client.ts | 141 +++++++++++++++++--- 6 files changed, 146 insertions(+), 21 deletions(-) create mode 100644 build.rs create mode 100644 migrations/20221121211314_swap_lat_long.sql create mode 100644 ts-client/Makefile diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..7609593 --- /dev/null +++ b/build.rs @@ -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"); +} \ No newline at end of file diff --git a/migrations/20221121211314_swap_lat_long.sql b/migrations/20221121211314_swap_lat_long.sql new file mode 100644 index 0000000..ca86355 --- /dev/null +++ b/migrations/20221121211314_swap_lat_long.sql @@ -0,0 +1 @@ +UPDATE places SET longitude=latitude, latitude=longitude; diff --git a/src/routes.rs b/src/routes.rs index d278712..679540d 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -43,7 +43,7 @@ enum UpsertResponse { NotFound(NotFound>), } -#[post("/places", format = "json", data = "")] +#[put("/places", format = "json", data = "")] async fn upsert_place(db: Connection, place: Json) -> Result { if place.id.is_some() { update_place(db, place).await @@ -79,14 +79,15 @@ async fn insert_place(mut db: Connection, mut place: Json) -> Result< async fn update_place(mut db: Connection, place: Json) -> Result { 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?; diff --git a/static/index.html b/static/index.html index d038ddf..2bf701f 100644 --- a/static/index.html +++ b/static/index.html @@ -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 @@

+

+ +

diff --git a/ts-client/Makefile b/ts-client/Makefile new file mode 100644 index 0000000..e33a104 --- /dev/null +++ b/ts-client/Makefile @@ -0,0 +1,3 @@ +all: client.ts + tsc + sed -i '1d' build/client.js diff --git a/ts-client/client.ts b/ts-client/client.ts index ca4f8fd..a118efc 100644 --- a/ts-client/client.ts +++ b/ts-client/client.ts @@ -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): GeoJSON { +function toLeafletPlaces(backendPlaces: Array): L.GeoJSON { let result: FeatureCollection = { type: "FeatureCollection", features: new Array(), @@ -45,9 +45,15 @@ function toLeafletPlaces(backendPlaces: Array): GeoJSON { } let placesLayer: L.GeoJSON; -let places = new Map(); +let places = new Map(); -async function createPlace(): Promise { +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 { 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 { 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 { + const newPlace = getPlaceFromForm(); await fetch('places', { method: 'PUT', @@ -84,16 +121,46 @@ async function createPlace(): Promise { .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 { + 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 { @@ -111,21 +178,62 @@ async function setupMap(): Promise { } } - 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); - latInput.value = lat.toString(); 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("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 { 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 { contextmenu: true, contextmenuInheritItems: false, contextmenuItems: [{ - text: 'Marker item' + text: 'Editar', + callback: openEditForm, }] }); }