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