⚡ Performance and code improvements
This commit is contained in:
parent
6703a197fb
commit
9d79ddf1e0
@ -1,5 +1,5 @@
|
|||||||
import { AfterViewInit, Component } from "@angular/core";
|
import { AfterViewInit, Component, OnInit } from "@angular/core";
|
||||||
import { combineLatest, debounceTime, tap } from "rxjs";
|
import { combineLatest, debounceTime, take, tap } from "rxjs";
|
||||||
import { Place, Category } from "../../types/poi";
|
import { Place, Category } from "../../types/poi";
|
||||||
import { ApiService } from "../../services/api.service";
|
import { ApiService } from "../../services/api.service";
|
||||||
import { PlaceBoxComponent } from "../../shared/place-box/place-box.component";
|
import { PlaceBoxComponent } from "../../shared/place-box/place-box.component";
|
||||||
@ -40,6 +40,7 @@ import { SelectItemGroup } from "primeng/api";
|
|||||||
import { YesNoModalComponent } from "../../modals/yes-no-modal/yes-no-modal.component";
|
import { YesNoModalComponent } from "../../modals/yes-no-modal/yes-no-modal.component";
|
||||||
import { CategoryCreateModalComponent } from "../../modals/category-create-modal/category-create-modal.component";
|
import { CategoryCreateModalComponent } from "../../modals/category-create-modal/category-create-modal.component";
|
||||||
import { AuthService } from "../../services/auth.service";
|
import { AuthService } from "../../services/auth.service";
|
||||||
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
|
|
||||||
export interface ContextMenuItem {
|
export interface ContextMenuItem {
|
||||||
text: string;
|
text: string;
|
||||||
@ -76,36 +77,37 @@ export interface MarkerOptions extends L.MarkerOptions {
|
|||||||
templateUrl: "./dashboard.component.html",
|
templateUrl: "./dashboard.component.html",
|
||||||
styleUrls: ["./dashboard.component.scss"],
|
styleUrls: ["./dashboard.component.scss"],
|
||||||
})
|
})
|
||||||
export class DashboardComponent implements AfterViewInit {
|
export class DashboardComponent implements OnInit, AfterViewInit {
|
||||||
searchInput = new FormControl("");
|
searchInput = new FormControl("", { nonNullable: true });
|
||||||
info: Info | undefined;
|
info?: Info;
|
||||||
isLowNet: boolean = false;
|
isLowNet = false;
|
||||||
isDarkMode: boolean = false;
|
isDarkMode = false;
|
||||||
isGpxInPlaceMode: boolean = false;
|
isGpxInPlaceMode = false;
|
||||||
|
|
||||||
viewSettings = false;
|
viewSettings = false;
|
||||||
viewFilters = false;
|
viewFilters = false;
|
||||||
viewMarkersList = false;
|
viewMarkersList = false;
|
||||||
viewMarkersListSearch = false;
|
viewMarkersListSearch = false;
|
||||||
settingsForm: FormGroup;
|
|
||||||
hoveredElements: HTMLElement[] = [];
|
|
||||||
|
|
||||||
map: any;
|
settingsForm: FormGroup;
|
||||||
mapDisplayedTrace: L.Polyline[] = [];
|
hoveredElement?: HTMLElement;
|
||||||
settings: Settings | undefined;
|
|
||||||
currencySigns: { c: string; s: string }[] = [];
|
map?: L.Map;
|
||||||
|
markerClusterGroup?: L.MarkerClusterGroup;
|
||||||
|
gpxLayerGroup?: L.LayerGroup;
|
||||||
|
settings?: Settings;
|
||||||
|
currencySigns = UtilsService.currencySigns();
|
||||||
doNotDisplayOptions: SelectItemGroup[] = [];
|
doNotDisplayOptions: SelectItemGroup[] = [];
|
||||||
markerClusterGroup: L.MarkerClusterGroup | undefined;
|
|
||||||
|
|
||||||
places: Place[] = [];
|
places: Place[] = [];
|
||||||
visiblePlaces: Place[] = [];
|
visiblePlaces: Place[] = [];
|
||||||
selectedPlace: Place | undefined;
|
selectedPlace?: Place;
|
||||||
categories: Category[] = [];
|
categories: Category[] = [];
|
||||||
|
|
||||||
filter_display_visited: boolean = false;
|
filter_display_visited = false;
|
||||||
filter_display_favorite_only: boolean = false;
|
filter_display_favorite_only = false;
|
||||||
filter_dog_only: boolean = false;
|
filter_dog_only = false;
|
||||||
activeCategories: Set<string> = new Set();
|
activeCategories = new Set<string>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
@ -115,7 +117,7 @@ export class DashboardComponent implements AfterViewInit {
|
|||||||
private router: Router,
|
private router: Router,
|
||||||
private fb: FormBuilder,
|
private fb: FormBuilder,
|
||||||
) {
|
) {
|
||||||
this.currencySigns = this.utilsService.currencySigns();
|
this.currencySigns = UtilsService.currencySigns();
|
||||||
|
|
||||||
this.settingsForm = this.fb.group({
|
this.settingsForm = this.fb.group({
|
||||||
map_lat: [
|
map_lat: [
|
||||||
@ -143,40 +145,21 @@ export class DashboardComponent implements AfterViewInit {
|
|||||||
tile_layer: ["", Validators.required],
|
tile_layer: ["", Validators.required],
|
||||||
});
|
});
|
||||||
|
|
||||||
this.apiService.getInfo().subscribe({
|
// HACK: Subscribe in constructor for takeUntilDestroyed
|
||||||
next: (info) => (this.info = info),
|
this.searchInput.valueChanges
|
||||||
});
|
.pipe(debounceTime(200), takeUntilDestroyed())
|
||||||
|
.subscribe({
|
||||||
this.searchInput.valueChanges.pipe(debounceTime(200)).subscribe({
|
next: () => this.setVisibleMarkers(),
|
||||||
next: () => this.setVisibleMarkers(),
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logout() {
|
ngOnInit(): void {
|
||||||
this.authService.logout();
|
this.apiService
|
||||||
}
|
.getInfo()
|
||||||
|
.pipe(take(1))
|
||||||
closePlaceBox() {
|
.subscribe({
|
||||||
this.selectedPlace = undefined;
|
next: (info) => (this.info = info),
|
||||||
}
|
});
|
||||||
|
|
||||||
toGithub() {
|
|
||||||
this.utilsService.toGithubTRIP();
|
|
||||||
}
|
|
||||||
|
|
||||||
check_update() {
|
|
||||||
this.apiService.checkVersion().subscribe({
|
|
||||||
next: (remote_version) => {
|
|
||||||
if (!remote_version)
|
|
||||||
this.utilsService.toast(
|
|
||||||
"success",
|
|
||||||
"Latest version",
|
|
||||||
"You're running the latest version of TRIP",
|
|
||||||
);
|
|
||||||
if (this.info && remote_version != this.info?.version)
|
|
||||||
this.info.update = remote_version;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
@ -186,6 +169,7 @@ export class DashboardComponent implements AfterViewInit {
|
|||||||
settings: this.apiService.getSettings(),
|
settings: this.apiService.getSettings(),
|
||||||
})
|
})
|
||||||
.pipe(
|
.pipe(
|
||||||
|
take(1),
|
||||||
tap(({ categories, places, settings }) => {
|
tap(({ categories, places, settings }) => {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.initMap();
|
this.initMap();
|
||||||
@ -199,7 +183,7 @@ export class DashboardComponent implements AfterViewInit {
|
|||||||
if (this.isDarkMode) this.utilsService.toggleDarkMode();
|
if (this.isDarkMode) this.utilsService.toggleDarkMode();
|
||||||
this.resetFilters();
|
this.resetFilters();
|
||||||
|
|
||||||
this.places.push(...places);
|
this.places = [...places];
|
||||||
this.updateMarkersAndClusters(); //Not optimized as I could do it on the forEach, but it allows me to modify only one function instead of multiple places
|
this.updateMarkersAndClusters(); //Not optimized as I could do it on the forEach, but it allows me to modify only one function instead of multiple places
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -209,7 +193,7 @@ export class DashboardComponent implements AfterViewInit {
|
|||||||
initMap(): void {
|
initMap(): void {
|
||||||
if (!this.settings) return;
|
if (!this.settings) return;
|
||||||
|
|
||||||
let contentMenuItems = [
|
const contentMenuItems = [
|
||||||
{
|
{
|
||||||
text: "Add Point of Interest",
|
text: "Add Point of Interest",
|
||||||
icon: "add-location.png",
|
icon: "add-location.png",
|
||||||
@ -220,26 +204,27 @@ export class DashboardComponent implements AfterViewInit {
|
|||||||
];
|
];
|
||||||
this.map = createMap(contentMenuItems, this.settings?.tile_layer);
|
this.map = createMap(contentMenuItems, this.settings?.tile_layer);
|
||||||
this.map.setView(L.latLng(this.settings.map_lat, this.settings.map_lng));
|
this.map.setView(L.latLng(this.settings.map_lat, this.settings.map_lng));
|
||||||
this.map.on("moveend zoomend", () => {
|
this.map.on("moveend zoomend", () => this.setVisibleMarkers());
|
||||||
this.setVisibleMarkers();
|
|
||||||
});
|
|
||||||
this.markerClusterGroup = createClusterGroup().addTo(this.map);
|
this.markerClusterGroup = createClusterGroup().addTo(this.map);
|
||||||
}
|
}
|
||||||
|
|
||||||
setVisibleMarkers() {
|
setVisibleMarkers() {
|
||||||
|
if (!this.viewMarkersList || !this.map) return;
|
||||||
const bounds = this.map.getBounds();
|
const bounds = this.map.getBounds();
|
||||||
this.visiblePlaces = this.filteredPlaces
|
|
||||||
.filter((p) => bounds.contains([p.lat, p.lng]))
|
this.visiblePlaces = this.filteredPlaces.filter((p) =>
|
||||||
.filter((p) => {
|
bounds.contains([p.lat, p.lng]),
|
||||||
const v = this.searchInput.value;
|
);
|
||||||
if (v)
|
|
||||||
return (
|
const searchValue = this.searchInput.value?.toLowerCase() ?? "";
|
||||||
p.name.toLowerCase().includes(v) ||
|
if (searchValue)
|
||||||
p.description?.toLowerCase().includes(v)
|
this.visiblePlaces.filter(
|
||||||
);
|
(p) =>
|
||||||
return true;
|
p.name.toLowerCase().includes(searchValue) ||
|
||||||
})
|
p.description?.toLowerCase().includes(searchValue),
|
||||||
.sort((a, b) => a.name.localeCompare(b.name));
|
);
|
||||||
|
|
||||||
|
this.visiblePlaces.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
resetFilters() {
|
resetFilters() {
|
||||||
@ -260,42 +245,14 @@ export class DashboardComponent implements AfterViewInit {
|
|||||||
if (this.viewMarkersList) this.setVisibleMarkers();
|
if (this.viewMarkersList) this.setVisibleMarkers();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleLowNet() {
|
|
||||||
this.apiService.putSettings({ mode_low_network: this.isLowNet }).subscribe({
|
|
||||||
next: (_) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.updateMarkersAndClusters();
|
|
||||||
}, 100);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleDarkMode() {
|
|
||||||
this.apiService.putSettings({ mode_dark: this.isDarkMode }).subscribe({
|
|
||||||
next: (_) => {
|
|
||||||
this.utilsService.toggleDarkMode();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleGpxInPlace() {
|
|
||||||
this.apiService
|
|
||||||
.putSettings({ mode_gpx_in_place: this.isGpxInPlaceMode })
|
|
||||||
.subscribe({
|
|
||||||
next: (_) => {
|
|
||||||
this.updateMarkersAndClusters();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get filteredPlaces(): Place[] {
|
get filteredPlaces(): Place[] {
|
||||||
return this.places.filter((p) => {
|
return this.places.filter(
|
||||||
if (!this.filter_display_visited && p.visited) return false;
|
(p) =>
|
||||||
if (this.filter_display_favorite_only && !p.favorite) return false;
|
(this.filter_display_visited || !p.visited) &&
|
||||||
if (this.filter_dog_only && !p.allowdog) return false;
|
(!this.filter_display_favorite_only || p.favorite) &&
|
||||||
if (!this.activeCategories.has(p.category.name)) return false;
|
(!this.filter_dog_only || p.allowdog) &&
|
||||||
return true;
|
this.activeCategories.has(p.category.name),
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateMarkersAndClusters(): void {
|
updateMarkersAndClusters(): void {
|
||||||
@ -308,7 +265,7 @@ export class DashboardComponent implements AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
placeToMarker(place: Place): L.Marker {
|
placeToMarker(place: Place): L.Marker {
|
||||||
let marker = placeToMarker(
|
const marker = placeToMarker(
|
||||||
place,
|
place,
|
||||||
this.isLowNet,
|
this.isLowNet,
|
||||||
place.visited,
|
place.visited,
|
||||||
@ -316,24 +273,23 @@ export class DashboardComponent implements AfterViewInit {
|
|||||||
);
|
);
|
||||||
marker
|
marker
|
||||||
.on("click", (e) => {
|
.on("click", (e) => {
|
||||||
this.selectedPlace = place;
|
this.selectedPlace = { ...place };
|
||||||
|
|
||||||
let toView = { ...e.latlng };
|
let toView = { ...e.latlng };
|
||||||
if ("ontouchstart" in window) toView.lat = toView.lat - 0.0175;
|
if ("ontouchstart" in window) toView.lat = toView.lat - 0.0175;
|
||||||
|
|
||||||
marker.closeTooltip();
|
marker.closeTooltip();
|
||||||
this.map.setView(toView);
|
this.map?.setView(toView);
|
||||||
})
|
})
|
||||||
.on("contextmenu", () => {
|
.on("contextmenu", () => {
|
||||||
this.map.contextmenu.hide();
|
if (this.map && (this.map as any).contextmenu)
|
||||||
|
(this.map as any).contextmenu.hide();
|
||||||
});
|
});
|
||||||
return marker;
|
return marker;
|
||||||
}
|
}
|
||||||
|
|
||||||
addPlaceModal(e?: any): void {
|
addPlaceModal(e?: any): void {
|
||||||
let opts = {};
|
const opts = e ? { data: { place: e.latlng } } : {};
|
||||||
if (e) opts = { data: { place: e.latlng } };
|
|
||||||
|
|
||||||
const modal: DynamicDialogRef = this.dialogService.open(
|
const modal: DynamicDialogRef = this.dialogService.open(
|
||||||
PlaceCreateModalComponent,
|
PlaceCreateModalComponent,
|
||||||
{
|
{
|
||||||
@ -351,19 +307,23 @@ export class DashboardComponent implements AfterViewInit {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
modal.onClose.subscribe({
|
modal.onClose.pipe(take(1)).subscribe({
|
||||||
next: (place: Place | null) => {
|
next: (place: Place | null) => {
|
||||||
if (!place) return;
|
if (!place) return;
|
||||||
|
|
||||||
this.apiService.postPlace(place).subscribe({
|
this.apiService
|
||||||
next: (place: Place) => {
|
.postPlace(place)
|
||||||
this.places.push(place);
|
.pipe(take(1))
|
||||||
this.places.sort((a, b) => a.name.localeCompare(b.name));
|
.subscribe({
|
||||||
setTimeout(() => {
|
next: (place: Place) => {
|
||||||
this.updateMarkersAndClusters();
|
this.places = [...this.places, place].sort((a, b) =>
|
||||||
}, 10);
|
a.name.localeCompare(b.name),
|
||||||
},
|
);
|
||||||
});
|
setTimeout(() => {
|
||||||
|
this.updateMarkersAndClusters();
|
||||||
|
}, 10);
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -385,41 +345,38 @@ export class DashboardComponent implements AfterViewInit {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
modal.onClose.subscribe({
|
modal.onClose.pipe(take(1)).subscribe({
|
||||||
next: (places: string | null) => {
|
next: (places: string | null) => {
|
||||||
if (!places) return;
|
if (!places) return;
|
||||||
|
|
||||||
let parsedPlaces = [];
|
let parsedPlaces = [];
|
||||||
try {
|
try {
|
||||||
parsedPlaces = JSON.parse(places);
|
parsedPlaces = JSON.parse(places);
|
||||||
if (!Array.isArray(parsedPlaces)) return;
|
if (!Array.isArray(parsedPlaces)) throw new Error();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.utilsService.toast("error", "Error", "Content looks invalid");
|
this.utilsService.toast("error", "Error", "Content looks invalid");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.apiService.postPlaces(parsedPlaces).subscribe((places) => {
|
this.apiService
|
||||||
places.forEach((p) => this.places.push(p));
|
.postPlaces(parsedPlaces)
|
||||||
this.places.sort((a, b) => a.name.localeCompare(b.name));
|
.pipe(take(1))
|
||||||
setTimeout(() => {
|
.subscribe((places) => {
|
||||||
this.updateMarkersAndClusters();
|
this.places = [...this.places, ...places].sort((a, b) =>
|
||||||
}, 10);
|
a.name.localeCompare(b.name),
|
||||||
});
|
);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.updateMarkersAndClusters();
|
||||||
|
}, 10);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
gotoPlace(p: Place) {
|
|
||||||
this.map.flyTo([p.lat, p.lng]);
|
|
||||||
}
|
|
||||||
|
|
||||||
gotoTrips() {
|
|
||||||
this.router.navigateByUrl("/trips");
|
|
||||||
}
|
|
||||||
|
|
||||||
resetHoverPlace() {
|
resetHoverPlace() {
|
||||||
this.hoveredElements.forEach((elem) => elem.classList.remove("listHover"));
|
if (!this.hoveredElement) return;
|
||||||
this.hoveredElements = [];
|
this.hoveredElement.classList.remove("listHover");
|
||||||
|
this.hoveredElement = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
hoverPlace(p: Place) {
|
hoverPlace(p: Place) {
|
||||||
@ -431,14 +388,14 @@ export class DashboardComponent implements AfterViewInit {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!marker) return;
|
if (!marker) return;
|
||||||
let markerElement = marker.getElement() as HTMLElement; // search for Marker. If 'null', is inside Cluster
|
const markerElement = marker.getElement() as HTMLElement; // search for Marker. If 'null', is inside Cluster
|
||||||
|
|
||||||
if (markerElement) {
|
if (markerElement) {
|
||||||
// marker, not clustered
|
// marker, not clustered
|
||||||
markerElement.classList.add("listHover");
|
markerElement.classList.add("listHover");
|
||||||
this.hoveredElements.push(markerElement);
|
this.hoveredElement = markerElement;
|
||||||
} else {
|
} else {
|
||||||
// marker , clustered
|
// marker is clustered
|
||||||
const parentCluster = (this.markerClusterGroup as any).getVisibleParent(
|
const parentCluster = (this.markerClusterGroup as any).getVisibleParent(
|
||||||
marker,
|
marker,
|
||||||
);
|
);
|
||||||
@ -446,7 +403,7 @@ export class DashboardComponent implements AfterViewInit {
|
|||||||
const clusterEl = parentCluster.getElement();
|
const clusterEl = parentCluster.getElement();
|
||||||
if (clusterEl) {
|
if (clusterEl) {
|
||||||
clusterEl.classList.add("listHover");
|
clusterEl.classList.add("listHover");
|
||||||
this.hoveredElements.push(clusterEl);
|
this.hoveredElement = clusterEl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -454,13 +411,19 @@ export class DashboardComponent implements AfterViewInit {
|
|||||||
|
|
||||||
favoritePlace() {
|
favoritePlace() {
|
||||||
if (!this.selectedPlace) return;
|
if (!this.selectedPlace) return;
|
||||||
|
const favoriteBool = !this.selectedPlace.favorite;
|
||||||
|
|
||||||
let favoriteBool = !this.selectedPlace.favorite;
|
|
||||||
this.apiService
|
this.apiService
|
||||||
.putPlace(this.selectedPlace.id, { favorite: favoriteBool })
|
.putPlace(this.selectedPlace.id, { favorite: favoriteBool })
|
||||||
|
.pipe(take(1))
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.selectedPlace!.favorite = favoriteBool;
|
const idx = this.places.findIndex(
|
||||||
|
(p) => p.id === this.selectedPlace!.id,
|
||||||
|
);
|
||||||
|
if (idx !== -1)
|
||||||
|
this.places[idx] = { ...this.places[idx], favorite: favoriteBool };
|
||||||
|
this.selectedPlace = { ...this.places[idx] };
|
||||||
this.updateMarkersAndClusters();
|
this.updateMarkersAndClusters();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -468,13 +431,19 @@ export class DashboardComponent implements AfterViewInit {
|
|||||||
|
|
||||||
visitPlace() {
|
visitPlace() {
|
||||||
if (!this.selectedPlace) return;
|
if (!this.selectedPlace) return;
|
||||||
|
const visitedBool = !this.selectedPlace.visited;
|
||||||
|
|
||||||
let visitedBool = !this.selectedPlace.visited;
|
|
||||||
this.apiService
|
this.apiService
|
||||||
.putPlace(this.selectedPlace.id, { visited: visitedBool })
|
.putPlace(this.selectedPlace.id, { visited: visitedBool })
|
||||||
|
.pipe(take(1))
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.selectedPlace!.visited = visitedBool;
|
const idx = this.places.findIndex(
|
||||||
|
(p) => p.id === this.selectedPlace!.id,
|
||||||
|
);
|
||||||
|
if (idx !== -1)
|
||||||
|
this.places[idx] = { ...this.places[idx], visited: visitedBool };
|
||||||
|
this.selectedPlace = { ...this.places[idx] };
|
||||||
this.updateMarkersAndClusters();
|
this.updateMarkersAndClusters();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -496,13 +465,15 @@ export class DashboardComponent implements AfterViewInit {
|
|||||||
|
|
||||||
modal.onClose.subscribe({
|
modal.onClose.subscribe({
|
||||||
next: (bool) => {
|
next: (bool) => {
|
||||||
if (bool)
|
if (!bool) return;
|
||||||
this.apiService.deletePlace(this.selectedPlace!.id).subscribe({
|
this.apiService
|
||||||
|
.deletePlace(this.selectedPlace!.id)
|
||||||
|
.pipe(take(1))
|
||||||
|
.subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
let index = this.places.findIndex(
|
this.places = this.places.filter(
|
||||||
(p) => p.id == this.selectedPlace!.id,
|
(p) => p.id !== this.selectedPlace!.id,
|
||||||
);
|
);
|
||||||
if (index > -1) this.places.splice(index, 1);
|
|
||||||
this.closePlaceBox();
|
this.closePlaceBox();
|
||||||
this.updateMarkersAndClusters();
|
this.updateMarkersAndClusters();
|
||||||
if (this.viewMarkersList) this.setVisibleMarkers();
|
if (this.viewMarkersList) this.setVisibleMarkers();
|
||||||
@ -536,74 +507,80 @@ export class DashboardComponent implements AfterViewInit {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
modal.onClose.subscribe({
|
modal.onClose.pipe(take(1)).subscribe({
|
||||||
next: (place: Place | null) => {
|
next: (place: Place | null) => {
|
||||||
if (!place) return;
|
if (!place) return;
|
||||||
|
|
||||||
this.apiService.putPlace(place.id, place).subscribe({
|
this.apiService
|
||||||
next: (place: Place) => {
|
.putPlace(place.id, place)
|
||||||
let index = this.places.findIndex((p) => p.id == place.id);
|
.pipe(take(1))
|
||||||
if (index > -1) this.places.splice(index, 1, place);
|
.subscribe({
|
||||||
this.places.sort((a, b) => a.name.localeCompare(b.name));
|
next: (place: Place) => {
|
||||||
this.selectedPlace = place;
|
const places = [...this.places];
|
||||||
setTimeout(() => {
|
const idx = places.findIndex((p) => p.id == place.id);
|
||||||
this.updateMarkersAndClusters();
|
if (idx > -1) places.splice(idx, 1, place);
|
||||||
}, 10);
|
places.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
if (this.viewMarkersList) this.setVisibleMarkers();
|
this.places = places;
|
||||||
},
|
this.selectedPlace = { ...place };
|
||||||
});
|
setTimeout(() => {
|
||||||
|
this.updateMarkersAndClusters();
|
||||||
|
}, 10);
|
||||||
|
if (this.viewMarkersList) this.setVisibleMarkers();
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
displayGPXOnMap(gpx: string) {
|
displayGPXOnMap(gpx: string) {
|
||||||
|
if (!this.map) return;
|
||||||
|
if (!this.gpxLayerGroup)
|
||||||
|
this.gpxLayerGroup = L.layerGroup().addTo(this.map);
|
||||||
|
this.gpxLayerGroup.clearLayers();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// HINT: For now, delete traces everytime we display a GPX
|
const gpxPolyline = gpxToPolyline(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", () => {
|
gpxPolyline.on("click", () => {
|
||||||
this.map.removeLayer(gpxPolyline);
|
this.gpxLayerGroup?.removeLayer(gpxPolyline);
|
||||||
});
|
});
|
||||||
|
this.gpxLayerGroup?.addLayer(gpxPolyline);
|
||||||
this.mapDisplayedTrace.push(gpxPolyline);
|
this.map.fitBounds(gpxPolyline.getBounds(), { padding: [20, 20] });
|
||||||
} catch {
|
} catch {
|
||||||
this.utilsService.toast("error", "Error", "Couldn't parse GPX data");
|
this.utilsService.toast("error", "Error", "Couldn't parse GPX data");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getPlaceGPX() {
|
getPlaceGPX() {
|
||||||
if (!this.selectedPlace) return;
|
if (!this.selectedPlace) return;
|
||||||
this.apiService.getPlaceGPX(this.selectedPlace.id).subscribe({
|
this.apiService
|
||||||
next: (p) => {
|
.getPlaceGPX(this.selectedPlace.id)
|
||||||
if (!p.gpx) {
|
.pipe(take(1))
|
||||||
this.utilsService.toast(
|
.subscribe({
|
||||||
"error",
|
next: (p) => {
|
||||||
"Error",
|
if (!p.gpx) {
|
||||||
"Couldn't retrieve GPX data",
|
this.utilsService.toast(
|
||||||
);
|
"error",
|
||||||
return;
|
"Error",
|
||||||
}
|
"Couldn't retrieve GPX data",
|
||||||
this.displayGPXOnMap(p.gpx);
|
);
|
||||||
},
|
return;
|
||||||
});
|
}
|
||||||
|
this.displayGPXOnMap(p.gpx);
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleSettings() {
|
toggleSettings() {
|
||||||
this.viewSettings = !this.viewSettings;
|
this.viewSettings = !this.viewSettings;
|
||||||
if (this.viewSettings && this.settings) {
|
if (!this.viewSettings || !this.settings) return;
|
||||||
this.settingsForm.reset();
|
|
||||||
this.settingsForm.patchValue(this.settings);
|
this.settingsForm.reset(this.settings);
|
||||||
this.doNotDisplayOptions = [
|
this.doNotDisplayOptions = [
|
||||||
{
|
{
|
||||||
label: "Categories",
|
label: "Categories",
|
||||||
items: this.categories.map((c) => ({ label: c.name, value: c.name })),
|
items: this.categories.map((c) => ({ label: c.name, value: c.name })),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleFilters() {
|
toggleFilters() {
|
||||||
@ -611,70 +588,82 @@ export class DashboardComponent implements AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggleMarkersList() {
|
toggleMarkersList() {
|
||||||
this.searchInput.setValue("");
|
|
||||||
this.viewMarkersListSearch = false;
|
|
||||||
this.viewMarkersList = !this.viewMarkersList;
|
this.viewMarkersList = !this.viewMarkersList;
|
||||||
|
this.viewMarkersListSearch = false;
|
||||||
|
this.searchInput.setValue("");
|
||||||
if (this.viewMarkersList) this.setVisibleMarkers();
|
if (this.viewMarkersList) this.setVisibleMarkers();
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleMarkersListSearch() {
|
toggleMarkersListSearch() {
|
||||||
this.searchInput.setValue("");
|
|
||||||
this.viewMarkersListSearch = !this.viewMarkersListSearch;
|
this.viewMarkersListSearch = !this.viewMarkersListSearch;
|
||||||
|
if (this.viewMarkersListSearch) this.searchInput.setValue("");
|
||||||
}
|
}
|
||||||
|
|
||||||
setMapCenterToCurrent() {
|
setMapCenterToCurrent() {
|
||||||
let latlng: L.LatLng = this.map.getCenter();
|
const latlng = this.map?.getCenter();
|
||||||
|
if (!latlng) return;
|
||||||
this.settingsForm.patchValue({ map_lat: latlng.lat, map_lng: latlng.lng });
|
this.settingsForm.patchValue({ map_lat: latlng.lat, map_lng: latlng.lng });
|
||||||
this.settingsForm.markAsDirty();
|
this.settingsForm.markAsDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
importData(e: any): void {
|
importData(event: Event): void {
|
||||||
const formdata = new FormData();
|
const input = event.target as HTMLInputElement;
|
||||||
if (e.target.files[0]) {
|
if (!input.files?.length) return;
|
||||||
formdata.append("file", e.target.files[0]);
|
|
||||||
|
|
||||||
this.apiService.settingsUserImport(formdata).subscribe({
|
const formdata = new FormData();
|
||||||
|
formdata.append("file", input.files[0]);
|
||||||
|
|
||||||
|
this.apiService
|
||||||
|
.settingsUserImport(formdata)
|
||||||
|
.pipe(take(1))
|
||||||
|
.subscribe({
|
||||||
next: (places) => {
|
next: (places) => {
|
||||||
places.forEach((p) => this.places.push(p));
|
this.places = [...this.places, ...places].sort((a, b) =>
|
||||||
this.places.sort((a, b) => a.name.localeCompare(b.name));
|
a.name.localeCompare(b.name),
|
||||||
|
);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.updateMarkersAndClusters();
|
this.updateMarkersAndClusters();
|
||||||
}, 10);
|
}, 10);
|
||||||
this.viewSettings = false;
|
this.viewSettings = false;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exportData(): void {
|
exportData(): void {
|
||||||
this.apiService.settingsUserExport().subscribe((resp: Object) => {
|
this.apiService
|
||||||
let _datablob = new Blob([JSON.stringify(resp, null, 2)], {
|
.settingsUserExport()
|
||||||
type: "text/json",
|
.pipe(take(1))
|
||||||
|
.subscribe((resp: Object) => {
|
||||||
|
const dataBlob = new Blob([JSON.stringify(resp, null, 2)], {
|
||||||
|
type: "application/json",
|
||||||
|
});
|
||||||
|
const downloadURL = URL.createObjectURL(dataBlob);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = downloadURL;
|
||||||
|
link.download = `TRIP_backup_${new Date().toISOString().split("T")[0]}.json`;
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
URL.revokeObjectURL(downloadURL);
|
||||||
});
|
});
|
||||||
var downloadURL = URL.createObjectURL(_datablob);
|
|
||||||
var link = document.createElement("a");
|
|
||||||
link.href = downloadURL;
|
|
||||||
link.download =
|
|
||||||
"TRIP_backup_" + new Date().toISOString().split("T")[0] + ".json";
|
|
||||||
link.click();
|
|
||||||
link.remove();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSettings() {
|
updateSettings() {
|
||||||
this.apiService.putSettings(this.settingsForm.value).subscribe({
|
this.apiService
|
||||||
next: (settings) => {
|
.putSettings(this.settingsForm.value)
|
||||||
const refreshMap = this.settings!.tile_layer != settings.tile_layer;
|
.pipe(take(1))
|
||||||
this.settings = settings;
|
.subscribe({
|
||||||
if (refreshMap) {
|
next: (settings) => {
|
||||||
this.map.remove();
|
const refreshMap = this.settings?.tile_layer != settings.tile_layer;
|
||||||
this.initMap();
|
this.settings = settings;
|
||||||
this.updateMarkersAndClusters();
|
if (refreshMap) {
|
||||||
}
|
this.map?.remove();
|
||||||
this.resetFilters();
|
this.initMap();
|
||||||
this.toggleSettings();
|
this.updateMarkersAndClusters();
|
||||||
},
|
}
|
||||||
});
|
this.resetFilters();
|
||||||
|
this.toggleSettings();
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
editCategory(c: Category) {
|
editCategory(c: Category) {
|
||||||
@ -695,30 +684,32 @@ export class DashboardComponent implements AfterViewInit {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
modal.onClose.subscribe({
|
modal.onClose.pipe(take(1)).subscribe({
|
||||||
next: (category: Category | null) => {
|
next: (category: Category | null) => {
|
||||||
if (!category) return;
|
if (!category) return;
|
||||||
|
|
||||||
this.apiService.putCategory(c.id, category).subscribe({
|
this.apiService
|
||||||
next: (category) => {
|
.putCategory(c.id, category)
|
||||||
const index = this.categories.findIndex(
|
.pipe(take(1))
|
||||||
(categ) => categ.id == c.id,
|
.subscribe({
|
||||||
);
|
next: (updated) => {
|
||||||
if (index > -1) {
|
this.categories = this.categories.map((cat) =>
|
||||||
this.categories.splice(index, 1, category);
|
cat.id === updated.id ? updated : cat,
|
||||||
|
);
|
||||||
|
|
||||||
this.activeCategories = new Set(
|
this.activeCategories = new Set(
|
||||||
this.categories.map((c) => c.name),
|
this.categories.map((c) => c.name),
|
||||||
);
|
);
|
||||||
this.places = this.places.map((p) => {
|
this.places = this.places.map((p) => {
|
||||||
if (p.category.id == category.id) return { ...p, category };
|
if (p.category.id == updated.id)
|
||||||
|
return { ...p, category: updated };
|
||||||
return p;
|
return p;
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.updateMarkersAndClusters();
|
this.updateMarkersAndClusters();
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -740,19 +731,22 @@ export class DashboardComponent implements AfterViewInit {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
modal.onClose.subscribe({
|
modal.onClose.pipe(take(1)).subscribe({
|
||||||
next: (category: Category | null) => {
|
next: (category: Category | null) => {
|
||||||
if (!category) return;
|
if (!category) return;
|
||||||
|
|
||||||
this.apiService.postCategory(category).subscribe({
|
this.apiService
|
||||||
next: (category: Category) => {
|
.postCategory(category)
|
||||||
this.categories.push(category);
|
.pipe(take(1))
|
||||||
this.categories.sort((categoryA: Category, categoryB: Category) =>
|
.subscribe({
|
||||||
categoryA.name.localeCompare(categoryB.name),
|
next: (category: Category) => {
|
||||||
);
|
this.categories.push(category);
|
||||||
this.activeCategories.add(category.name);
|
this.categories.sort((categoryA: Category, categoryB: Category) =>
|
||||||
},
|
categoryA.name.localeCompare(categoryB.name),
|
||||||
});
|
);
|
||||||
|
this.activeCategories.add(category.name);
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -769,23 +763,95 @@ export class DashboardComponent implements AfterViewInit {
|
|||||||
data: "Delete this category ?",
|
data: "Delete this category ?",
|
||||||
});
|
});
|
||||||
|
|
||||||
modal.onClose.subscribe({
|
modal.onClose.pipe(take(1)).subscribe({
|
||||||
next: (bool) => {
|
next: (bool) => {
|
||||||
if (bool)
|
if (bool)
|
||||||
this.apiService.deleteCategory(c_id).subscribe({
|
this.apiService
|
||||||
next: () => {
|
.deleteCategory(c_id)
|
||||||
const index = this.categories.findIndex(
|
.pipe(take(1))
|
||||||
(categ) => categ.id == c_id,
|
.subscribe({
|
||||||
);
|
next: () => {
|
||||||
if (index > -1) {
|
this.categories = this.categories.filter((c) => c.id !== c_id);
|
||||||
this.categories.splice(index, 1);
|
|
||||||
this.activeCategories = new Set(
|
this.activeCategories = new Set(
|
||||||
this.categories.map((c) => c.name),
|
this.categories.map((c) => c.name),
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gotoPlace(p: Place) {
|
||||||
|
this.map?.flyTo([p.lat, p.lng]);
|
||||||
|
}
|
||||||
|
|
||||||
|
gotoTrips() {
|
||||||
|
this.router.navigateByUrl("/trips");
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
this.authService.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
closePlaceBox() {
|
||||||
|
this.selectedPlace = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
toGithub() {
|
||||||
|
this.utilsService.toGithubTRIP();
|
||||||
|
}
|
||||||
|
|
||||||
|
check_update() {
|
||||||
|
this.apiService
|
||||||
|
.checkVersion()
|
||||||
|
.pipe(take(1))
|
||||||
|
.subscribe({
|
||||||
|
next: (remote_version) => {
|
||||||
|
if (!remote_version)
|
||||||
|
this.utilsService.toast(
|
||||||
|
"success",
|
||||||
|
"Latest version",
|
||||||
|
"You're running the latest version of TRIP",
|
||||||
|
);
|
||||||
|
if (this.info && remote_version != this.info?.version)
|
||||||
|
this.info.update = remote_version;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleLowNet() {
|
||||||
|
this.apiService
|
||||||
|
.putSettings({ mode_low_network: this.isLowNet })
|
||||||
|
.pipe(take(1))
|
||||||
|
.subscribe({
|
||||||
|
next: (_) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.updateMarkersAndClusters();
|
||||||
|
}, 100);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDarkMode() {
|
||||||
|
this.apiService
|
||||||
|
.putSettings({ mode_dark: this.isDarkMode })
|
||||||
|
.pipe(take(1))
|
||||||
|
.subscribe({
|
||||||
|
next: (_) => {
|
||||||
|
this.utilsService.toggleDarkMode();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleGpxInPlace() {
|
||||||
|
this.apiService
|
||||||
|
.putSettings({ mode_gpx_in_place: this.isGpxInPlaceMode })
|
||||||
|
.pipe(take(1))
|
||||||
|
.subscribe({
|
||||||
|
next: (_) => {
|
||||||
|
this.updateMarkersAndClusters();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import { TripStatus } from "../types/trip";
|
|||||||
import { ApiService } from "./api.service";
|
import { ApiService } from "./api.service";
|
||||||
import { map } from "rxjs";
|
import { map } from "rxjs";
|
||||||
|
|
||||||
|
type ToastSeverity = "info" | "warn" | "error" | "success";
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: "root",
|
providedIn: "root",
|
||||||
})
|
})
|
||||||
@ -11,6 +13,13 @@ export class UtilsService {
|
|||||||
private apiService = inject(ApiService);
|
private apiService = inject(ApiService);
|
||||||
currency$ = this.apiService.settings$.pipe(map((s) => s?.currency ?? "€"));
|
currency$ = this.apiService.settings$.pipe(map((s) => s?.currency ?? "€"));
|
||||||
|
|
||||||
|
readonly statuses: TripStatus[] = [
|
||||||
|
{ label: "pending", color: "#3258A8" },
|
||||||
|
{ label: "booked", color: "#007A30" },
|
||||||
|
{ label: "constraint", color: "#FFB900" },
|
||||||
|
{ label: "optional", color: "#625A84" },
|
||||||
|
];
|
||||||
|
|
||||||
constructor(private ngMessageService: MessageService) {}
|
constructor(private ngMessageService: MessageService) {}
|
||||||
|
|
||||||
toGithubTRIP() {
|
toGithubTRIP() {
|
||||||
@ -22,16 +31,12 @@ export class UtilsService {
|
|||||||
element?.classList.toggle("dark");
|
element?.classList.toggle("dark");
|
||||||
}
|
}
|
||||||
|
|
||||||
get statuses(): TripStatus[] {
|
toast(
|
||||||
return [
|
severity: ToastSeverity = "info",
|
||||||
{ label: "pending", color: "#3258A8" },
|
summary = "Info",
|
||||||
{ label: "booked", color: "#007A30" },
|
detail = "",
|
||||||
{ label: "constraint", color: "#FFB900" },
|
life = 3000,
|
||||||
{ label: "optional", color: "#625A84" },
|
): void {
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
toast(severity = "info", summary = "Info", detail = "", life = 3000): void {
|
|
||||||
this.ngMessageService.add({
|
this.ngMessageService.add({
|
||||||
severity,
|
severity,
|
||||||
summary,
|
summary,
|
||||||
@ -40,7 +45,7 @@ export class UtilsService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
parseGoogleMapsUrl(url: string): [string, string] {
|
parseGoogleMapsUrl(url: string): [place: string, latlng: string] {
|
||||||
// Look /place/<place>/ and !3d<lat> and !4d<lng>
|
// Look /place/<place>/ and !3d<lat> and !4d<lng>
|
||||||
const placeMatch = url.match(/\/place\/([^\/]+)/);
|
const placeMatch = url.match(/\/place\/([^\/]+)/);
|
||||||
const latMatch = url.match(/!3d([\d\-.]+)/);
|
const latMatch = url.match(/!3d([\d\-.]+)/);
|
||||||
@ -52,12 +57,12 @@ export class UtilsService {
|
|||||||
return ["", ""];
|
return ["", ""];
|
||||||
}
|
}
|
||||||
|
|
||||||
let place = decodeURIComponent(placeMatch[1].replace(/\+/g, " ").trim());
|
const place = decodeURIComponent(placeMatch[1].replace(/\+/g, " ").trim());
|
||||||
let latlng = `${latMatch[1]},${lngMatch[1]}`;
|
const latlng = `${latMatch[1]},${lngMatch[1]}`;
|
||||||
return [place, latlng];
|
return [place, latlng];
|
||||||
}
|
}
|
||||||
|
|
||||||
currencySigns(): { c: string; s: string }[] {
|
static currencySigns(): { c: string; s: string }[] {
|
||||||
return [
|
return [
|
||||||
{ c: "EUR", s: "€" },
|
{ c: "EUR", s: "€" },
|
||||||
{ c: "GBP", s: "£" },
|
{ c: "GBP", s: "£" },
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user