feat: use takeUntilDestroy instead of manually unsubscribing

This commit is contained in:
Thea Schöbl
2023-07-26 16:56:41 +00:00
committed by Rainer Killinger
parent bebee6b4d0
commit 06f3120345
37 changed files with 476 additions and 713 deletions

View File

@@ -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) {

View File

@@ -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();
});
}
}

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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();
}
}

View File

@@ -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;
}
}
});
});
}
}

View File

@@ -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();
});
}
}

View File

@@ -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);
});
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
});
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}
}

View File

@@ -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]);
}
}
}),
);
});
}
/**

View File

@@ -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();
}
});
}
}

View File

@@ -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);
}

View File

@@ -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'});
});
});

View File

@@ -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});
}
}
}

View File

@@ -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
*/

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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) {

View File

@@ -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
*/

View File

@@ -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

View File

@@ -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();
});

View File

@@ -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.
*/

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
});
}
}

View File

@@ -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';

View File

@@ -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';

View File

@@ -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();
}
}

View 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))}),
);
}