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 {AssessmentsProvider} from '../assessments.provider';
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 {SCAssessment} from '@openstapps/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@@ -27,7 +27,7 @@ import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
templateUrl: 'assessments-detail.html',
styleUrls: ['assessments-detail.scss'],
})
export class AssessmentsDetailComponent implements ViewWillEnter, OnInit {
export class AssessmentsDetailComponent implements OnInit {
destroy$ = inject(DestroyRef);
constructor(
@@ -67,8 +67,4 @@ export class AssessmentsDetailComponent implements ViewWillEnter, OnInit {
event.resolve(this.item);
});
}
async ionViewWillEnter() {
await this.detailComponent.ionViewWillEnter();
}
}

View File

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

View File

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

View File

@@ -65,7 +65,7 @@ export class CatalogComponent implements OnInit {
.itemSelectListener()
.pipe(takeUntilDestroyed())
.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 -->
<a
[routerLink]="nextEvent ? ['/data-detail', nextEvent!.uid] : ['/schedule/calendar']"
[state]="{item: nextEvent}"
class="schedule-item-button"
>
<ion-label>{{ 'dashboard.schedule.title' | translate }}</ion-label>

View File

@@ -76,7 +76,7 @@ export class DashboardComponent implements OnInit, OnDestroy {
.itemSelectListener()
.pipe(takeUntilDestroyed())
.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
*ngFor="let dish of dishes"
[hideThumbnail]="true"
[favoriteButton]="false"
[item]="dish"
appearance="square"
></stapps-data-list-item>

View File

@@ -111,8 +111,8 @@ describe('DataDetailComponent', () => {
expect(DataDetailComponent.prototype.getItem).toHaveBeenCalledWith(sampleThing.uid, false);
});
it('should get a data item when the view is entered', () => {
comp.ionViewWillEnter();
it('should get a data item when initialized', () => {
comp.ngOnInit();
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
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component, ContentChild, EventEmitter, Input, Output, TemplateRef} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {ModalController, ViewWillEnter} from '@ionic/angular';
import {Component, ContentChild, EventEmitter, Input, OnInit, Output, TemplateRef} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {ModalController} from '@ionic/angular';
import {LangChangeEvent, TranslateService} from '@ngx-translate/core';
import {SCLanguageCode, SCSaveableThing, SCThings, SCUuid} from '@openstapps/core';
import {DataProvider, DataScope} from '../data.provider';
@@ -37,7 +37,7 @@ export interface ExternalDataLoadEvent {
styleUrls: ['data-detail.scss'],
templateUrl: 'data-detail.html',
})
export class DataDetailComponent implements ViewWillEnter {
export class DataDetailComponent implements OnInit {
/**
* The associated item
*
@@ -84,21 +84,19 @@ export class DataDetailComponent implements ViewWillEnter {
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(
protected readonly route: ActivatedRoute,
router: Router,
private readonly dataProvider: DataProvider,
private readonly favoritesService: FavoritesService,
readonly modalController: ModalController,
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;
translateService.onLangChange.subscribe((event: LangChangeEvent) => {
this.language = event.lang as SCLanguageCode;
@@ -138,10 +136,7 @@ export class DataDetailComponent implements ViewWillEnter {
}
}
/**
* Initialize
*/
async ionViewWillEnter() {
async ngOnInit() {
const uid = this.route.snapshot.paramMap.get('uid') || '';
await this.getItem(uid ?? '', false);
// fallback to the saved item (from favorites)

View File

@@ -342,7 +342,7 @@ export class SearchPageComponent implements OnInit {
.pipe(takeUntilDestroyed(this.destroy$))
.subscribe(item => {
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()
.pipe(takeUntilDestroyed(this.destroy$))
.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()
.pipe(takeUntilDestroyed())
.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()
.pipe(takeUntilDestroyed())
.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()
.pipe(takeUntilDestroyed(this.destroy$))
.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 => {
if (this.itemRouting) {
if ([SCThingType.Book, SCThingType.Periodical, SCThingType.Article].includes(item.type)) {
void this.router.navigate([
'hebis-detail',
(item.origin && 'originalId' in item.origin && item.origin['originalId']) || '',
]);
void this.router.navigate(
[
'hebis-detail',
(item.origin && 'originalId' in item.origin && item.origin['originalId']) || '',
],
{state: {item}},
);
} 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
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {TranslateService} from '@ngx-translate/core';
import {Component, inject, OnInit} from '@angular/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 {DaiaDataProvider} from '../daia-data.provider';
import {DaiaHolding} from '../protocol/response';
import {ModalController} from '@ionic/angular';
import {groupByStable} from '@openstapps/collection-utils';
/**
@@ -37,28 +32,10 @@ export class DaiaAvailabilityComponent extends DataDetailComponent implements On
holdingsByDepartments?: Map<DaiaHolding['department']['id'], DaiaHolding[]>;
/**
*
* @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);
}
private daiaDataProvider = inject(DaiaDataProvider);
/**
* Initialize
* @override
*/
async ngOnInit() {
const uid = this.route.snapshot.paramMap.get('uid');

View File

@@ -107,8 +107,8 @@ describe('HebisDetailComponent', () => {
expect(HebisDetailComponent.prototype.getItem).toHaveBeenCalledWith(sampleThing.uid, false);
});
it('should get a data item when the view is entered', () => {
comp.ionViewWillEnter();
it('should get a data item when initialized', () => {
comp.ngOnInit();
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
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {TranslateService} from '@ngx-translate/core';
import {Component, inject, OnInit} from '@angular/core';
import {SCUuid} from '@openstapps/core';
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 {DaiaHolding} from '../protocol/response';
import {ModalController} from '@ionic/angular';
/**
* A Component to display an SCThing detailed
@@ -31,33 +26,15 @@ import {ModalController} from '@ionic/angular';
styleUrls: ['hebis-detail.scss'],
templateUrl: 'hebis-detail.html',
})
export class HebisDetailComponent extends DataDetailComponent {
export class HebisDetailComponent extends DataDetailComponent implements OnInit {
holdings: DaiaHolding[];
/**
*
* @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);
}
private hebisDataProvider = inject(HebisDataProvider);
/**
* Initialize
* @override
*/
async ionViewWillEnter() {
async ngOnInit() {
const uid = this.route.snapshot.paramMap.get('uid') || '';
await this.getItem(uid ?? '', false);
}
@@ -68,9 +45,11 @@ export class HebisDetailComponent extends DataDetailComponent {
* @param _forceReload Ignore any cached data
*/
async getItem(uid: SCUuid, _forceReload: boolean) {
this.hebisDataProvider.hebisSearch({query: uid, page: 0}).then(result => {
// eslint-disable-next-line unicorn/no-null
this.item = (result.data && result.data[0]) || null;
});
this.item = await (this.inputItem ??
this.hebisDataProvider.hebisSearch({query: uid, page: 0}).then(
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/>.
*/
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 {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 {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 {DataProvider} from '../../data/data.provider';
/**
* 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',
templateUrl: 'hebis-search-page.html',
styleUrls: ['../../data/list/search-page.scss'],
providers: [{provide: DataProvider, useClass: HebisDataProvider}],
})
export class HebisSearchPageComponent extends SearchPageComponent implements OnInit {
/**
@@ -46,47 +40,6 @@ export class HebisSearchPageComponent extends SearchPageComponent implements OnI
*/
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
* @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;
}
return this.dataProvider.hebisSearch(searchOptions).then(
return (this.dataProvider as HebisDataProvider).hebisSearch(searchOptions).then(
async result => {
/*this.singleTypeResponse =
result.facets.find(facet => facet.field === 'type')?.buckets
@@ -176,10 +129,10 @@ export class HebisSearchPageComponent extends SearchPageComponent implements OnI
.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']) || '',
]);
void this.router.navigate(
['hebis-detail', (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) {
await Promise.all([this.modalController.dismiss(), this.showItem(item.uid)]);
} else {
void this.router.navigate(['/data-detail', item.uid]);
void this.router.navigate(['/data-detail', item.uid], {state: {item}});
}
});
this.positionService

View File

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

View File

@@ -15,7 +15,7 @@
<ion-grid>
<ion-row>
<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>
</a>