diff --git a/src/src/app/components/dashboard/dashboard.component.html b/src/src/app/components/dashboard/dashboard.component.html index 9b4573c..9b95ada 100644 --- a/src/src/app/components/dashboard/dashboard.component.html +++ b/src/src/app/components/dashboard/dashboard.component.html @@ -2,7 +2,8 @@ @if (selectedPlace) { + (favoriteEmitter)="favoritePlace()" (gpxEmitter)="getPlaceGPX()" (visitEmitter)="visitPlace()" + (closeEmitter)="closePlaceBox()"> }
diff --git a/src/src/app/components/dashboard/dashboard.component.ts b/src/src/app/components/dashboard/dashboard.component.ts index 91e63c1..1e485bb 100644 --- a/src/src/app/components/dashboard/dashboard.component.ts +++ b/src/src/app/components/dashboard/dashboard.component.ts @@ -25,7 +25,12 @@ import { FloatLabelModule } from "primeng/floatlabel"; import { BatchCreateModalComponent } from "../../modals/batch-create-modal/batch-create-modal.component"; import { UtilsService } from "../../services/utils.service"; import { Info } from "../../types/info"; -import { createMap, placeToMarker, createClusterGroup } from "../../shared/map"; +import { + createMap, + placeToMarker, + createClusterGroup, + gpxToPolyline, +} from "../../shared/map"; import { Router } from "@angular/router"; import { SelectModule } from "primeng/select"; import { MultiSelectModule } from "primeng/multiselect"; @@ -82,6 +87,7 @@ export class DashboardComponent implements AfterViewInit { hoveredElements: HTMLElement[] = []; map: any; + mapDisplayedTrace: L.Polyline[] = []; settings: Settings | undefined; currencySigns: { c: string; s: string }[] = []; doNotDisplayOptions: SelectItemGroup[] = []; @@ -499,6 +505,42 @@ export class DashboardComponent implements AfterViewInit { }); } + displayGPXOnMap(gpx: string) { + try { + // HINT: For now, delete traces everytime we display a GPX + // TODO: Handle multiple polygons and handle Click events + this.mapDisplayedTrace.forEach((p) => this.map.removeLayer(p)); + this.mapDisplayedTrace = []; + + const gpxPolyline = gpxToPolyline(gpx).addTo(this.map); + gpxPolyline.on("click", () => { + this.map.removeLayer(gpxPolyline); + }); + + this.mapDisplayedTrace.push(gpxPolyline); + } catch { + this.utilsService.toast("error", "Error", "Couldn't parse GPX data"); + return; + } + } + + getPlaceGPX() { + if (!this.selectedPlace) return; + this.apiService.getPlaceGPX(this.selectedPlace.id).subscribe({ + next: (p) => { + if (!p.gpx) { + this.utilsService.toast( + "error", + "Error", + "Couldn't retrieve GPX data", + ); + return; + } + this.displayGPXOnMap(p.gpx); + }, + }); + } + toggleSettings() { this.viewSettings = !this.viewSettings; if (this.viewSettings && this.settings) { diff --git a/src/src/app/modals/place-create-modal/place-create-modal.component.html b/src/src/app/modals/place-create-modal/place-create-modal.component.html index 5ad97c5..7231410 100644 --- a/src/src/app/modals/place-create-modal/place-create-modal.component.html +++ b/src/src/app/modals/place-create-modal/place-create-modal.component.html @@ -43,14 +43,25 @@ -
- - -
+
+
+ + +
-
- - +
+ + +
+ +
+ @if (placeForm.get('gpx')?.value) { + + } @else { + + } + +
@@ -61,7 +72,7 @@
@if (placeForm.get("image_id")?.value) { -
+
Click to edit
- } @else { @if (placeForm.get("image")?.value) {
@@ -81,7 +91,7 @@
} @else { -
+
Click to edit
- } } +
diff --git a/src/src/app/modals/place-create-modal/place-create-modal.component.scss b/src/src/app/modals/place-create-modal/place-create-modal.component.scss index d0a7019..ec08078 100644 --- a/src/src/app/modals/place-create-modal/place-create-modal.component.scss +++ b/src/src/app/modals/place-create-modal/place-create-modal.component.scss @@ -14,8 +14,3 @@ .p-floatlabel:has(.p-inputwrapper-focus) input::placeholder { color: var(--p-inputtext-placeholder-color) !important; } - -.p-tooltip > .p-tooltip-text { - width: 350px !important; - background: red; -} diff --git a/src/src/app/modals/place-create-modal/place-create-modal.component.ts b/src/src/app/modals/place-create-modal/place-create-modal.component.ts index cf1b12c..ec6a418 100644 --- a/src/src/app/modals/place-create-modal/place-create-modal.component.ts +++ b/src/src/app/modals/place-create-modal/place-create-modal.component.ts @@ -91,8 +91,9 @@ export class PlaceCreateModalComponent { price: "", allowdog: false, visited: false, - image: "", + image: null, image_id: null, + gpx: null, }); if (this.config.data) { @@ -172,7 +173,7 @@ export class PlaceCreateModalComponent { this.placeForm.get("name")?.setValue(place); } - onFileSelected(event: Event) { + onImageSelected(event: Event) { const input = event.target as HTMLInputElement; if (input.files && input.files.length > 0) { const file = input.files[0]; @@ -201,4 +202,24 @@ export class PlaceCreateModalComponent { this.placeForm.get("image")?.setValue(this.previous_image); } } + + onGPXSelected(event: Event) { + const input = event.target as HTMLInputElement; + if (input.files && input.files.length > 0) { + const file = input.files[0]; + const reader = new FileReader(); + + reader.onload = (e) => { + this.placeForm.get("gpx")?.setValue(e.target?.result as string); + this.placeForm.get("gpx")?.markAsDirty(); + }; + + reader.readAsText(file); + } + } + + clearGPX() { + this.placeForm.get("gpx")?.setValue(null); + this.placeForm.get("gpx")?.markAsDirty(); + } } diff --git a/src/src/app/services/api.service.ts b/src/src/app/services/api.service.ts index 087f101..6bf145f 100644 --- a/src/src/app/services/api.service.ts +++ b/src/src/app/services/api.service.ts @@ -164,6 +164,12 @@ export class ApiService { ); } + getPlaceGPX(place_id: number): Observable { + return this.httpClient + .get(`${this.apiBaseUrl}/places/${place_id}`) + .pipe(map((p) => this._normalizePlaceImage(p))); + } + getTrips(): Observable { return this.httpClient.get(`${this.apiBaseUrl}/trips`).pipe( map((resp) => { diff --git a/src/src/app/shared/map.ts b/src/src/app/shared/map.ts index c230aaf..946de93 100644 --- a/src/src/app/shared/map.ts +++ b/src/src/app/shared/map.ts @@ -99,3 +99,18 @@ export function placeToMarker(place: Place): L.Marker { } return marker; } + +export function gpxToPolyline(gpx: string): L.Polyline { + const parser = new DOMParser(); + const gpxDoc = parser.parseFromString(gpx, "application/xml"); + + const trkpts = Array.from(gpxDoc.querySelectorAll("trkpt")); + const latlngs = trkpts.map((pt) => { + return [ + parseFloat(pt.getAttribute("lat")!), + parseFloat(pt.getAttribute("lon")!), + ] as [number, number]; + }); + + return L.polyline(latlngs, { color: "blue" }); +}