mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-21 09:03:02 +00:00
feat: use takeUntilDestroy instead of manually unsubscribing
This commit is contained in:
committed by
Rainer Killinger
parent
bebee6b4d0
commit
06f3120345
@@ -13,21 +13,23 @@
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Component, Input, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
||||
import {Component, DestroyRef, inject, Input, OnInit, ViewChild} from '@angular/core';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {AssessmentsProvider} from '../assessments.provider';
|
||||
import {DataDetailComponent, ExternalDataLoadEvent} from '../../data/detail/data-detail.component';
|
||||
import {NavController, ViewWillEnter} from '@ionic/angular';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {DataRoutingService} from '../../data/data-routing.service';
|
||||
import {SCAssessment} from '@openstapps/core';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
@Component({
|
||||
selector: 'assessments-detail',
|
||||
templateUrl: 'assessments-detail.html',
|
||||
styleUrls: ['assessments-detail.scss'],
|
||||
})
|
||||
export class AssessmentsDetailComponent implements ViewWillEnter, OnInit, OnDestroy {
|
||||
export class AssessmentsDetailComponent implements ViewWillEnter, OnInit {
|
||||
destroy$ = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
readonly route: ActivatedRoute,
|
||||
readonly assessmentsProvider: AssessmentsProvider,
|
||||
@@ -36,8 +38,6 @@ export class AssessmentsDetailComponent implements ViewWillEnter, OnInit, OnDest
|
||||
readonly activatedRoute: ActivatedRoute,
|
||||
) {}
|
||||
|
||||
subscriptions: Subscription[] = [];
|
||||
|
||||
@Input() dataPathAutoRouting = true;
|
||||
|
||||
@ViewChild(DataDetailComponent)
|
||||
@@ -47,19 +47,16 @@ export class AssessmentsDetailComponent implements ViewWillEnter, OnInit, OnDest
|
||||
|
||||
ngOnInit() {
|
||||
if (!this.dataPathAutoRouting) return;
|
||||
this.subscriptions.push(
|
||||
this.dataRoutingService.pathSelectListener().subscribe(item => {
|
||||
this.dataRoutingService
|
||||
.pathSelectListener()
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(item => {
|
||||
void this.navController.navigateBack(['assessments', 'detail', item.uid], {
|
||||
queryParams: {
|
||||
token: this.activatedRoute.snapshot.queryParamMap.get('token'),
|
||||
},
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
for (const sub of this.subscriptions) sub.unsubscribe();
|
||||
});
|
||||
}
|
||||
|
||||
getItem(event: ExternalDataLoadEvent) {
|
||||
|
||||
@@ -13,18 +13,18 @@
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Component, DestroyRef, inject, Input, OnInit} from '@angular/core';
|
||||
import {SCThings} from '@openstapps/core';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {DataRoutingService} from '../../data/data-routing.service';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
@Component({
|
||||
selector: 'assessments-simple-data-list',
|
||||
templateUrl: 'assessments-simple-data-list.html',
|
||||
styleUrls: ['assessments-simple-data-list.scss'],
|
||||
})
|
||||
export class AssessmentsSimpleDataListComponent implements OnInit, OnDestroy {
|
||||
export class AssessmentsSimpleDataListComponent implements OnInit {
|
||||
/**
|
||||
* All SCThings to display
|
||||
*/
|
||||
@@ -44,7 +44,7 @@ export class AssessmentsSimpleDataListComponent implements OnInit, OnDestroy {
|
||||
this._items = new Promise(resolve => resolve(items));
|
||||
}
|
||||
|
||||
subscriptions: Subscription[] = [];
|
||||
destroy$ = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
readonly dataRoutingService: DataRoutingService,
|
||||
@@ -53,18 +53,15 @@ export class AssessmentsSimpleDataListComponent implements OnInit, OnDestroy {
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.subscriptions.push(
|
||||
this.dataRoutingService.itemSelectListener().subscribe(thing => {
|
||||
this.dataRoutingService
|
||||
.itemSelectListener()
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(thing => {
|
||||
void this.router.navigate(['assessments', 'detail', thing.uid], {
|
||||
queryParams: {
|
||||
token: this.activatedRoute.snapshot.queryParamMap.get('token'),
|
||||
},
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
for (const subscription of this.subscriptions) subscription.unsubscribe();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,17 +12,17 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {AfterViewInit, Component, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
||||
import {AfterViewInit, Component, DestroyRef, inject, OnInit, ViewChild} from '@angular/core';
|
||||
import {AssessmentsProvider} from '../assessments.provider';
|
||||
import {SCAssessment, SCCourseOfStudy} from '@openstapps/core';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {NGXLogger} from 'ngx-logger';
|
||||
import {materialSharedAxisX} from '../../../animation/material-motion';
|
||||
import {SharedAxisChoreographer} from '../../../animation/animation-choreographer';
|
||||
import {DataProvider, DataScope} from '../../data/data.provider';
|
||||
import {DataRoutingService} from '../../data/data-routing.service';
|
||||
import {groupBy, mapValues} from '@openstapps/collection-utils';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
@Component({
|
||||
selector: 'app-assessments-page',
|
||||
@@ -30,7 +30,7 @@ import {groupBy, mapValues} from '@openstapps/collection-utils';
|
||||
styleUrls: ['assessments-page.scss'],
|
||||
animations: [materialSharedAxisX],
|
||||
})
|
||||
export class AssessmentsPageComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
export class AssessmentsPageComponent implements OnInit, AfterViewInit {
|
||||
assessments: Promise<
|
||||
Record<
|
||||
string,
|
||||
@@ -43,12 +43,12 @@ export class AssessmentsPageComponent implements OnInit, AfterViewInit, OnDestro
|
||||
|
||||
assessmentKeys: string[] = [];
|
||||
|
||||
routingSubscription: Subscription;
|
||||
|
||||
@ViewChild('segment') segmentView!: HTMLIonSegmentElement;
|
||||
|
||||
sharedAxisChoreographer: SharedAxisChoreographer<string> = new SharedAxisChoreographer<string>('', []);
|
||||
|
||||
destroy$ = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
readonly logger: NGXLogger,
|
||||
readonly assessmentsProvider: AssessmentsProvider,
|
||||
@@ -62,18 +62,17 @@ export class AssessmentsPageComponent implements OnInit, AfterViewInit, OnDestro
|
||||
this.segmentView.value = this.sharedAxisChoreographer.currentValue;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.routingSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.routingSubscription = this.dataRoutingService.itemSelectListener().subscribe(thing => {
|
||||
void this.router.navigate(['assessments', 'detail', thing.uid], {
|
||||
queryParams: {
|
||||
token: this.activatedRoute.snapshot.queryParamMap.get('token'),
|
||||
},
|
||||
this.dataRoutingService
|
||||
.itemSelectListener()
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(thing => {
|
||||
void this.router.navigate(['assessments', 'detail', thing.uid], {
|
||||
queryParams: {
|
||||
token: this.activatedRoute.snapshot.queryParamMap.get('token'),
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
this.activatedRoute.queryParams.subscribe(parameters => {
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 StApps
|
||||
* Copyright (C) 2023 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
@@ -12,41 +12,29 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {OnInit, OnDestroy, Component} from '@angular/core';
|
||||
import {Component} from '@angular/core';
|
||||
import {NavController} from '@ionic/angular';
|
||||
import {Router} from '@angular/router';
|
||||
import {AuthActions, IAuthAction} from 'ionic-appauth';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {SCAuthorizationProviderType} from '@openstapps/core';
|
||||
import {AuthHelperService} from '../../auth-helper.service';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
import {Observable} from 'rxjs';
|
||||
import {IPAIAAuthAction} from '../../paia/paia-auth-action';
|
||||
|
||||
@Component({
|
||||
templateUrl: 'auth-callback-page.component.html',
|
||||
styleUrls: ['auth-callback-page.component.scss'],
|
||||
})
|
||||
export class AuthCallbackPageComponent implements OnInit, OnDestroy {
|
||||
export class AuthCallbackPageComponent {
|
||||
PROVIDER_TYPE: SCAuthorizationProviderType = 'default';
|
||||
|
||||
private authEvents: Subscription;
|
||||
constructor(private navCtrl: NavController, private router: Router, private authHelper: AuthHelperService) {
|
||||
const provider = this.authHelper.getProvider(this.PROVIDER_TYPE);
|
||||
const events: Observable<IPAIAAuthAction | IAuthAction> = provider.events$;
|
||||
|
||||
constructor(
|
||||
private navCtrl: NavController,
|
||||
private router: Router,
|
||||
private authHelper: AuthHelperService,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.authEvents = this.authHelper
|
||||
.getProvider(this.PROVIDER_TYPE)
|
||||
.events$.subscribe((action: IAuthAction) => this.postCallback(action));
|
||||
this.authHelper
|
||||
.getProvider(this.PROVIDER_TYPE)
|
||||
.authorizationCallback(window.location.origin + this.router.url);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.authEvents.unsubscribe();
|
||||
events.pipe(takeUntilDestroyed()).subscribe((action: IAuthAction) => this.postCallback(action));
|
||||
provider.authorizationCallback(window.location.origin + this.router.url);
|
||||
}
|
||||
|
||||
async postCallback(action: IAuthAction) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 StApps
|
||||
* Copyright (C) 2023 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
@@ -12,8 +12,7 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Injectable, OnDestroy} from '@angular/core';
|
||||
import {DestroyRef, inject, Injectable} from '@angular/core';
|
||||
import {
|
||||
DateSeriesRelevantData,
|
||||
dateSeriesRelevantKeys,
|
||||
@@ -28,7 +27,6 @@ import {BackgroundFetch} from '@transistorsoft/capacitor-background-fetch';
|
||||
import {StorageProvider} from '../../storage/storage.provider';
|
||||
import {CalendarService} from '../../calendar/calendar.service';
|
||||
import {toICal} from '../../calendar/ical/ical';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {ChangesOf} from './changes';
|
||||
import {hashStringToInt} from './hash';
|
||||
import {
|
||||
@@ -38,9 +36,12 @@ import {
|
||||
} from '../../settings/page/calendar-sync-settings-keys';
|
||||
import {filter} from 'rxjs/operators';
|
||||
import {Capacitor} from '@capacitor/core';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
@Injectable()
|
||||
export class ScheduleSyncService implements OnDestroy {
|
||||
export class ScheduleSyncService {
|
||||
destroy$ = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
private scheduleProvider: ScheduleProvider,
|
||||
private storageProvider: StorageProvider,
|
||||
@@ -51,20 +52,19 @@ export class ScheduleSyncService implements OnDestroy {
|
||||
) {}
|
||||
|
||||
init() {
|
||||
this.scheduleProvider.uuids$.pipe(filter(uuids => uuids?.length > 0)).subscribe(uuids => {
|
||||
this.uuids = uuids;
|
||||
void this.syncNativeCalendar();
|
||||
});
|
||||
this.scheduleProvider.uuids$
|
||||
.pipe(
|
||||
takeUntilDestroyed(this.destroy$),
|
||||
filter(uuids => uuids?.length > 0),
|
||||
)
|
||||
.subscribe(uuids => {
|
||||
this.uuids = uuids;
|
||||
void this.syncNativeCalendar();
|
||||
});
|
||||
}
|
||||
|
||||
uuids: SCUuid[];
|
||||
|
||||
uuidSubscription: Subscription;
|
||||
|
||||
ngOnDestroy() {
|
||||
this.uuidSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
private async isSyncEnabled(): Promise<boolean> {
|
||||
return getCalendarSetting(this.storageProvider, CALENDAR_SYNC_ENABLED_KEY);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/* eslint-disable unicorn/no-null */
|
||||
import {Injectable, OnDestroy} from '@angular/core';
|
||||
import {DestroyRef, inject, Injectable, OnDestroy} from '@angular/core';
|
||||
import {
|
||||
Bounds,
|
||||
SCDateSeries,
|
||||
@@ -23,11 +23,12 @@ import {
|
||||
SCThingType,
|
||||
SCUuid,
|
||||
} from '@openstapps/core';
|
||||
import {BehaviorSubject, Observable, Subscription} from 'rxjs';
|
||||
import {BehaviorSubject, Observable} from 'rxjs';
|
||||
import {DataProvider} from '../data/data.provider';
|
||||
import {map} from 'rxjs/operators';
|
||||
import {DateFormatPipe, DurationPipe} from 'ngx-moment';
|
||||
import {pick} from '@openstapps/collection-utils';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -73,7 +74,7 @@ export class ScheduleProvider implements OnDestroy {
|
||||
|
||||
private _partialEvents$?: BehaviorSubject<DateSeriesRelevantData[]>;
|
||||
|
||||
private _partialEventsSubscription?: Subscription;
|
||||
destroy$ = inject(DestroyRef);
|
||||
|
||||
constructor(private readonly dataProvider: DataProvider) {
|
||||
window.addEventListener('storage', this.storageListener);
|
||||
@@ -125,7 +126,7 @@ export class ScheduleProvider implements OnDestroy {
|
||||
const data = ScheduleProvider.get<DateSeriesRelevantData>(ScheduleProvider.partialEventsStorageKey);
|
||||
|
||||
this._partialEvents$ = new BehaviorSubject(data ?? []);
|
||||
this._partialEventsSubscription = this._partialEvents$.subscribe(result => {
|
||||
this._partialEvents$.pipe(takeUntilDestroyed(this.destroy$)).subscribe(result => {
|
||||
ScheduleProvider.set(ScheduleProvider.partialEventsStorageKey, result);
|
||||
});
|
||||
}
|
||||
@@ -257,7 +258,6 @@ export class ScheduleProvider implements OnDestroy {
|
||||
* TODO
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this._partialEventsSubscription?.unsubscribe();
|
||||
window.removeEventListener('storage', this.storageListener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,22 +12,22 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component, OnInit, OnDestroy} from '@angular/core';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {Router, ActivatedRoute} from '@angular/router';
|
||||
import {SCCatalog, SCSemester} from '@openstapps/core';
|
||||
import moment from 'moment';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {CatalogProvider} from './catalog.provider';
|
||||
import {NGXLogger} from 'ngx-logger';
|
||||
import {Location} from '@angular/common';
|
||||
import {DataRoutingService} from '../data/data-routing.service';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
@Component({
|
||||
selector: 'app-catalog',
|
||||
templateUrl: './catalog.component.html',
|
||||
styleUrls: ['./catalog.component.scss'],
|
||||
})
|
||||
export class CatalogComponent implements OnInit, OnDestroy {
|
||||
export class CatalogComponent implements OnInit {
|
||||
/**
|
||||
* SCSemester to show
|
||||
*/
|
||||
@@ -48,11 +48,6 @@ export class CatalogComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
catalogs: SCCatalog[] | undefined;
|
||||
|
||||
/**
|
||||
* Array of all subscriptions to Observables
|
||||
*/
|
||||
subscriptions: Subscription[] = [];
|
||||
|
||||
/**
|
||||
* Supercatalog (SCCatalog) to refer to
|
||||
*/
|
||||
@@ -66,11 +61,12 @@ export class CatalogComponent implements OnInit, OnDestroy {
|
||||
protected router: Router,
|
||||
public location: Location,
|
||||
) {
|
||||
this.subscriptions.push(
|
||||
this.dataRoutingService.itemSelectListener().subscribe(item => {
|
||||
this.dataRoutingService
|
||||
.itemSelectListener()
|
||||
.pipe(takeUntilDestroyed())
|
||||
.subscribe(item => {
|
||||
void this.router.navigate(['data-detail', item.uid]);
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -78,15 +74,6 @@ export class CatalogComponent implements OnInit, OnDestroy {
|
||||
void this.fetchCatalog();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove subscriptions when the component is removed
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
for (const sub of this.subscriptions) {
|
||||
sub.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
async fetchCatalog() {
|
||||
try {
|
||||
if (this.availableSemesters.length === 0) {
|
||||
|
||||
@@ -12,21 +12,18 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
||||
import {Component, DestroyRef, ElementRef, inject, NgZone, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
||||
import {Router} from '@angular/router';
|
||||
import {Location} from '@angular/common';
|
||||
import {Subscription} from 'rxjs';
|
||||
import moment from 'moment';
|
||||
import {SCDateSeries, SCUuid} from '@openstapps/core';
|
||||
import {SplashScreen} from '@capacitor/splash-screen';
|
||||
|
||||
import {DataRoutingService} from '../data/data-routing.service';
|
||||
import {ScheduleProvider} from '../calendar/schedule.provider';
|
||||
import {AnimationController, IonContent} from '@ionic/angular';
|
||||
import {DashboardCollapse} from './dashboard-collapse';
|
||||
import {BreakpointObserver} from '@angular/cdk/layout';
|
||||
|
||||
// const scrollTimeline = new ScrollTimeline();
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dashboard',
|
||||
@@ -34,11 +31,6 @@ import {BreakpointObserver} from '@angular/cdk/layout';
|
||||
styleUrls: ['./dashboard.component.scss', '/dashboard.collapse.component.scss'],
|
||||
})
|
||||
export class DashboardComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* Array of all subscriptions to Observables
|
||||
*/
|
||||
subscriptions: Subscription[] = [];
|
||||
|
||||
@ViewChild('toolbar', {read: ElementRef}) toolbarRef: ElementRef;
|
||||
|
||||
@ViewChild('schedule', {read: ElementRef}) scheduleRef: ElementRef;
|
||||
@@ -47,11 +39,6 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
||||
|
||||
collapseAnimation: DashboardCollapse;
|
||||
|
||||
/**
|
||||
* UUID subscription
|
||||
*/
|
||||
private _eventUuidSubscription: Subscription;
|
||||
|
||||
/**
|
||||
* The events to display
|
||||
*/
|
||||
@@ -74,6 +61,8 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
||||
},
|
||||
};
|
||||
|
||||
destroy$ = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
private readonly dataRoutingService: DataRoutingService,
|
||||
private scheduleProvider: ScheduleProvider,
|
||||
@@ -83,15 +72,16 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
||||
private breakpointObserver: BreakpointObserver,
|
||||
private zone: NgZone,
|
||||
) {
|
||||
this.subscriptions.push(
|
||||
this.dataRoutingService.itemSelectListener().subscribe(item => {
|
||||
this.dataRoutingService
|
||||
.itemSelectListener()
|
||||
.pipe(takeUntilDestroyed())
|
||||
.subscribe(item => {
|
||||
void this.router.navigate(['data-detail', item.uid]);
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this._eventUuidSubscription = this.scheduleProvider.uuids$.subscribe(async result => {
|
||||
this.scheduleProvider.uuids$.pipe(takeUntilDestroyed(this.destroy$)).subscribe(async result => {
|
||||
this.eventUuids = result;
|
||||
await this.loadNextEvent();
|
||||
});
|
||||
@@ -105,12 +95,13 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
||||
this.scheduleRef.nativeElement,
|
||||
);
|
||||
|
||||
this.subscriptions.push(
|
||||
this.breakpointObserver.observe(['(min-width: 768px)']).subscribe(async state => {
|
||||
this.breakpointObserver
|
||||
.observe(['(min-width: 768px)'])
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(async state => {
|
||||
await this.collapseAnimation.ready;
|
||||
this.collapseAnimation.active = !state.matches;
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async loadNextEvent() {
|
||||
@@ -133,14 +124,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
||||
.find(({time}) => !!time)?.series;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove subscriptions when the component is removed
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
for (const sub of this.subscriptions) {
|
||||
sub.unsubscribe();
|
||||
}
|
||||
this._eventUuidSubscription.unsubscribe();
|
||||
this.collapseAnimation.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 StApps
|
||||
* Copyright (C) 2023 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
@@ -12,9 +12,7 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* tslint:disable:prefer-function-over-method */
|
||||
import {Component, Input, OnDestroy, ViewChild} from '@angular/core';
|
||||
import {Component, DestroyRef, inject, Input, ViewChild} from '@angular/core';
|
||||
import {IonRouterOutlet, ModalController} from '@ionic/angular';
|
||||
import {SCDateSeries, SCThing, SCThingType, SCUuid} from '@openstapps/core';
|
||||
import {Subscription} from 'rxjs';
|
||||
@@ -27,6 +25,7 @@ import {
|
||||
import {AddEventStates, AddEventStatesMap} from './add-event-action-chip.config';
|
||||
import {EditEventSelectionComponent} from '../edit-event-selection.component';
|
||||
import {AddEventReviewModalComponent} from '../../../calendar/add-event-review-modal.component';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
/**
|
||||
* Shows a horizontal list of action chips
|
||||
@@ -37,7 +36,7 @@ import {AddEventReviewModalComponent} from '../../../calendar/add-event-review-m
|
||||
styleUrls: ['add-event-action-chip.scss'],
|
||||
animations: [chipSkeletonTransition, chipTransition],
|
||||
})
|
||||
export class AddEventActionChipComponent implements OnDestroy {
|
||||
export class AddEventActionChipComponent {
|
||||
/**
|
||||
* Associated date series
|
||||
*/
|
||||
@@ -91,6 +90,8 @@ export class AddEventActionChipComponent implements OnDestroy {
|
||||
@ViewChild('selection', {static: false})
|
||||
selection: EditEventSelectionComponent;
|
||||
|
||||
destroy$ = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
readonly dataProvider: CoordinatedSearchProvider,
|
||||
readonly modalController: ModalController,
|
||||
@@ -111,13 +112,6 @@ export class AddEventActionChipComponent implements OnDestroy {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
this.uuidSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
async export() {
|
||||
const modal = await this.modalController.create({
|
||||
component: AddEventReviewModalComponent,
|
||||
@@ -180,28 +174,30 @@ export class AddEventActionChipComponent implements OnDestroy {
|
||||
})
|
||||
.then(it => it.data as SCDateSeries[]);
|
||||
|
||||
this.uuidSubscription = this.scheduleProvider.uuids$.subscribe(async result => {
|
||||
this.uuids = result;
|
||||
const associatedDateSeries = await this.associatedDateSeries;
|
||||
if (associatedDateSeries.length === 0) {
|
||||
this.applyState(AddEventStates.UNAVAILABLE);
|
||||
this.uuidSubscription = this.scheduleProvider.uuids$
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(async result => {
|
||||
this.uuids = result;
|
||||
const associatedDateSeries = await this.associatedDateSeries;
|
||||
if (associatedDateSeries.length === 0) {
|
||||
this.applyState(AddEventStates.UNAVAILABLE);
|
||||
|
||||
return;
|
||||
}
|
||||
switch (associatedDateSeries.map(it => it.uid).filter(it => !this.uuids.includes(it)).length) {
|
||||
case 0: {
|
||||
this.applyState(AddEventStates.ADDED_ALL);
|
||||
break;
|
||||
return;
|
||||
}
|
||||
case associatedDateSeries.length: {
|
||||
this.applyState(AddEventStates.REMOVED_ALL);
|
||||
break;
|
||||
switch (associatedDateSeries.map(it => it.uid).filter(it => !this.uuids.includes(it)).length) {
|
||||
case 0: {
|
||||
this.applyState(AddEventStates.ADDED_ALL);
|
||||
break;
|
||||
}
|
||||
case associatedDateSeries.length: {
|
||||
this.applyState(AddEventStates.REMOVED_ALL);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
this.applyState(AddEventStates.ADDED_SOME);
|
||||
break;
|
||||
}
|
||||
}
|
||||
default: {
|
||||
this.applyState(AddEventStates.ADDED_SOME);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,27 +12,26 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Component, DestroyRef, inject, Input, OnInit} from '@angular/core';
|
||||
import {RoutingStackService} from '../../../util/routing-stack.service';
|
||||
import {SCCatalog, SCThings, SCThingType, SCThingWithoutReferences} from '@openstapps/core';
|
||||
import {DataProvider, DataScope} from '../data.provider';
|
||||
import {fromEvent, Observable, Subscription} from 'rxjs';
|
||||
import {fromEvent, Observable} from 'rxjs';
|
||||
import {map, startWith} from 'rxjs/operators';
|
||||
import {DataRoutingService} from '../data-routing.service';
|
||||
import {NavController} from '@ionic/angular';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
@Component({
|
||||
selector: 'stapps-data-path',
|
||||
templateUrl: './data-path.html',
|
||||
styleUrls: ['./data-path.scss'],
|
||||
})
|
||||
export class DataPathComponent implements OnInit, OnDestroy {
|
||||
export class DataPathComponent implements OnInit {
|
||||
path: Promise<SCThingWithoutReferences[]>;
|
||||
|
||||
$width: Observable<number>;
|
||||
|
||||
subscriptions: Subscription[] = [];
|
||||
|
||||
@Input() autoRouting = true;
|
||||
|
||||
@Input() maxItems = 2;
|
||||
@@ -74,6 +73,8 @@ export class DataPathComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
destroy$ = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
readonly dataRoutingService: DataRoutingService,
|
||||
readonly navController: NavController,
|
||||
@@ -88,14 +89,11 @@ export class DataPathComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
|
||||
if (!this.autoRouting) return;
|
||||
this.subscriptions.push(
|
||||
this.dataRoutingService.pathSelectListener().subscribe(item => {
|
||||
this.dataRoutingService
|
||||
.pathSelectListener()
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(item => {
|
||||
void this.navController.navigateBack(['data-detail', item.uid]);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
for (const sub of this.subscriptions) sub.unsubscribe();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,12 @@
|
||||
import {
|
||||
Component,
|
||||
ContentChild,
|
||||
DestroyRef,
|
||||
EventEmitter,
|
||||
HostListener,
|
||||
inject,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
SimpleChanges,
|
||||
@@ -27,8 +28,9 @@ import {
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import {SCThings} from '@openstapps/core';
|
||||
import {BehaviorSubject, Observable, Subscription} from 'rxjs';
|
||||
import {BehaviorSubject, Observable} from 'rxjs';
|
||||
import {IonInfiniteScroll} from '@ionic/angular';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
export interface DataListContext<T> {
|
||||
$implicit: T;
|
||||
@@ -42,7 +44,7 @@ export interface DataListContext<T> {
|
||||
templateUrl: 'data-list.html',
|
||||
styleUrls: ['data-list.scss'],
|
||||
})
|
||||
export class DataListComponent implements OnChanges, OnInit, OnDestroy {
|
||||
export class DataListComponent implements OnChanges, OnInit {
|
||||
/**
|
||||
* All SCThings to display
|
||||
*/
|
||||
@@ -76,11 +78,6 @@ export class DataListComponent implements OnChanges, OnInit, OnDestroy {
|
||||
*/
|
||||
skeletonItems: number;
|
||||
|
||||
/**
|
||||
* Array of all subscriptions to Observables
|
||||
*/
|
||||
subscriptions: Subscription[] = [];
|
||||
|
||||
@ViewChild(IonInfiniteScroll) infiniteScroll: IonInfiniteScroll;
|
||||
|
||||
/**
|
||||
@@ -88,6 +85,8 @@ export class DataListComponent implements OnChanges, OnInit, OnDestroy {
|
||||
*/
|
||||
@Input() loading = true;
|
||||
|
||||
destroy$ = inject(DestroyRef);
|
||||
|
||||
/**
|
||||
* Calculate how many items would fill the screen
|
||||
*/
|
||||
@@ -112,20 +111,12 @@ export class DataListComponent implements OnChanges, OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
for (const subscription of this.subscriptions) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.calcSkeletonItems();
|
||||
if (this.resetToTop !== undefined) {
|
||||
this.subscriptions.push(
|
||||
this.resetToTop.subscribe(() => {
|
||||
// this.viewPort.scrollToIndex(0);
|
||||
}),
|
||||
);
|
||||
this.resetToTop.pipe(takeUntilDestroyed(this.destroy$)).subscribe(() => {
|
||||
// this.viewPort.scrollToIndex(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,11 +12,13 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {MapPosition} from '../../map/position.service';
|
||||
import {SearchPageComponent} from './search-page.component';
|
||||
import {Geolocation} from '@capacitor/geolocation';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {BehaviorSubject} from 'rxjs';
|
||||
import {pauseWhen} from '../../../util/pause-when';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
/**
|
||||
* Presents a list of places for eating/drinking
|
||||
@@ -25,19 +27,29 @@ import {Subscription} from 'rxjs';
|
||||
templateUrl: 'search-page.html',
|
||||
styleUrls: ['../../data/list/search-page.scss'],
|
||||
})
|
||||
export class FoodDataListComponent extends SearchPageComponent implements OnInit, OnDestroy {
|
||||
export class FoodDataListComponent extends SearchPageComponent implements OnInit {
|
||||
title = 'canteens.title';
|
||||
|
||||
showNavigation = false;
|
||||
|
||||
locationWatch?: Subscription;
|
||||
isNotInView$ = new BehaviorSubject(true);
|
||||
|
||||
/**
|
||||
* Sets the forced filter to present only places for eating/drinking
|
||||
*/
|
||||
ngOnInit() {
|
||||
this.locationWatch?.unsubscribe();
|
||||
this.locationWatch = this.createLocationWatch();
|
||||
this.positionService
|
||||
.watchCurrentLocation({enableHighAccuracy: false, maximumAge: 1000})
|
||||
.pipe(pauseWhen(this.isNotInView$), takeUntilDestroyed(this.destroy$))
|
||||
.subscribe({
|
||||
next: (position: MapPosition) => {
|
||||
this.positionService.position = position;
|
||||
},
|
||||
error: async _error => {
|
||||
this.positionService.position = undefined;
|
||||
await Geolocation.checkPermissions();
|
||||
},
|
||||
});
|
||||
this.showDefaultData = true;
|
||||
|
||||
this.sortQuery = [
|
||||
@@ -101,32 +113,12 @@ export class FoodDataListComponent extends SearchPageComponent implements OnInit
|
||||
super.ngOnInit();
|
||||
}
|
||||
|
||||
private createLocationWatch(): Subscription {
|
||||
return this.positionService
|
||||
.watchCurrentLocation(this.constructor.name, {enableHighAccuracy: false, maximumAge: 1000})
|
||||
.subscribe({
|
||||
next: (position: MapPosition) => {
|
||||
this.positionService.position = position;
|
||||
},
|
||||
error: async _error => {
|
||||
this.positionService.position = undefined;
|
||||
await Geolocation.checkPermissions();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async ionViewWillEnter() {
|
||||
await super.ionViewWillEnter();
|
||||
this.locationWatch?.unsubscribe();
|
||||
this.locationWatch = this.createLocationWatch();
|
||||
this.isNotInView$.next(false);
|
||||
}
|
||||
|
||||
ionViewWillLeave() {
|
||||
this.locationWatch?.unsubscribe();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
super.ngOnDestroy();
|
||||
this.locationWatch?.unsubscribe();
|
||||
this.isNotInView$.next(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Component, DestroyRef, inject, Input, OnInit} from '@angular/core';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {Keyboard} from '@capacitor/keyboard';
|
||||
import {AlertController, AnimationBuilder, AnimationController} from '@ionic/angular';
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
SCThings,
|
||||
} from '@openstapps/core';
|
||||
import {NGXLogger} from 'ngx-logger';
|
||||
import {combineLatest, Subject, Subscription} from 'rxjs';
|
||||
import {combineLatest, Subject} from 'rxjs';
|
||||
import {debounceTime, distinctUntilChanged, startWith} from 'rxjs/operators';
|
||||
import {ContextMenuService} from '../../menu/context/context-menu.service';
|
||||
import {SettingsProvider} from '../../settings/settings.provider';
|
||||
@@ -35,6 +35,7 @@ import {DataProvider} from '../data.provider';
|
||||
import {PositionService} from '../../map/position.service';
|
||||
import {ConfigProvider} from '../../config/config.provider';
|
||||
import {searchPageSwitchAnimation} from './search-page-switch-animation';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
/**
|
||||
* SearchPageComponent queries things and shows list of things as search results and filter as context menu
|
||||
@@ -45,7 +46,7 @@ import {searchPageSwitchAnimation} from './search-page-switch-animation';
|
||||
styleUrls: ['search-page.scss'],
|
||||
providers: [ContextMenuService],
|
||||
})
|
||||
export class SearchPageComponent implements OnInit, OnDestroy {
|
||||
export class SearchPageComponent implements OnInit {
|
||||
@Input() title = 'search.title';
|
||||
|
||||
@Input() placeholder = 'search.search_bar.placeholder';
|
||||
@@ -141,10 +142,7 @@ export class SearchPageComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
sortQuery: SCSearchSort[] | undefined;
|
||||
|
||||
/**
|
||||
* Array of all subscriptions to Observables
|
||||
*/
|
||||
subscriptions: Subscription[] = [];
|
||||
destroy$ = inject(DestroyRef);
|
||||
|
||||
routeAnimation: AnimationBuilder;
|
||||
|
||||
@@ -286,7 +284,7 @@ export class SearchPageComponent implements OnInit, OnDestroy {
|
||||
this.contextMenuService.updateContextFilter(facets);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
ngOnInit(defaultListeners = true) {
|
||||
this.initialize();
|
||||
this.contextMenuService.setContextSort({
|
||||
name: 'sort',
|
||||
@@ -308,7 +306,7 @@ export class SearchPageComponent implements OnInit, OnDestroy {
|
||||
],
|
||||
});
|
||||
|
||||
this.subscriptions.push(
|
||||
if (defaultListeners) {
|
||||
combineLatest([
|
||||
this.queryTextChanged.pipe(
|
||||
debounceTime(this.searchQueryDueTime),
|
||||
@@ -317,30 +315,37 @@ export class SearchPageComponent implements OnInit, OnDestroy {
|
||||
),
|
||||
this.contextMenuService.filterQueryChanged$.pipe(startWith(this.filterQuery)),
|
||||
this.contextMenuService.sortQueryChanged$.pipe(startWith(this.sortQuery)),
|
||||
]).subscribe(async query => {
|
||||
this.queryText = query[0];
|
||||
this.filterQuery = query[1];
|
||||
this.sortQuery = query[2];
|
||||
this.from = 0;
|
||||
if (this.filterQuery !== undefined || this.queryText?.length > 0 || this.showDefaultData) {
|
||||
await this.fetchAndUpdateItems();
|
||||
this.queryChanged.next();
|
||||
}
|
||||
}),
|
||||
this.settingsProvider.settingsActionChanged$.subscribe(({type, payload}) => {
|
||||
if (type === 'stapps.settings.changed') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const {category, name, value} = payload!;
|
||||
this.logger.log(`received event "settings.changed" with category:
|
||||
])
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(async query => {
|
||||
this.queryText = query[0];
|
||||
this.filterQuery = query[1];
|
||||
this.sortQuery = query[2];
|
||||
this.from = 0;
|
||||
if (this.filterQuery !== undefined || this.queryText?.length > 0 || this.showDefaultData) {
|
||||
await this.fetchAndUpdateItems();
|
||||
this.queryChanged.next();
|
||||
}
|
||||
});
|
||||
this.settingsProvider.settingsActionChanged$
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(({type, payload}) => {
|
||||
if (type === 'stapps.settings.changed') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const {category, name, value} = payload!;
|
||||
this.logger.log(`received event "settings.changed" with category:
|
||||
${category}, name: ${name}, value: ${JSON.stringify(value)}`);
|
||||
}
|
||||
}),
|
||||
this.dataRoutingService.itemSelectListener().subscribe(item => {
|
||||
if (this.itemRouting) {
|
||||
void this.router.navigate(['/data-detail', item.uid]);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
this.dataRoutingService
|
||||
.itemSelectListener()
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(item => {
|
||||
if (this.itemRouting) {
|
||||
void this.router.navigate(['/data-detail', item.uid]);
|
||||
}
|
||||
});
|
||||
}
|
||||
try {
|
||||
const features = this.configProvider.getValue('features') as SCFeatureConfiguration;
|
||||
this.isHebisAvailable = !!features.plugins?.['hebis-plugin']?.urlPath;
|
||||
@@ -359,10 +364,4 @@ export class SearchPageComponent implements OnInit, OnDestroy {
|
||||
this.searchStringChanged(term);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
for (const subscription of this.subscriptions) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component, ContentChild, Input, OnDestroy, OnInit, TemplateRef} from '@angular/core';
|
||||
import {Component, ContentChild, DestroyRef, inject, Input, OnInit, TemplateRef} from '@angular/core';
|
||||
import {SCThings} from '@openstapps/core';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {Router} from '@angular/router';
|
||||
import {DataRoutingService} from '../data-routing.service';
|
||||
import {DataListContext} from './data-list.component';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
/**
|
||||
* Shows the list of items
|
||||
@@ -27,7 +27,7 @@ import {DataListContext} from './data-list.component';
|
||||
templateUrl: 'simple-data-list.html',
|
||||
styleUrls: ['simple-data-list.scss'],
|
||||
})
|
||||
export class SimpleDataListComponent implements OnInit, OnDestroy {
|
||||
export class SimpleDataListComponent implements OnInit {
|
||||
@Input() items?: Promise<SCThings[] | undefined>;
|
||||
|
||||
/**
|
||||
@@ -51,28 +51,17 @@ export class SimpleDataListComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
skeletonItems = 6;
|
||||
|
||||
/**
|
||||
* Array of all subscriptions to Observables
|
||||
*/
|
||||
subscriptions: Subscription[] = [];
|
||||
destroy$ = inject(DestroyRef);
|
||||
|
||||
constructor(protected router: Router, private readonly dataRoutingService: DataRoutingService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (!this.autoRouting) return;
|
||||
this.subscriptions.push(
|
||||
this.dataRoutingService.itemSelectListener().subscribe(item => {
|
||||
this.dataRoutingService
|
||||
.itemSelectListener()
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(item => {
|
||||
void this.router.navigate(['/data-detail', item.uid]);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove subscriptions when the component is removed
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
for (const sub of this.subscriptions) {
|
||||
sub.unsubscribe();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,42 +12,30 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {SCBuilding, SCFloor, SCPointOfInterest, SCRoom, SCThings} from '@openstapps/core';
|
||||
import {DataProvider} from '../../data.provider';
|
||||
import {hasValidLocation, isSCFloor} from './place-types';
|
||||
import {DataRoutingService} from '../../data-routing.service';
|
||||
import {Router} from '@angular/router';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
@Component({
|
||||
providers: [DataProvider],
|
||||
styleUrls: ['place-detail-content.scss'],
|
||||
selector: 'stapps-place-detail-content',
|
||||
templateUrl: 'place-detail-content.html',
|
||||
})
|
||||
export class PlaceDetailContentComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
export class PlaceDetailContentComponent implements OnInit {
|
||||
@Input() item: SCBuilding | SCRoom | SCPointOfInterest | SCFloor;
|
||||
|
||||
@Input() openAsModal = false;
|
||||
|
||||
/**
|
||||
* Does it have valid location or not (for showing in in a map widget)
|
||||
* Does it have a valid location or not (for showing in a map widget)
|
||||
*/
|
||||
hasValidLocation = false;
|
||||
|
||||
itemRouting: Subscription;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* @param item TODO
|
||||
*/
|
||||
hasCategories(item: SCThings): item is SCThings & {categories: string[]} {
|
||||
return (item as {categories: string[]}).categories !== undefined;
|
||||
}
|
||||
@@ -67,16 +55,15 @@ export class PlaceDetailContentComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
constructor(dataRoutingService: DataRoutingService, router: Router) {
|
||||
this.itemRouting = dataRoutingService.itemSelectListener().subscribe(item => {
|
||||
void router.navigate(['/data-detail', item.uid]);
|
||||
});
|
||||
dataRoutingService
|
||||
.itemSelectListener()
|
||||
.pipe(takeUntilDestroyed())
|
||||
.subscribe(item => {
|
||||
void router.navigate(['/data-detail', item.uid]);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.hasValidLocation = !isSCFloor(this.item) && hasValidLocation(this.item);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.itemRouting.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import moment, {Moment} from 'moment';
|
||||
import {AfterViewInit, Component, Input, OnDestroy} from '@angular/core';
|
||||
import {AfterViewInit, Component, DestroyRef, inject, Input} from '@angular/core';
|
||||
import {SCDish, SCISO8601Date, SCPlace} from '@openstapps/core';
|
||||
import {PlaceMensaService} from './place-mensa-service';
|
||||
import {Router} from '@angular/router';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {IonRouterOutlet} from '@ionic/angular';
|
||||
import {DataRoutingService} from '../../../../data-routing.service';
|
||||
import {groupBy} from '@openstapps/collection-utils';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
/**
|
||||
* TODO
|
||||
@@ -31,7 +31,7 @@ import {groupBy} from '@openstapps/collection-utils';
|
||||
templateUrl: 'place-mensa.html',
|
||||
styleUrls: ['place-mensa.scss'],
|
||||
})
|
||||
export class PlaceMensaDetailComponent implements AfterViewInit, OnDestroy {
|
||||
export class PlaceMensaDetailComponent implements AfterViewInit {
|
||||
/**
|
||||
* Map of dishes for each day
|
||||
*/
|
||||
@@ -44,9 +44,6 @@ export class PlaceMensaDetailComponent implements AfterViewInit, OnDestroy {
|
||||
*/
|
||||
@Input() displayRange = 7;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
@Input() item: SCPlace;
|
||||
|
||||
@Input() openAsModal = false;
|
||||
@@ -61,10 +58,7 @@ export class PlaceMensaDetailComponent implements AfterViewInit, OnDestroy {
|
||||
*/
|
||||
startingDay: Moment;
|
||||
|
||||
/**
|
||||
* Array of all subscriptions to Observables
|
||||
*/
|
||||
subscriptions: Subscription[] = [];
|
||||
destroy$ = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
private readonly mensaService: PlaceMensaService,
|
||||
@@ -75,16 +69,14 @@ export class PlaceMensaDetailComponent implements AfterViewInit, OnDestroy {
|
||||
this.startingDay = moment().startOf('day');
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
ngAfterViewInit() {
|
||||
if (!this.openAsModal) {
|
||||
this.subscriptions.push(
|
||||
this.dataRoutingService.itemSelectListener().subscribe(item => {
|
||||
this.dataRoutingService
|
||||
.itemSelectListener()
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(item => {
|
||||
void this.router.navigate(['/data-detail', item.uid]);
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const dishesByDay = this.mensaService.getAllDishes(this.item, this.displayRange);
|
||||
@@ -111,13 +103,4 @@ export class PlaceMensaDetailComponent implements AfterViewInit, OnDestroy {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove subscriptions when the component is removed
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
for (const sub of this.subscriptions) {
|
||||
sub.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import {DataProvider} from '../data/data.provider';
|
||||
import {SettingsProvider} from '../settings/settings.provider';
|
||||
import {PositionService} from '../map/position.service';
|
||||
import {ConfigProvider} from '../config/config.provider';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
/**
|
||||
* The page for showing favorites
|
||||
@@ -71,23 +72,21 @@ export class FavoritesPageComponent extends SearchPageComponent implements OnIni
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
for (const subscription of this.subscriptions) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
super.ngOnInit(false);
|
||||
|
||||
// Recreate subscriptions to handle different routing
|
||||
this.subscriptions.push(
|
||||
combineLatest([
|
||||
this.queryTextChanged.pipe(
|
||||
debounceTime(this.searchQueryDueTime),
|
||||
distinctUntilChanged(),
|
||||
startWith(this.queryText),
|
||||
),
|
||||
this.contextMenuService.filterQueryChanged$.pipe(startWith(this.filterQuery)),
|
||||
this.contextMenuService.sortQueryChanged$.pipe(startWith(this.sortQuery)),
|
||||
this.favoritesService.favoritesChanged$,
|
||||
]).subscribe(async query => {
|
||||
combineLatest([
|
||||
this.queryTextChanged.pipe(
|
||||
debounceTime(this.searchQueryDueTime),
|
||||
distinctUntilChanged(),
|
||||
startWith(this.queryText),
|
||||
),
|
||||
this.contextMenuService.filterQueryChanged$.pipe(startWith(this.filterQuery)),
|
||||
this.contextMenuService.sortQueryChanged$.pipe(startWith(this.sortQuery)),
|
||||
this.favoritesService.favoritesChanged$,
|
||||
])
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(async query => {
|
||||
this.queryText = query[0];
|
||||
this.filterQuery = query[1];
|
||||
this.sortQuery = query[2];
|
||||
@@ -96,16 +95,21 @@ export class FavoritesPageComponent extends SearchPageComponent implements OnIni
|
||||
await this.fetchAndUpdateItems();
|
||||
this.queryChanged.next();
|
||||
}
|
||||
}),
|
||||
this.settingsProvider.settingsActionChanged$.subscribe(({type, payload}) => {
|
||||
});
|
||||
this.settingsProvider.settingsActionChanged$
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(({type, payload}) => {
|
||||
if (type === 'stapps.settings.changed') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const {category, name, value} = payload!;
|
||||
this.logger.log(`received event "settings.changed" with category:
|
||||
${category}, name: ${name}, value: ${JSON.stringify(value)}`);
|
||||
}
|
||||
}),
|
||||
this.dataRoutingService.itemSelectListener().subscribe(item => {
|
||||
});
|
||||
this.dataRoutingService
|
||||
.itemSelectListener()
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(item => {
|
||||
if (this.itemRouting) {
|
||||
if ([SCThingType.Book, SCThingType.Periodical, SCThingType.Article].includes(item.type)) {
|
||||
void this.router.navigate([
|
||||
@@ -116,8 +120,7 @@ export class FavoritesPageComponent extends SearchPageComponent implements OnIni
|
||||
void this.router.navigate(['data-detail', item.uid]);
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {AlertController, AnimationController} from '@ionic/angular';
|
||||
import {NGXLogger} from 'ngx-logger';
|
||||
@@ -25,6 +25,7 @@ import {SearchPageComponent} from '../../data/list/search-page.component';
|
||||
import {HebisDataProvider} from '../hebis-data.provider';
|
||||
import {PositionService} from '../../map/position.service';
|
||||
import {ConfigProvider} from '../../config/config.provider';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
/**
|
||||
* HebisSearchPageComponent queries things and shows list of things as search results and filter as context menu
|
||||
@@ -34,7 +35,7 @@ import {ConfigProvider} from '../../config/config.provider';
|
||||
templateUrl: 'hebis-search-page.html',
|
||||
styleUrls: ['../../data/list/search-page.scss'],
|
||||
})
|
||||
export class HebisSearchPageComponent extends SearchPageComponent implements OnInit, OnDestroy {
|
||||
export class HebisSearchPageComponent extends SearchPageComponent implements OnInit {
|
||||
/**
|
||||
* If routing should be done if the user clicks on an item
|
||||
*/
|
||||
@@ -144,43 +145,42 @@ export class HebisSearchPageComponent extends SearchPageComponent implements OnI
|
||||
//this.fetchAndUpdateItems();
|
||||
this.initialize();
|
||||
|
||||
this.subscriptions.push(
|
||||
combineLatest([
|
||||
this.queryTextChanged.pipe(
|
||||
debounceTime(this.searchQueryDueTime),
|
||||
distinctUntilChanged(),
|
||||
startWith(this.queryText),
|
||||
),
|
||||
]).subscribe(async query => {
|
||||
combineLatest([
|
||||
this.queryTextChanged.pipe(
|
||||
debounceTime(this.searchQueryDueTime),
|
||||
distinctUntilChanged(),
|
||||
startWith(this.queryText),
|
||||
),
|
||||
])
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(async query => {
|
||||
this.queryText = query[0];
|
||||
this.page = 0;
|
||||
if (this.queryText?.length > 0 || this.showDefaultData) {
|
||||
await this.fetchAndUpdateItems();
|
||||
this.queryChanged.next();
|
||||
}
|
||||
}),
|
||||
this.settingsProvider.settingsActionChanged$.subscribe(({type, payload}) => {
|
||||
});
|
||||
this.settingsProvider.settingsActionChanged$
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(({type, payload}) => {
|
||||
if (type === 'stapps.settings.changed') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const {category, name, value} = payload!;
|
||||
this.logger.log(`received event "settings.changed" with category:
|
||||
${category}, name: ${name}, value: ${JSON.stringify(value)}`);
|
||||
}
|
||||
}),
|
||||
this.dataRoutingService.itemSelectListener().subscribe(async item => {
|
||||
});
|
||||
this.dataRoutingService
|
||||
.itemSelectListener()
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(async item => {
|
||||
if (this.itemRouting) {
|
||||
void this.router.navigate([
|
||||
'hebis-detail',
|
||||
(item.origin && 'originalId' in item.origin && item.origin['originalId']) || '',
|
||||
]);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
for (const subscription of this.subscriptions) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,20 +13,22 @@
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Location} from '@angular/common';
|
||||
import {ChangeDetectorRef, Component, ElementRef, ViewChild} from '@angular/core';
|
||||
import {ChangeDetectorRef, Component, DestroyRef, ElementRef, inject, OnInit, ViewChild} from '@angular/core';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {Keyboard} from '@capacitor/keyboard';
|
||||
import {AlertController, IonRouterOutlet, ModalController} from '@ionic/angular';
|
||||
import {TranslateService} from '@ngx-translate/core';
|
||||
import {SCBuilding, SCPlace, SCRoom, SCSearchFilter, SCUuid} from '@openstapps/core';
|
||||
import {featureGroup, geoJSON, LatLng, Layer, Map, MapOptions, Marker, tileLayer} from 'leaflet';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {BehaviorSubject} from 'rxjs';
|
||||
import {DataRoutingService} from '../../data/data-routing.service';
|
||||
import {ContextMenuService} from '../../menu/context/context-menu.service';
|
||||
import {MapProvider} from '../map.provider';
|
||||
import {MapPosition, PositionService} from '../position.service';
|
||||
import {Geolocation, PermissionStatus} from '@capacitor/geolocation';
|
||||
import {Capacitor} from '@capacitor/core';
|
||||
import {pauseWhen} from '../../../util/pause-when';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
/**
|
||||
* The main page of the map
|
||||
@@ -36,7 +38,7 @@ import {Capacitor} from '@capacitor/core';
|
||||
templateUrl: './map-page.html',
|
||||
providers: [ContextMenuService],
|
||||
})
|
||||
export class MapPageComponent {
|
||||
export class MapPageComponent implements OnInit {
|
||||
/**
|
||||
* Default map zoom level
|
||||
*/
|
||||
@@ -115,10 +117,9 @@ export class MapPageComponent {
|
||||
*/
|
||||
queryText: string;
|
||||
|
||||
/**
|
||||
* Subscriptions used by the page
|
||||
*/
|
||||
subscriptions: Subscription[] = [];
|
||||
isNotInView$ = new BehaviorSubject(true);
|
||||
|
||||
destroy$ = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
private translateService: TranslateService,
|
||||
@@ -148,6 +149,34 @@ export class MapPageComponent {
|
||||
};
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.dataRoutingService
|
||||
.itemSelectListener()
|
||||
.pipe(pauseWhen(this.isNotInView$), takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(async item => {
|
||||
// in case the list item is clicked
|
||||
if (this.items.length > 1) {
|
||||
await Promise.all([this.modalController.dismiss(), this.showItem(item.uid)]);
|
||||
} else {
|
||||
void this.router.navigate(['/data-detail', item.uid]);
|
||||
}
|
||||
});
|
||||
this.positionService
|
||||
.watchCurrentLocation({enableHighAccuracy: true, maximumAge: 1000})
|
||||
.pipe(pauseWhen(this.isNotInView$), takeUntilDestroyed(this.destroy$))
|
||||
.subscribe({
|
||||
next: (position: MapPosition) => {
|
||||
this.position = position;
|
||||
this.positionMarker = MapProvider.getPositionMarker(position, 'stapps-device-location', 32);
|
||||
},
|
||||
error: async _error => {
|
||||
this.locationStatus = await Geolocation.checkPermissions();
|
||||
// eslint-disable-next-line unicorn/no-null
|
||||
this.position = null;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate to coordinates
|
||||
* @param latLng Coordinates to animate to
|
||||
@@ -257,47 +286,18 @@ export class MapPageComponent {
|
||||
* Subscribe to needed observables and get the location status when user is entering the page
|
||||
*/
|
||||
async ionViewWillEnter() {
|
||||
this.isNotInView$.next(false);
|
||||
if (this.positionService.position) {
|
||||
this.position = this.positionService.position;
|
||||
this.positionMarker = MapProvider.getPositionMarker(this.position, 'stapps-device-location', 32);
|
||||
}
|
||||
|
||||
this.subscriptions.push(
|
||||
this.dataRoutingService.itemSelectListener().subscribe(async item => {
|
||||
// in case the list item is clicked
|
||||
if (this.items.length > 1) {
|
||||
await Promise.all([this.modalController.dismiss(), this.showItem(item.uid)]);
|
||||
} else {
|
||||
void this.router.navigate(['/data-detail', item.uid]);
|
||||
}
|
||||
}),
|
||||
this.positionService
|
||||
.watchCurrentLocation(this.constructor.name, {enableHighAccuracy: true, maximumAge: 1000})
|
||||
.subscribe({
|
||||
next: (position: MapPosition) => {
|
||||
this.position = position;
|
||||
this.positionMarker = MapProvider.getPositionMarker(position, 'stapps-device-location', 32);
|
||||
},
|
||||
error: async _error => {
|
||||
this.locationStatus = await Geolocation.checkPermissions();
|
||||
// eslint-disable-next-line unicorn/no-null
|
||||
this.position = null;
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
// get detailed location status (diagnostics only supports devices)
|
||||
this.locationStatus = await Geolocation.checkPermissions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from all subscriptions when user leaves page
|
||||
*/
|
||||
ionViewWillLeave() {
|
||||
void this.positionService.clearWatcher(this.constructor.name);
|
||||
for (const sub of this.subscriptions) {
|
||||
sub.unsubscribe();
|
||||
}
|
||||
this.isNotInView$.next(true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -322,12 +322,12 @@ export class MapPageComponent {
|
||||
this.addToMap(this.items, true, uid !== null);
|
||||
this.contextMenuService.updateContextFilter(response.facets);
|
||||
|
||||
this.subscriptions.push(
|
||||
this.contextMenuService.filterQueryChanged$.subscribe(query => {
|
||||
this.contextMenuService.filterQueryChanged$
|
||||
.pipe(pauseWhen(this.isNotInView$), takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(query => {
|
||||
this.filterQuery = query;
|
||||
this.fetchAndUpdateItems(false, true);
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
this.distance = this.positionService.getDistance(this.items[0].geo.point);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import {StorageModule} from '../storage/storage.module';
|
||||
import {MapPosition, PositionService} from './position.service';
|
||||
import {ConfigProvider} from '../config/config.provider';
|
||||
import {LoggerTestingModule} from 'ngx-logger/testing';
|
||||
import {firstValueFrom} from 'rxjs';
|
||||
import {Geolocation} from '@capacitor/geolocation';
|
||||
|
||||
describe('PositionService', () => {
|
||||
let positionService: PositionService;
|
||||
@@ -54,31 +56,17 @@ describe('PositionService', () => {
|
||||
expect(currentLocation).toEqual(sampleMapPosition);
|
||||
});
|
||||
|
||||
it('should continuously provide (watch) location of the device', done => {
|
||||
positionService.watchCurrentLocation('testCaller').subscribe(location => {
|
||||
expect(location).toBeDefined();
|
||||
done();
|
||||
});
|
||||
it('should continuously provide (watch) location of the device', async () => {
|
||||
expect(await firstValueFrom(positionService.watchCurrentLocation())).toBeDefined();
|
||||
});
|
||||
|
||||
it('should stop to continuously provide (watch) location of the device', done => {
|
||||
positionService.watchers.set(
|
||||
'clearWatch',
|
||||
new Promise(resolve => {
|
||||
setTimeout(function () {
|
||||
resolve(`watcherID123`);
|
||||
}, 20);
|
||||
}),
|
||||
);
|
||||
positionService
|
||||
.clearWatcher('clearWatch')
|
||||
.then(result => {
|
||||
expect(result).toBeUndefined();
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
expect(error).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
it('should stop to continuously provide (watch) location of the device', async () => {
|
||||
const watchPosition = spyOn(Geolocation, 'watchPosition').and.resolveTo('abc');
|
||||
const clearWatch = spyOn(Geolocation, 'clearWatch').and.callThrough();
|
||||
const subscription = positionService.watchCurrentLocation().subscribe();
|
||||
expect(watchPosition).toHaveBeenCalled();
|
||||
subscription.unsubscribe();
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
expect(clearWatch).toHaveBeenCalledWith({id: 'abc'});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -85,11 +85,10 @@ export class PositionService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Watches (continuously gets) current coordinates information of the device
|
||||
* @param caller Identifier for later reference. (I.e use of `clearWatcher`)
|
||||
* @param options Options which define which data should be provided (e.g. how accurate or how old)
|
||||
* Watches (continuously gets) the current coordinates information of the device
|
||||
* @param options Options which define which data should be provided (e.g., how accurate or how old)
|
||||
*/
|
||||
watchCurrentLocation(caller: string, options: PositionOptions = {}): Observable<MapPosition> {
|
||||
watchCurrentLocation(options: PositionOptions = {}): Observable<MapPosition> {
|
||||
return new Observable(subscriber => {
|
||||
const watcherID = Geolocation.watchPosition(options, (position, error) => {
|
||||
if (error) {
|
||||
@@ -106,18 +105,15 @@ export class PositionService {
|
||||
subscriber.next(this.position);
|
||||
}
|
||||
});
|
||||
this.watchers.set(caller, watcherID);
|
||||
watcherID.then(console.log);
|
||||
return {
|
||||
unsubscribe() {
|
||||
watcherID.then(id => {
|
||||
console.log(id);
|
||||
void Geolocation.clearWatch({id});
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears watcher for a certain caller
|
||||
* @param caller Identifier of the caller wanting to clear the watcher
|
||||
*/
|
||||
async clearWatcher(caller: string): Promise<void> {
|
||||
const watcherID = await this.watchers.get(caller);
|
||||
if (watcherID) {
|
||||
Geolocation.clearWatch({id: watcherID});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component, Input, OnDestroy} from '@angular/core';
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {LangChangeEvent, TranslateService} from '@ngx-translate/core';
|
||||
import {SCLanguage, SCThingTranslator, SCThingType, SCTranslations} from '@openstapps/core';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {ContextMenuService} from './context-menu.service';
|
||||
import {FilterContext, FilterFacet, SortContext, SortContextOption} from './context-type.js';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
/**
|
||||
* The context menu
|
||||
@@ -32,7 +32,7 @@ import {FilterContext, FilterFacet, SortContext, SortContextOption} from './cont
|
||||
selector: 'stapps-context',
|
||||
templateUrl: 'context-menu.html',
|
||||
})
|
||||
export class ContextMenuComponent implements OnDestroy {
|
||||
export class ContextMenuComponent {
|
||||
/**
|
||||
* Id of the content the menu is used for
|
||||
*/
|
||||
@@ -77,11 +77,6 @@ export class ContextMenuComponent implements OnDestroy {
|
||||
*/
|
||||
sortOption: SortContext;
|
||||
|
||||
/**
|
||||
* Array of all Subscriptions
|
||||
*/
|
||||
subscriptions: Subscription[] = [];
|
||||
|
||||
/**
|
||||
* Core translator
|
||||
*/
|
||||
@@ -94,18 +89,16 @@ export class ContextMenuComponent implements OnDestroy {
|
||||
this.language = this.translateService.currentLang as keyof SCTranslations<SCLanguage>;
|
||||
this.translator = new SCThingTranslator(this.language);
|
||||
|
||||
this.subscriptions.push(
|
||||
this.translateService.onLangChange.subscribe((event: LangChangeEvent) => {
|
||||
this.language = event.lang as keyof SCTranslations<SCLanguage>;
|
||||
this.translator = new SCThingTranslator(this.language);
|
||||
}),
|
||||
this.contextMenuService.filterContextChanged$.subscribe(filterContext => {
|
||||
this.filterOption = filterContext;
|
||||
}),
|
||||
this.contextMenuService.sortOptions.subscribe(sortContext => {
|
||||
this.sortOption = sortContext;
|
||||
}),
|
||||
);
|
||||
this.translateService.onLangChange.pipe(takeUntilDestroyed()).subscribe((event: LangChangeEvent) => {
|
||||
this.language = event.lang as keyof SCTranslations<SCLanguage>;
|
||||
this.translator = new SCThingTranslator(this.language);
|
||||
});
|
||||
this.contextMenuService.filterContextChanged$.pipe(takeUntilDestroyed()).subscribe(filterContext => {
|
||||
this.filterOption = filterContext;
|
||||
});
|
||||
this.contextMenuService.sortOptions.pipe(takeUntilDestroyed()).subscribe(sortContext => {
|
||||
this.sortOption = sortContext;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,15 +115,6 @@ export class ContextMenuComponent implements OnDestroy {
|
||||
return this.translator.translatedPropertyValue(onlyForType, field, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from Observables
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
for (const sub of this.subscriptions) {
|
||||
sub.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets filter options
|
||||
*/
|
||||
|
||||
@@ -12,40 +12,35 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Component, ElementRef, HostBinding, OnDestroy, ViewChild} from '@angular/core';
|
||||
import {Component, ElementRef, HostBinding, ViewChild} from '@angular/core';
|
||||
import {InternetConnectionService} from '../../../util/internet-connection.service';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {Router} from '@angular/router';
|
||||
import {NGXLogger} from 'ngx-logger';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
@Component({
|
||||
selector: 'stapps-offline-notice',
|
||||
templateUrl: 'offline-notice.html',
|
||||
styleUrls: ['offline-notice.scss'],
|
||||
})
|
||||
export class OfflineNoticeComponent implements OnDestroy {
|
||||
export class OfflineNoticeComponent {
|
||||
@HostBinding('class.is-offline') isOffline = false;
|
||||
|
||||
@HostBinding('class.has-error') hasError = false;
|
||||
|
||||
@ViewChild('spinIcon', {read: ElementRef}) spinIcon: ElementRef;
|
||||
|
||||
readonly subscriptions: Subscription[];
|
||||
|
||||
constructor(
|
||||
readonly offlineProvider: InternetConnectionService,
|
||||
readonly router: Router,
|
||||
readonly logger: NGXLogger,
|
||||
) {
|
||||
this.subscriptions = [
|
||||
this.offlineProvider.offline$.subscribe(isOffline => {
|
||||
this.isOffline = isOffline;
|
||||
}),
|
||||
this.offlineProvider.error$.subscribe(hasError => {
|
||||
this.hasError = hasError;
|
||||
}),
|
||||
];
|
||||
this.offlineProvider.offline$.pipe(takeUntilDestroyed()).subscribe(isOffline => {
|
||||
this.isOffline = isOffline;
|
||||
});
|
||||
this.offlineProvider.error$.pipe(takeUntilDestroyed()).subscribe(hasError => {
|
||||
this.hasError = hasError;
|
||||
});
|
||||
}
|
||||
|
||||
retry() {
|
||||
@@ -54,10 +49,4 @@ export class OfflineNoticeComponent implements OnDestroy {
|
||||
this.spinIcon.nativeElement.classList.add('spin');
|
||||
this.offlineProvider.retry();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
for (const subscription of this.subscriptions) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,28 +12,26 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Directive, ElementRef, Input, OnDestroy, OnInit, Renderer2} from '@angular/core';
|
||||
import {DestroyRef, Directive, ElementRef, inject, Input, OnInit, Renderer2} from '@angular/core';
|
||||
import {AnimationController, NavController} from '@ionic/angular';
|
||||
import {Router, RouterEvent} from '@angular/router';
|
||||
import {tabsTransition} from './tabs-transition';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
@Directive({
|
||||
selector: '[rootLink]',
|
||||
})
|
||||
export class RootLinkDirective implements OnInit, OnDestroy {
|
||||
export class RootLinkDirective implements OnInit {
|
||||
@Input() rootLink: string;
|
||||
|
||||
@Input() redirectedFrom: string;
|
||||
|
||||
dispose: () => void;
|
||||
|
||||
subscriptions: Subscription[] = [];
|
||||
|
||||
private readonly classNames = ['tab-selected', 'link-active'];
|
||||
|
||||
private needsInit = true;
|
||||
|
||||
destroy$ = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
private element: ElementRef,
|
||||
private renderer: Renderer2,
|
||||
@@ -52,28 +50,28 @@ export class RootLinkDirective implements OnInit, OnDestroy {
|
||||
this.needsInit = false;
|
||||
}
|
||||
|
||||
this.subscriptions.push(
|
||||
this.router.events.subscribe(event => {
|
||||
if (
|
||||
event instanceof RouterEvent &&
|
||||
// @ts-expect-error access private member
|
||||
(this.navController.direction === 'root' || this.needsInit)
|
||||
) {
|
||||
if (event.url === this.rootLink || (this.redirectedFrom && event.url === this.redirectedFrom)) {
|
||||
this.setActive();
|
||||
} else {
|
||||
this.setInactive();
|
||||
}
|
||||
this.needsInit = false;
|
||||
this.router.events.pipe(takeUntilDestroyed(this.destroy$)).subscribe(event => {
|
||||
if (
|
||||
event instanceof RouterEvent &&
|
||||
// @ts-expect-error access private member
|
||||
(this.navController.direction === 'root' || this.needsInit)
|
||||
) {
|
||||
if (event.url === this.rootLink || (this.redirectedFrom && event.url === this.redirectedFrom)) {
|
||||
this.setActive();
|
||||
} else {
|
||||
this.setInactive();
|
||||
}
|
||||
this.needsInit = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.destroy$.onDestroy(
|
||||
this.renderer.listen(this.element.nativeElement, 'click', () => {
|
||||
this.setActive();
|
||||
this.navController.setDirection('root', true, 'back', animation);
|
||||
void this.router.navigate([this.rootLink]);
|
||||
}),
|
||||
);
|
||||
|
||||
this.dispose = this.renderer.listen(this.element.nativeElement, 'click', () => {
|
||||
this.setActive();
|
||||
this.navController.setDirection('root', true, 'back', animation);
|
||||
void this.router.navigate([this.rootLink]);
|
||||
});
|
||||
}
|
||||
|
||||
setActive() {
|
||||
@@ -87,11 +85,4 @@ export class RootLinkDirective implements OnInit, OnDestroy {
|
||||
this.renderer.removeClass(this.element.nativeElement, className);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.dispose();
|
||||
for (const subscription of this.subscriptions) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,20 +12,20 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Component, DestroyRef, inject, Input, OnInit} from '@angular/core';
|
||||
import {SCSection} from '../../../../config/profile-page-sections';
|
||||
import {AuthHelperService} from '../../auth/auth-helper.service';
|
||||
import {Observable, Subscription} from 'rxjs';
|
||||
import {Observable} from 'rxjs';
|
||||
import {SCAuthorizationProviderType} from '@openstapps/core';
|
||||
import Swiper from 'swiper';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
@Component({
|
||||
selector: 'stapps-profile-page-section',
|
||||
templateUrl: 'profile-page-section.html',
|
||||
styleUrls: ['profile-page-section.scss'],
|
||||
})
|
||||
export class ProfilePageSectionComponent implements OnInit, OnDestroy {
|
||||
export class ProfilePageSectionComponent implements OnInit {
|
||||
@Input() item: SCSection;
|
||||
|
||||
@Input() minSlideWidth = 110;
|
||||
@@ -36,8 +36,6 @@ export class ProfilePageSectionComponent implements OnInit, OnDestroy {
|
||||
|
||||
isBeginning = true;
|
||||
|
||||
subscriptions: Subscription[] = [];
|
||||
|
||||
slidesPerView: number;
|
||||
|
||||
slidesFillScreen = false;
|
||||
@@ -53,15 +51,17 @@ export class ProfilePageSectionComponent implements OnInit, OnDestroy {
|
||||
},
|
||||
};
|
||||
|
||||
destroy$ = inject(DestroyRef);
|
||||
|
||||
constructor(private authHelper: AuthHelperService) {}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.item.authProvider) {
|
||||
this.subscriptions.push(
|
||||
this.data[this.item.authProvider].loggedIn$.subscribe(loggedIn => {
|
||||
this.data[this.item.authProvider].loggedIn$
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(loggedIn => {
|
||||
this.isLoggedIn = loggedIn;
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,10 +96,4 @@ export class ProfilePageSectionComponent implements OnInit, OnDestroy {
|
||||
await this.authHelper.getProvider(providerType).signOut();
|
||||
await this.authHelper.endBrowserSession(providerType);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
for (const subscription of this.subscriptions) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,8 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {Observable, of, Subscription} from 'rxjs';
|
||||
import {firstValueFrom, Observable, of, Subscription} from 'rxjs';
|
||||
import {AuthHelperService} from '../../auth/auth-helper.service';
|
||||
import {SCAuthorizationProviderType, SCDateSeries, SCUserConfiguration} from '@openstapps/core';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
@@ -93,22 +92,20 @@ export class ProfilePageComponent implements OnInit {
|
||||
}
|
||||
|
||||
async getMyCourses() {
|
||||
const uuidSubscription = this.scheduleProvider.uuids$.subscribe(async result => {
|
||||
const courses = await this.scheduleProvider.getDateSeries(result);
|
||||
const result = await firstValueFrom(this.scheduleProvider.uuids$);
|
||||
const courses = await this.scheduleProvider.getDateSeries(result);
|
||||
|
||||
for (const course of courses.dates) {
|
||||
for (const date of course.dates) {
|
||||
if (moment(date).startOf('day').format() === this.todayDate) {
|
||||
this.myCoursesToday[this.myCoursesToday.length] = {
|
||||
startTime: moment(date).format('LT'),
|
||||
endTime: moment(date).add(course.duration).format('LT'),
|
||||
course,
|
||||
};
|
||||
}
|
||||
for (const course of courses.dates) {
|
||||
for (const date of course.dates) {
|
||||
if (moment(date).startOf('day').format() === this.todayDate) {
|
||||
this.myCoursesToday[this.myCoursesToday.length] = {
|
||||
startTime: moment(date).format('LT'),
|
||||
endTime: moment(date).add(course.duration).format('LT'),
|
||||
course,
|
||||
};
|
||||
}
|
||||
}
|
||||
uuidSubscription.unsubscribe();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async signIn(providerType: SCAuthorizationProviderType) {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {AfterViewInit, Component, Input, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
||||
import {AfterViewInit, Component, Input, OnInit, ViewChild} from '@angular/core';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import moment from 'moment';
|
||||
import {materialFade, materialManualFade, materialSharedAxisX} from '../../../animation/material-motion';
|
||||
@@ -22,6 +22,7 @@ import {CalendarComponent} from './components/calendar.component';
|
||||
import {CalendarService} from '../../calendar/calendar.service';
|
||||
import {InfiniteSwiperComponent} from './grid/infinite-swiper.component';
|
||||
import {IonContent} from '@ionic/angular';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
/**
|
||||
* Component that displays the schedule
|
||||
@@ -32,7 +33,7 @@ import {IonContent} from '@ionic/angular';
|
||||
styleUrls: ['calendar-view.scss', './components/calendar-component.scss'],
|
||||
animations: [materialFade, materialSharedAxisX, materialManualFade],
|
||||
})
|
||||
export class CalendarViewComponent extends CalendarComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
export class CalendarViewComponent extends CalendarComponent implements OnInit, AfterViewInit {
|
||||
@ViewChild('mainSwiper') mainSwiper: InfiniteSwiperComponent;
|
||||
|
||||
@ViewChild('headerSwiper') headerSwiper: InfiniteSwiperComponent;
|
||||
@@ -73,11 +74,8 @@ export class CalendarViewComponent extends CalendarComponent implements OnInit,
|
||||
* Initialize
|
||||
*/
|
||||
ngOnInit() {
|
||||
super.onInit();
|
||||
if (this.calendarServiceSubscription) {
|
||||
this.calendarServiceSubscription.unsubscribe();
|
||||
}
|
||||
this.calendarServiceSubscription = this.calendarService.goToDateClicked.subscribe(async newIndex => {
|
||||
super.ngOnInit();
|
||||
this.calendarService.goToDateClicked.pipe(takeUntilDestroyed(this.destroy$)).subscribe(async newIndex => {
|
||||
await this.mainSwiper.goToIndex(newIndex);
|
||||
this.setDateRange(newIndex);
|
||||
await this.scrollCursorIntoView(this.content);
|
||||
@@ -88,13 +86,6 @@ export class CalendarViewComponent extends CalendarComponent implements OnInit,
|
||||
void this.scrollCursorIntoView(this.content);
|
||||
}
|
||||
|
||||
/**
|
||||
* OnDestroy
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load events
|
||||
*/
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Component, DestroyRef, inject, Input, OnInit} from '@angular/core';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import {SCISO8601Date, SCUuid} from '@openstapps/core';
|
||||
import moment, {Moment} from 'moment';
|
||||
@@ -22,9 +22,9 @@ import {ScheduleEvent, ScheduleResponsiveBreakpoint} from '../schema/schema';
|
||||
import {SwiperComponent} from 'swiper/angular';
|
||||
import {InfiniteSwiperComponent} from '../grid/infinite-swiper.component';
|
||||
import {IonContent, IonDatetime} from '@ionic/angular';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {CalendarService} from '../../../calendar/calendar.service';
|
||||
import {getScheduleCursorOffset} from '../grid/schedule-cursor-offset';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
/**
|
||||
* Component that displays the schedule
|
||||
@@ -35,7 +35,7 @@ import {getScheduleCursorOffset} from '../grid/schedule-cursor-offset';
|
||||
styleUrls: ['calendar-component.scss'],
|
||||
animations: [materialFade, materialSharedAxisX, materialManualFade],
|
||||
})
|
||||
export class CalendarComponent implements OnInit, OnDestroy {
|
||||
export class CalendarComponent implements OnInit {
|
||||
/**
|
||||
* The day that the schedule started out on
|
||||
*/
|
||||
@@ -52,8 +52,6 @@ export class CalendarComponent implements OnInit, OnDestroy {
|
||||
endDate: '',
|
||||
};
|
||||
|
||||
calendarServiceSubscription: Subscription;
|
||||
|
||||
prevHeaderIndex = 0;
|
||||
|
||||
/**
|
||||
@@ -98,15 +96,12 @@ export class CalendarComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
@Input() uuids: SCUuid[];
|
||||
|
||||
/**
|
||||
* UUID subscription
|
||||
*/
|
||||
uuidSubscription: Subscription;
|
||||
|
||||
@Input() useInfiniteSwiper = true;
|
||||
|
||||
@Input() weekDates: Array<Moment>;
|
||||
|
||||
destroy$ = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
protected readonly activatedRoute: ActivatedRoute,
|
||||
protected readonly calendarService: CalendarService,
|
||||
@@ -114,14 +109,6 @@ export class CalendarComponent implements OnInit, OnDestroy {
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.onInit();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy();
|
||||
}
|
||||
|
||||
onInit() {
|
||||
let dayString: string | number | null = this.activatedRoute.snapshot.paramMap.get('date');
|
||||
if (dayString == undefined || dayString === 'now') {
|
||||
const fragments = window.location.href.split('/');
|
||||
@@ -133,7 +120,7 @@ export class CalendarComponent implements OnInit, OnDestroy {
|
||||
this.baselineDate = moment(dayString).startOf('day');
|
||||
|
||||
this.initialSlideIndex = new Promise(resolve => {
|
||||
this.uuidSubscription = this.scheduleProvider.uuids$.subscribe(async result => {
|
||||
this.scheduleProvider.uuids$.pipe(takeUntilDestroyed(this.destroy$)).subscribe(async result => {
|
||||
this.uuids = result;
|
||||
resolve(await this.loadEvents());
|
||||
});
|
||||
@@ -150,13 +137,6 @@ export class CalendarComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
this.uuidSubscription.unsubscribe();
|
||||
if (this.calendarServiceSubscription) {
|
||||
this.calendarServiceSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get date from baseline date and index of current slide.
|
||||
* @param index number
|
||||
|
||||
@@ -12,14 +12,14 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Component, DestroyRef, inject, Input, OnInit} from '@angular/core';
|
||||
import {SCDateSeries, SCUuid} from '@openstapps/core';
|
||||
import moment from 'moment';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {materialFade} from '../../../animation/material-motion';
|
||||
import {ScheduleProvider} from '../../calendar/schedule.provider';
|
||||
import {ScheduleEvent} from './schema/schema';
|
||||
import {groupBy, omit, stringSortBy} from '@openstapps/collection-utils';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
/**
|
||||
* A single event
|
||||
@@ -31,7 +31,7 @@ export interface ScheduleSingleEvent {
|
||||
day: string;
|
||||
|
||||
/**
|
||||
* Event the date is referring to
|
||||
* The event the date is referring to
|
||||
*/
|
||||
event: ScheduleEvent;
|
||||
}
|
||||
@@ -45,12 +45,7 @@ export interface ScheduleSingleEvent {
|
||||
styleUrls: ['schedule-single-events.scss'],
|
||||
animations: [materialFade],
|
||||
})
|
||||
export class ScheduleSingleEventsComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* UUID subscription
|
||||
*/
|
||||
private _uuidSubscription: Subscription;
|
||||
|
||||
export class ScheduleSingleEventsComponent implements OnInit {
|
||||
/**
|
||||
* The events to display
|
||||
*/
|
||||
@@ -66,6 +61,8 @@ export class ScheduleSingleEventsComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
@Input() scale = 60;
|
||||
|
||||
destroy$ = inject(DestroyRef);
|
||||
|
||||
/**
|
||||
* Sorts dates to a list of days with events on each
|
||||
*/
|
||||
@@ -116,24 +113,13 @@ export class ScheduleSingleEventsComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
|
||||
// TODO: replace with filter
|
||||
const test = ScheduleSingleEventsComponent.groupDateSeriesToDays(
|
||||
return ScheduleSingleEventsComponent.groupDateSeriesToDays(
|
||||
dateSeries.dates.filter(it => !it.repeatFrequency),
|
||||
);
|
||||
return test;
|
||||
}
|
||||
|
||||
/**
|
||||
* OnDestroy
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this._uuidSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize
|
||||
*/
|
||||
ngOnInit() {
|
||||
this._uuidSubscription = this.scheduleProvider.uuids$.subscribe(async result => {
|
||||
this.scheduleProvider.uuids$.pipe(takeUntilDestroyed(this.destroy$)).subscribe(async result => {
|
||||
this.uuids = result;
|
||||
this.events = this.fetchDateSeries();
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {AfterViewInit, Component, Input, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
||||
import {AfterViewInit, Component, Input, OnInit, ViewChild} from '@angular/core';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
import moment, {Moment} from 'moment';
|
||||
import {materialFade, materialManualFade, materialSharedAxisX} from '../../../animation/material-motion';
|
||||
@@ -23,6 +23,7 @@ import {CalendarService} from '../../calendar/calendar.service';
|
||||
import {CalendarComponent} from './components/calendar.component';
|
||||
import {IonContent, IonDatetime} from '@ionic/angular';
|
||||
import {SwiperComponent} from 'swiper/angular';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
/**
|
||||
* Component that displays the schedule
|
||||
@@ -33,10 +34,7 @@ import {SwiperComponent} from 'swiper/angular';
|
||||
styleUrls: ['schedule-view.scss', './components/calendar-component.scss'],
|
||||
animations: [materialFade, materialSharedAxisX, materialManualFade],
|
||||
})
|
||||
export class ScheduleViewComponent
|
||||
extends CalendarComponent
|
||||
implements OnInit, AfterViewInit, OnDestroy, AfterViewInit
|
||||
{
|
||||
export class ScheduleViewComponent extends CalendarComponent implements OnInit, AfterViewInit {
|
||||
@ViewChild('mainSwiper') mainSwiper: SwiperComponent;
|
||||
|
||||
@ViewChild('headerSwiper') headerSwiper: SwiperComponent;
|
||||
@@ -99,11 +97,8 @@ export class ScheduleViewComponent
|
||||
* Initialize
|
||||
*/
|
||||
ngOnInit() {
|
||||
super.onInit();
|
||||
if (this.calendarServiceSubscription) {
|
||||
this.calendarServiceSubscription.unsubscribe();
|
||||
}
|
||||
this.calendarServiceSubscription = this.calendarService.goToDateClicked.subscribe(() => {
|
||||
super.ngOnInit();
|
||||
this.calendarService.goToDateClicked.pipe(takeUntilDestroyed(this.destroy$)).subscribe(() => {
|
||||
this.slideToToday();
|
||||
});
|
||||
}
|
||||
@@ -112,13 +107,6 @@ export class ScheduleViewComponent
|
||||
this.slideToToday();
|
||||
}
|
||||
|
||||
/**
|
||||
* OnDestroy
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Slide today into view.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 StApps
|
||||
* Copyright (C) 2023 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
@@ -12,8 +12,7 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Injectable, OnDestroy} from '@angular/core';
|
||||
import {Injectable} from '@angular/core';
|
||||
import {LangChangeEvent, TranslateService} from '@ngx-translate/core';
|
||||
import {
|
||||
SCLanguage,
|
||||
@@ -24,8 +23,8 @@ import {
|
||||
SCTranslations,
|
||||
} from '@openstapps/core';
|
||||
import moment from 'moment';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {isDefined, ThingTranslateParser} from './thing-translate.parser';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
// export const DEFAULT_LANGUAGE = new InjectionToken<string>('DEFAULT_LANGUAGE');
|
||||
|
||||
@@ -34,9 +33,7 @@ import {isDefined, ThingTranslateParser} from './thing-translate.parser';
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ThingTranslateService implements OnDestroy {
|
||||
onLangChange: Subscription;
|
||||
|
||||
export class ThingTranslateService {
|
||||
translator: SCThingTranslator;
|
||||
|
||||
/**
|
||||
@@ -49,7 +46,7 @@ export class ThingTranslateService implements OnDestroy {
|
||||
(translateService.currentLang ?? translateService.defaultLang) as SCLanguageCode,
|
||||
);
|
||||
/** set the default language from configuration */
|
||||
this.onLangChange = this.translateService.onLangChange.subscribe((event: LangChangeEvent) => {
|
||||
this.translateService.onLangChange.pipe(takeUntilDestroyed()).subscribe((event: LangChangeEvent) => {
|
||||
this.translator.language = event.lang as keyof SCTranslations<SCLanguage>;
|
||||
moment.locale(event.lang);
|
||||
});
|
||||
@@ -104,10 +101,4 @@ export class ThingTranslateService implements OnDestroy {
|
||||
|
||||
return this.getParsedResult(translatedPropertyNames, keyPath);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (!this.onLangChange.closed) {
|
||||
this.onLangChange.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,17 +12,18 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Injectable, OnDestroy, Pipe, PipeTransform} from '@angular/core';
|
||||
import {DestroyRef, inject, Injectable, Pipe, PipeTransform} from '@angular/core';
|
||||
import {TranslateService} from '@ngx-translate/core';
|
||||
import {get} from '@openstapps/collection-utils';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
@Injectable()
|
||||
@Pipe({
|
||||
name: 'translateSimple',
|
||||
pure: false,
|
||||
})
|
||||
export class TranslateSimplePipe implements PipeTransform, OnDestroy {
|
||||
export class TranslateSimplePipe implements PipeTransform {
|
||||
value: unknown;
|
||||
|
||||
query: unknown;
|
||||
@@ -31,6 +32,8 @@ export class TranslateSimplePipe implements PipeTransform, OnDestroy {
|
||||
|
||||
onLangChange: Subscription;
|
||||
|
||||
destroy$ = inject(DestroyRef);
|
||||
|
||||
constructor(private readonly translate: TranslateService) {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
@@ -58,14 +61,12 @@ export class TranslateSimplePipe implements PipeTransform, OnDestroy {
|
||||
|
||||
this.updateValue();
|
||||
|
||||
this.onLangChange ??= this.translate.onLangChange.subscribe(() => {
|
||||
this.updateValue();
|
||||
});
|
||||
this.onLangChange ??= this.translate.onLangChange
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe(() => {
|
||||
this.updateValue();
|
||||
});
|
||||
|
||||
return this.value as never;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.onLangChange?.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 StApps
|
||||
* Copyright (C) 2023 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
@@ -12,20 +12,29 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Directive, ElementRef, Host, Optional, Self, ViewContainerRef} from '@angular/core';
|
||||
import {
|
||||
DestroyRef,
|
||||
Directive,
|
||||
ElementRef,
|
||||
Host,
|
||||
inject,
|
||||
OnInit,
|
||||
Optional,
|
||||
Self,
|
||||
ViewContainerRef,
|
||||
} from '@angular/core';
|
||||
import {SCIcon} from './icon';
|
||||
import {IconReplacer} from './replace-util';
|
||||
import {TranslateService} from '@ngx-translate/core';
|
||||
import {Subscription} from 'rxjs';
|
||||
import {IonBackButton} from '@ionic/angular';
|
||||
import {TitleCasePipe} from '@angular/common';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
@Directive({
|
||||
selector: 'ion-back-button',
|
||||
})
|
||||
export class IonBackButtonDirective extends IconReplacer {
|
||||
private subscriptions: Subscription[] = [];
|
||||
export class IonBackButtonDirective extends IconReplacer implements OnInit {
|
||||
destroy$ = inject(DestroyRef);
|
||||
|
||||
constructor(
|
||||
element: ElementRef,
|
||||
@@ -45,17 +54,13 @@ export class IonBackButtonDirective extends IconReplacer {
|
||||
});
|
||||
}
|
||||
|
||||
init() {
|
||||
this.subscriptions.push(
|
||||
this.translateService.stream('back').subscribe((value: string) => {
|
||||
async ngOnInit() {
|
||||
await super.ngOnInit();
|
||||
this.translateService
|
||||
.stream('back')
|
||||
.pipe(takeUntilDestroyed(this.destroy$))
|
||||
.subscribe((value: string) => {
|
||||
this.ionBackButton.text = this.titleCasePipe.transform(value);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
for (const subscription of this.subscriptions) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Directive, ElementRef, ViewContainerRef} from '@angular/core';
|
||||
import {SCIcon} from './icon';
|
||||
import {IconReplacer} from './replace-util';
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Directive, ElementRef, ViewContainerRef} from '@angular/core';
|
||||
import {SCIcon} from './icon';
|
||||
import {IconReplacer} from './replace-util';
|
||||
|
||||
@@ -65,24 +65,7 @@ export abstract class IconReplacer implements OnInit, OnDestroy {
|
||||
*/
|
||||
abstract replace(): void;
|
||||
|
||||
/**
|
||||
* If any additional work needs to be done, this
|
||||
* is called during ngOnInit
|
||||
*/
|
||||
init() {
|
||||
// noop
|
||||
}
|
||||
|
||||
/**
|
||||
* If you need to do cleanup, this method is called during ngOnDestroy
|
||||
*/
|
||||
destroy() {
|
||||
// noop
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.init();
|
||||
|
||||
if (this.host) {
|
||||
this.attachObserver();
|
||||
} else {
|
||||
@@ -110,8 +93,8 @@ export abstract class IconReplacer implements OnInit, OnDestroy {
|
||||
scIcon.location.nativeElement.classList.add(...icon.classList);
|
||||
|
||||
if (this.iconDomLocation === 'shadow') {
|
||||
// shadow dom needs to utilize slotting, to put it outside
|
||||
// the shadow dom, otherwise it won't receive any css data
|
||||
// shadow dom needs to utilize slotting, to put it outside the shadow dom
|
||||
// otherwise it won't receive any css data
|
||||
const slot = document.createElement('slot');
|
||||
slot.name = this.slotName + slotName;
|
||||
icon.replaceWith(slot);
|
||||
@@ -137,6 +120,5 @@ export abstract class IconReplacer implements OnInit, OnDestroy {
|
||||
|
||||
ngOnDestroy() {
|
||||
this.mutationObserver?.disconnect();
|
||||
this.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
12
frontend/app/src/app/util/pause-when.ts
Normal file
12
frontend/app/src/app/util/pause-when.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import {filter, MonoTypeOperatorFunction, Observable, repeat, takeUntil} from 'rxjs';
|
||||
|
||||
/**
|
||||
* Pause the observable if the notifier emits true, and resume when it emits false
|
||||
*/
|
||||
export function pauseWhen<T>(notifier: Observable<boolean>): MonoTypeOperatorFunction<T> {
|
||||
return value =>
|
||||
value.pipe(
|
||||
takeUntil(notifier.pipe(filter(it => it))),
|
||||
repeat({delay: () => notifier.pipe(filter(it => !it))}),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user