feat: share loaded data to detail views across routes

This commit is contained in:
2023-09-06 14:35:04 +02:00
parent bd09b36620
commit f2c4ee308f
23 changed files with 64 additions and 153 deletions

View File

@@ -0,0 +1,5 @@
---
'@openstapps/app': minor
---
Detail views now won't load data again if it is being navigated to from a list item

View File

@@ -17,7 +17,7 @@ import {Component, DestroyRef, inject, Input, OnInit, ViewChild} from '@angular/
import {ActivatedRoute} from '@angular/router'; import {ActivatedRoute} from '@angular/router';
import {AssessmentsProvider} from '../assessments.provider'; import {AssessmentsProvider} from '../assessments.provider';
import {DataDetailComponent, ExternalDataLoadEvent} from '../../data/detail/data-detail.component'; import {DataDetailComponent, ExternalDataLoadEvent} from '../../data/detail/data-detail.component';
import {NavController, ViewWillEnter} from '@ionic/angular'; import {NavController} from '@ionic/angular';
import {DataRoutingService} from '../../data/data-routing.service'; import {DataRoutingService} from '../../data/data-routing.service';
import {SCAssessment} from '@openstapps/core'; import {SCAssessment} from '@openstapps/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@@ -27,7 +27,7 @@ import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
templateUrl: 'assessments-detail.html', templateUrl: 'assessments-detail.html',
styleUrls: ['assessments-detail.scss'], styleUrls: ['assessments-detail.scss'],
}) })
export class AssessmentsDetailComponent implements ViewWillEnter, OnInit { export class AssessmentsDetailComponent implements OnInit {
destroy$ = inject(DestroyRef); destroy$ = inject(DestroyRef);
constructor( constructor(
@@ -67,8 +67,4 @@ export class AssessmentsDetailComponent implements ViewWillEnter, OnInit {
event.resolve(this.item); event.resolve(this.item);
}); });
} }
async ionViewWillEnter() {
await this.detailComponent.ionViewWillEnter();
}
} }

View File

@@ -61,6 +61,7 @@ export class AssessmentsSimpleDataListComponent implements OnInit {
queryParams: { queryParams: {
token: this.activatedRoute.snapshot.queryParamMap.get('token'), token: this.activatedRoute.snapshot.queryParamMap.get('token'),
}, },
state: {item: thing},
}); });
}); });
} }

View File

@@ -71,6 +71,7 @@ export class AssessmentsPageComponent implements OnInit, AfterViewInit {
queryParams: { queryParams: {
token: this.activatedRoute.snapshot.queryParamMap.get('token'), token: this.activatedRoute.snapshot.queryParamMap.get('token'),
}, },
state: {item: thing},
}); });
}); });

View File

@@ -65,7 +65,7 @@ export class CatalogComponent implements OnInit {
.itemSelectListener() .itemSelectListener()
.pipe(takeUntilDestroyed()) .pipe(takeUntilDestroyed())
.subscribe(item => { .subscribe(item => {
void this.router.navigate(['data-detail', item.uid]); void this.router.navigate(['data-detail', item.uid], {state: {item}});
}); });
} }

View File

@@ -26,6 +26,7 @@
<!-- Avoid structural directives here, they might interfere with the collapse animation --> <!-- Avoid structural directives here, they might interfere with the collapse animation -->
<a <a
[routerLink]="nextEvent ? ['/data-detail', nextEvent!.uid] : ['/schedule/calendar']" [routerLink]="nextEvent ? ['/data-detail', nextEvent!.uid] : ['/schedule/calendar']"
[state]="{item: nextEvent}"
class="schedule-item-button" class="schedule-item-button"
> >
<ion-label>{{ 'dashboard.schedule.title' | translate }}</ion-label> <ion-label>{{ 'dashboard.schedule.title' | translate }}</ion-label>

View File

@@ -76,7 +76,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
.itemSelectListener() .itemSelectListener()
.pipe(takeUntilDestroyed()) .pipe(takeUntilDestroyed())
.subscribe(item => { .subscribe(item => {
void this.router.navigate(['data-detail', item.uid]); void this.router.navigate(['data-detail', item.uid], {state: {item}});
}); });
} }

View File

@@ -18,7 +18,6 @@
<stapps-data-list-item <stapps-data-list-item
*ngFor="let dish of dishes" *ngFor="let dish of dishes"
[hideThumbnail]="true" [hideThumbnail]="true"
[favoriteButton]="false"
[item]="dish" [item]="dish"
appearance="square" appearance="square"
></stapps-data-list-item> ></stapps-data-list-item>

View File

@@ -111,8 +111,8 @@ describe('DataDetailComponent', () => {
expect(DataDetailComponent.prototype.getItem).toHaveBeenCalledWith(sampleThing.uid, false); expect(DataDetailComponent.prototype.getItem).toHaveBeenCalledWith(sampleThing.uid, false);
}); });
it('should get a data item when the view is entered', () => { it('should get a data item when initialized', () => {
comp.ionViewWillEnter(); comp.ngOnInit();
expect(DataDetailComponent.prototype.getItem).toHaveBeenCalledWith(sampleThing.uid, false); expect(DataDetailComponent.prototype.getItem).toHaveBeenCalledWith(sampleThing.uid, false);
}); });
}); });

View File

@@ -12,9 +12,9 @@
* You should have received a copy of the GNU General Public License along with * You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {Component, ContentChild, EventEmitter, Input, Output, TemplateRef} from '@angular/core'; import {Component, ContentChild, EventEmitter, Input, OnInit, Output, TemplateRef} from '@angular/core';
import {ActivatedRoute} from '@angular/router'; import {ActivatedRoute, Router} from '@angular/router';
import {ModalController, ViewWillEnter} from '@ionic/angular'; import {ModalController} from '@ionic/angular';
import {LangChangeEvent, TranslateService} from '@ngx-translate/core'; import {LangChangeEvent, TranslateService} from '@ngx-translate/core';
import {SCLanguageCode, SCSaveableThing, SCThings, SCUuid} from '@openstapps/core'; import {SCLanguageCode, SCSaveableThing, SCThings, SCUuid} from '@openstapps/core';
import {DataProvider, DataScope} from '../data.provider'; import {DataProvider, DataScope} from '../data.provider';
@@ -37,7 +37,7 @@ export interface ExternalDataLoadEvent {
styleUrls: ['data-detail.scss'], styleUrls: ['data-detail.scss'],
templateUrl: 'data-detail.html', templateUrl: 'data-detail.html',
}) })
export class DataDetailComponent implements ViewWillEnter { export class DataDetailComponent implements OnInit {
/** /**
* The associated item * The associated item
* *
@@ -84,21 +84,19 @@ export class DataDetailComponent implements ViewWillEnter {
return (thing as SCSaveableThing).data !== undefined; return (thing as SCSaveableThing).data !== undefined;
} }
/**
*
* @param route the route the page was accessed from
* @param dataProvider the data provider
* @param favoritesService the favorites provider
* @param modalController the modal controller
* @param translateService he translate provider
*/
constructor( constructor(
protected readonly route: ActivatedRoute, protected readonly route: ActivatedRoute,
router: Router,
private readonly dataProvider: DataProvider, private readonly dataProvider: DataProvider,
private readonly favoritesService: FavoritesService, private readonly favoritesService: FavoritesService,
readonly modalController: ModalController, readonly modalController: ModalController,
translateService: TranslateService, translateService: TranslateService,
) { ) {
this.inputItem = router.getCurrentNavigation()?.extras.state?.item;
if (!this.inputItem) {
// TODO: remove
console.warn('Did you forget to pass a state?');
}
this.language = translateService.currentLang as SCLanguageCode; this.language = translateService.currentLang as SCLanguageCode;
translateService.onLangChange.subscribe((event: LangChangeEvent) => { translateService.onLangChange.subscribe((event: LangChangeEvent) => {
this.language = event.lang as SCLanguageCode; this.language = event.lang as SCLanguageCode;
@@ -138,10 +136,7 @@ export class DataDetailComponent implements ViewWillEnter {
} }
} }
/** async ngOnInit() {
* Initialize
*/
async ionViewWillEnter() {
const uid = this.route.snapshot.paramMap.get('uid') || ''; const uid = this.route.snapshot.paramMap.get('uid') || '';
await this.getItem(uid ?? '', false); await this.getItem(uid ?? '', false);
// fallback to the saved item (from favorites) // fallback to the saved item (from favorites)

View File

@@ -342,7 +342,7 @@ export class SearchPageComponent implements OnInit {
.pipe(takeUntilDestroyed(this.destroy$)) .pipe(takeUntilDestroyed(this.destroy$))
.subscribe(item => { .subscribe(item => {
if (this.itemRouting) { if (this.itemRouting) {
void this.router.navigate(['/data-detail', item.uid]); void this.router.navigate(['/data-detail', item.uid], {state: {item}});
} }
}); });
} }

View File

@@ -61,7 +61,7 @@ export class SimpleDataListComponent implements OnInit {
.itemSelectListener() .itemSelectListener()
.pipe(takeUntilDestroyed(this.destroy$)) .pipe(takeUntilDestroyed(this.destroy$))
.subscribe(item => { .subscribe(item => {
void this.router.navigate(['/data-detail', item.uid]); void this.router.navigate(['/data-detail', item.uid], {state: {item}});
}); });
} }
} }

View File

@@ -40,7 +40,7 @@ export class DateSeriesDetailContentComponent implements OnInit {
.itemSelectListener() .itemSelectListener()
.pipe(takeUntilDestroyed()) .pipe(takeUntilDestroyed())
.subscribe(item => { .subscribe(item => {
void router.navigate(['/data-detail', item.uid]); void router.navigate(['/data-detail', item.uid], {state: {item}});
}); });
} }

View File

@@ -59,7 +59,7 @@ export class PlaceDetailContentComponent implements OnInit {
.itemSelectListener() .itemSelectListener()
.pipe(takeUntilDestroyed()) .pipe(takeUntilDestroyed())
.subscribe(item => { .subscribe(item => {
void router.navigate(['/data-detail', item.uid]); void router.navigate(['/data-detail', item.uid], {state: {item}});
}); });
} }

View File

@@ -75,7 +75,7 @@ export class PlaceMensaDetailComponent implements AfterViewInit {
.itemSelectListener() .itemSelectListener()
.pipe(takeUntilDestroyed(this.destroy$)) .pipe(takeUntilDestroyed(this.destroy$))
.subscribe(item => { .subscribe(item => {
void this.router.navigate(['/data-detail', item.uid]); void this.router.navigate(['/data-detail', item.uid], {state: {item}});
}); });
} }

View File

@@ -112,12 +112,15 @@ export class FavoritesPageComponent extends SearchPageComponent implements OnIni
.subscribe(item => { .subscribe(item => {
if (this.itemRouting) { if (this.itemRouting) {
if ([SCThingType.Book, SCThingType.Periodical, SCThingType.Article].includes(item.type)) { if ([SCThingType.Book, SCThingType.Periodical, SCThingType.Article].includes(item.type)) {
void this.router.navigate([ void this.router.navigate(
'hebis-detail', [
(item.origin && 'originalId' in item.origin && item.origin['originalId']) || '', 'hebis-detail',
]); (item.origin && 'originalId' in item.origin && item.origin['originalId']) || '',
],
{state: {item}},
);
} else { } else {
void this.router.navigate(['data-detail', item.uid]); void this.router.navigate(['data-detail', item.uid], {state: {item}});
} }
} }
}); });

View File

@@ -12,16 +12,11 @@
* You should have received a copy of the GNU General Public License along with * You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {Component, OnInit} from '@angular/core'; import {Component, inject, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {TranslateService} from '@ngx-translate/core';
import {SCUuid} from '@openstapps/core'; import {SCUuid} from '@openstapps/core';
import {FavoritesService} from '../../favorites/favorites.service';
import {DataProvider} from '../../data/data.provider';
import {DataDetailComponent} from '../../data/detail/data-detail.component'; import {DataDetailComponent} from '../../data/detail/data-detail.component';
import {DaiaDataProvider} from '../daia-data.provider'; import {DaiaDataProvider} from '../daia-data.provider';
import {DaiaHolding} from '../protocol/response'; import {DaiaHolding} from '../protocol/response';
import {ModalController} from '@ionic/angular';
import {groupByStable} from '@openstapps/collection-utils'; import {groupByStable} from '@openstapps/collection-utils';
/** /**
@@ -37,28 +32,10 @@ export class DaiaAvailabilityComponent extends DataDetailComponent implements On
holdingsByDepartments?: Map<DaiaHolding['department']['id'], DaiaHolding[]>; holdingsByDepartments?: Map<DaiaHolding['department']['id'], DaiaHolding[]>;
/** private daiaDataProvider = inject(DaiaDataProvider);
*
* @param route the route the page was accessed from
* @param dataProvider the data provider
* @param favoritesService the favorites provider
* @param modalController the modal controller
* @param translateService he translate provider
* @param daiaDataProvider DaiaDataProvider
*/
constructor(
route: ActivatedRoute,
dataProvider: DataProvider,
favoritesService: FavoritesService,
modalController: ModalController,
translateService: TranslateService,
private daiaDataProvider: DaiaDataProvider,
) {
super(route, dataProvider, favoritesService, modalController, translateService);
}
/** /**
* Initialize * @override
*/ */
async ngOnInit() { async ngOnInit() {
const uid = this.route.snapshot.paramMap.get('uid'); const uid = this.route.snapshot.paramMap.get('uid');

View File

@@ -107,8 +107,8 @@ describe('HebisDetailComponent', () => {
expect(HebisDetailComponent.prototype.getItem).toHaveBeenCalledWith(sampleThing.uid, false); expect(HebisDetailComponent.prototype.getItem).toHaveBeenCalledWith(sampleThing.uid, false);
}); });
it('should get a data item when the view is entered', () => { it('should get a data item when initialized', () => {
comp.ionViewWillEnter(); comp.ngOnInit();
expect(HebisDetailComponent.prototype.getItem).toHaveBeenCalledWith(sampleThing.uid, false); expect(HebisDetailComponent.prototype.getItem).toHaveBeenCalledWith(sampleThing.uid, false);
}); });
}); });

View File

@@ -12,16 +12,11 @@
* You should have received a copy of the GNU General Public License along with * You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {Component} from '@angular/core'; import {Component, inject, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {TranslateService} from '@ngx-translate/core';
import {SCUuid} from '@openstapps/core'; import {SCUuid} from '@openstapps/core';
import {HebisDataProvider} from '../hebis-data.provider'; import {HebisDataProvider} from '../hebis-data.provider';
import {FavoritesService} from '../../favorites/favorites.service';
import {DataProvider} from '../../data/data.provider';
import {DataDetailComponent} from '../../data/detail/data-detail.component'; import {DataDetailComponent} from '../../data/detail/data-detail.component';
import {DaiaHolding} from '../protocol/response'; import {DaiaHolding} from '../protocol/response';
import {ModalController} from '@ionic/angular';
/** /**
* A Component to display an SCThing detailed * A Component to display an SCThing detailed
@@ -31,33 +26,15 @@ import {ModalController} from '@ionic/angular';
styleUrls: ['hebis-detail.scss'], styleUrls: ['hebis-detail.scss'],
templateUrl: 'hebis-detail.html', templateUrl: 'hebis-detail.html',
}) })
export class HebisDetailComponent extends DataDetailComponent { export class HebisDetailComponent extends DataDetailComponent implements OnInit {
holdings: DaiaHolding[]; holdings: DaiaHolding[];
/** private hebisDataProvider = inject(HebisDataProvider);
*
* @param route the route the page was accessed from
* @param dataProvider the data provider
* @param favoritesService the favorites provider
* @param modalController the modal controller
* @param translateService he translate provider
* @param hebisDataProvider HebisDataProvider
*/
constructor(
route: ActivatedRoute,
dataProvider: DataProvider,
favoritesService: FavoritesService,
modalController: ModalController,
translateService: TranslateService,
private hebisDataProvider: HebisDataProvider,
) {
super(route, dataProvider, favoritesService, modalController, translateService);
}
/** /**
* Initialize * @override
*/ */
async ionViewWillEnter() { async ngOnInit() {
const uid = this.route.snapshot.paramMap.get('uid') || ''; const uid = this.route.snapshot.paramMap.get('uid') || '';
await this.getItem(uid ?? '', false); await this.getItem(uid ?? '', false);
} }
@@ -68,9 +45,11 @@ export class HebisDetailComponent extends DataDetailComponent {
* @param _forceReload Ignore any cached data * @param _forceReload Ignore any cached data
*/ */
async getItem(uid: SCUuid, _forceReload: boolean) { async getItem(uid: SCUuid, _forceReload: boolean) {
this.hebisDataProvider.hebisSearch({query: uid, page: 0}).then(result => { this.item = await (this.inputItem ??
// eslint-disable-next-line unicorn/no-null this.hebisDataProvider.hebisSearch({query: uid, page: 0}).then(
this.item = (result.data && result.data[0]) || null; result =>
}); // eslint-disable-next-line unicorn/no-null
(result.data && result.data[0]) || null,
));
} }
} }

View File

@@ -13,19 +13,12 @@
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {Component, Input, 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';
import {combineLatest} from 'rxjs'; import {combineLatest} from 'rxjs';
import {debounceTime, distinctUntilChanged, startWith} from 'rxjs/operators'; import {debounceTime, distinctUntilChanged, startWith} from 'rxjs/operators';
import {ContextMenuService} from '../../menu/context/context-menu.service';
import {SettingsProvider} from '../../settings/settings.provider';
import {DataRoutingService} from '../../data/data-routing.service';
import {SearchPageComponent} from '../../data/list/search-page.component'; import {SearchPageComponent} from '../../data/list/search-page.component';
import {HebisDataProvider} from '../hebis-data.provider'; 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'; import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {DataProvider} from '../../data/data.provider';
/** /**
* HebisSearchPageComponent queries things and shows list of things as search results and filter as context menu * HebisSearchPageComponent queries things and shows list of things as search results and filter as context menu
@@ -34,6 +27,7 @@ import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
selector: 'stapps-hebissearch-page', selector: 'stapps-hebissearch-page',
templateUrl: 'hebis-search-page.html', templateUrl: 'hebis-search-page.html',
styleUrls: ['../../data/list/search-page.scss'], styleUrls: ['../../data/list/search-page.scss'],
providers: [{provide: DataProvider, useClass: HebisDataProvider}],
}) })
export class HebisSearchPageComponent extends SearchPageComponent implements OnInit { export class HebisSearchPageComponent extends SearchPageComponent implements OnInit {
/** /**
@@ -46,47 +40,6 @@ export class HebisSearchPageComponent extends SearchPageComponent implements OnI
*/ */
page = 0; page = 0;
/**
* Injects the providers and creates subscriptions
* @param alertController AlertController
* @param dataProvider HebisProvider
* @param contextMenuService ContextMenuService
* @param settingsProvider SettingsProvider
* @param logger An angular logger
* @param dataRoutingService DataRoutingService
* @param router Router
* @param route Active Route
* @param positionService PositionService
* @param configProvider ConfigProvider
*/
constructor(
protected readonly alertController: AlertController,
protected dataProvider: HebisDataProvider,
protected readonly contextMenuService: ContextMenuService,
protected readonly settingsProvider: SettingsProvider,
protected readonly logger: NGXLogger,
protected dataRoutingService: DataRoutingService,
protected router: Router,
route: ActivatedRoute,
protected positionService: PositionService,
configProvider: ConfigProvider,
animationController: AnimationController,
) {
super(
alertController,
dataProvider,
contextMenuService,
settingsProvider,
logger,
dataRoutingService,
router,
route,
positionService,
configProvider,
animationController,
);
}
/** /**
* Fetches items with set query configuration * Fetches items with set query configuration
* @param append If true fetched data gets appended to existing, override otherwise (default false) * @param append If true fetched data gets appended to existing, override otherwise (default false)
@@ -103,7 +56,7 @@ export class HebisSearchPageComponent extends SearchPageComponent implements OnI
searchOptions.query = this.queryText; searchOptions.query = this.queryText;
} }
return this.dataProvider.hebisSearch(searchOptions).then( return (this.dataProvider as HebisDataProvider).hebisSearch(searchOptions).then(
async result => { async result => {
/*this.singleTypeResponse = /*this.singleTypeResponse =
result.facets.find(facet => facet.field === 'type')?.buckets result.facets.find(facet => facet.field === 'type')?.buckets
@@ -176,10 +129,10 @@ export class HebisSearchPageComponent extends SearchPageComponent implements OnI
.pipe(takeUntilDestroyed(this.destroy$)) .pipe(takeUntilDestroyed(this.destroy$))
.subscribe(async item => { .subscribe(async item => {
if (this.itemRouting) { if (this.itemRouting) {
void this.router.navigate([ void this.router.navigate(
'hebis-detail', ['hebis-detail', (item.origin && 'originalId' in item.origin && item.origin['originalId']) || ''],
(item.origin && 'originalId' in item.origin && item.origin['originalId']) || '', {state: {item}},
]); );
} }
}); });
} }

View File

@@ -158,7 +158,7 @@ export class MapPageComponent implements OnInit {
if (this.items.length > 1) { if (this.items.length > 1) {
await Promise.all([this.modalController.dismiss(), this.showItem(item.uid)]); await Promise.all([this.modalController.dismiss(), this.showItem(item.uid)]);
} else { } else {
void this.router.navigate(['/data-detail', item.uid]); void this.router.navigate(['/data-detail', item.uid], {state: {item}});
} }
}); });
this.positionService this.positionService

View File

@@ -15,6 +15,7 @@
<ion-card <ion-card
[routerLink]="['/data-detail', item.uid]" [routerLink]="['/data-detail', item.uid]"
[state]="{item}"
class="card" class="card"
[style.--background]="item.image ? 'url(' + item.image + ')' : undefined" [style.--background]="item.image ? 'url(' + item.image + ')' : undefined"
> >

View File

@@ -15,7 +15,7 @@
<ion-grid> <ion-grid>
<ion-row> <ion-row>
<ion-col> <ion-col>
<a *ngIf="item; else titleTemplate" [routerLink]="['/data-detail', item.uid]"> <a *ngIf="item; else titleTemplate" [routerLink]="['/data-detail', item.uid]" [state]="{item}">
<ng-container *ngTemplateOutlet="titleTemplate"></ng-container> <ng-container *ngTemplateOutlet="titleTemplate"></ng-container>
</a> </a>