From dda1ee13e928f64ece17c3945c438c7aa2ebb931 Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Wed, 10 Feb 2021 17:03:39 +0100 Subject: [PATCH] MOBILE-3675 course: Migrate participants search --- src/core/classes/page-items-list-manager.ts | 10 ++ src/core/components/split-view/split-view.ts | 17 ++-- src/core/features/features.module.ts | 2 + .../user/pages/participants/participants.html | 41 ++++++-- .../user/pages/participants/participants.ts | 96 ++++++++++++++++--- .../features/user/user-course-lazy.module.ts | 2 + 6 files changed, 142 insertions(+), 26 deletions(-) diff --git a/src/core/classes/page-items-list-manager.ts b/src/core/classes/page-items-list-manager.ts index 4148f52cc..1c226eaa6 100644 --- a/src/core/classes/page-items-list-manager.ts +++ b/src/core/classes/page-items-list-manager.ts @@ -95,6 +95,16 @@ export abstract class CorePageItemsListManager { this.updateSelectedItem(splitView.outletRoute); } + /** + * Reset items data. + */ + resetItems(): void { + this.itemsList = null; + this.itemsMap = null; + this.hasMoreItems = true; + this.selectedItem = null; + } + // @todo Implement watchResize. /** diff --git a/src/core/components/split-view/split-view.ts b/src/core/components/split-view/split-view.ts index 75868e2d1..870db7056 100644 --- a/src/core/components/split-view/split-view.ts +++ b/src/core/components/split-view/split-view.ts @@ -14,7 +14,7 @@ import { AfterViewInit, Component, ElementRef, HostBinding, Input, OnDestroy, ViewChild } from '@angular/core'; import { ActivatedRouteSnapshot } from '@angular/router'; -import { IonRouterOutlet } from '@ionic/angular'; +import { IonContent, IonRouterOutlet } from '@ionic/angular'; import { CoreScreen } from '@services/screen'; import { BehaviorSubject, Observable, Subscription } from 'rxjs'; @@ -31,7 +31,8 @@ export enum CoreSplitViewMode { }) export class CoreSplitViewComponent implements AfterViewInit, OnDestroy { - @ViewChild(IonRouterOutlet) outlet!: IonRouterOutlet; + @ViewChild(IonContent) menuContent!: IonContent; + @ViewChild(IonRouterOutlet) contentOutlet!: IonRouterOutlet; @HostBinding('class') classes = ''; @Input() placeholderText = 'core.emptysplit'; @Input() mode?: CoreSplitViewMode; @@ -56,11 +57,11 @@ export class CoreSplitViewComponent implements AfterViewInit, OnDestroy { ngAfterViewInit(): void { this.isNested = !!this.element.nativeElement.parentElement?.closest('core-split-view'); this.subscriptions = [ - this.outlet.activateEvents.subscribe(() => { + this.contentOutlet.activateEvents.subscribe(() => { this.updateClasses(); - this.outletRouteSubject.next(this.outlet.activatedRoute.snapshot); + this.outletRouteSubject.next(this.contentOutlet.activatedRoute.snapshot); }), - this.outlet.deactivateEvents.subscribe(() => { + this.contentOutlet.deactivateEvents.subscribe(() => { this.updateClasses(); this.outletRouteSubject.next(null); }), @@ -83,7 +84,7 @@ export class CoreSplitViewComponent implements AfterViewInit, OnDestroy { private updateClasses(): void { const classes: string[] = [this.getCurrentMode()]; - if (this.outlet.isActivated) { + if (this.contentOutlet.isActivated) { classes.push('outlet-activated'); } @@ -110,7 +111,7 @@ export class CoreSplitViewComponent implements AfterViewInit, OnDestroy { } if (CoreScreen.instance.isMobile) { - return this.outlet.isActivated + return this.contentOutlet.isActivated ? CoreSplitViewMode.ContentOnly : CoreSplitViewMode.MenuOnly; } @@ -124,7 +125,7 @@ export class CoreSplitViewComponent implements AfterViewInit, OnDestroy { * @return If split view is enabled. */ isOn(): boolean { - return this.outlet.isActivated; + return this.contentOutlet.isActivated; } } diff --git a/src/core/features/features.module.ts b/src/core/features/features.module.ts index a3e29dea8..d3692a017 100644 --- a/src/core/features/features.module.ts +++ b/src/core/features/features.module.ts @@ -29,6 +29,7 @@ import { CoreUserModule } from './user/user.module'; import { CorePushNotificationsModule } from './pushnotifications/pushnotifications.module'; import { CoreXAPIModule } from './xapi/xapi.module'; import { CoreViewerModule } from './viewer/viewer.module'; +import { CoreSearchModule } from './search/search.module'; @NgModule({ imports: [ @@ -44,6 +45,7 @@ import { CoreViewerModule } from './viewer/viewer.module'; CoreTagModule, CoreUserModule, CorePushNotificationsModule, + CoreSearchModule, CoreXAPIModule, CoreH5PModule, CoreViewerModule, diff --git a/src/core/features/user/pages/participants/participants.html b/src/core/features/user/pages/participants/participants.html index a0fc5140e..8d1fb6c47 100644 --- a/src/core/features/user/pages/participants/participants.html +++ b/src/core/features/user/pages/participants/participants.html @@ -1,21 +1,50 @@ + + + + + + - + + + + - + + + + + - + + + -

- -

+ +

{{ participant.fullname }}

+

{{ 'core.lastaccess' | translate }}: {{ participant.lastcourseaccess | coreTimeAgo }}

+

{{ 'core.lastaccess' | translate }}: {{ participant.lastaccess | coreTimeAgo }}

+
+ + +

+ +

+
+
diff --git a/src/core/features/user/pages/participants/participants.ts b/src/core/features/user/pages/participants/participants.ts index 9ebca2847..7ec5932b1 100644 --- a/src/core/features/user/pages/participants/participants.ts +++ b/src/core/features/user/pages/participants/participants.ts @@ -13,15 +13,16 @@ // limitations under the License. import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router'; -import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { IonRefresher } from '@ionic/angular'; +import { CoreApp } from '@services/app'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreNavigator } from '@services/navigator'; import { CorePageItemsListManager } from '@classes/page-items-list-manager'; import { CoreScreen } from '@services/screen'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; -import { CoreUser, CoreUserParticipant } from '@features/user/services/user'; +import { CoreUser, CoreUserProvider, CoreUserParticipant, CoreUserData } from '@features/user/services/user'; import { CoreUtils } from '@services/utils/utils'; /** @@ -31,9 +32,13 @@ import { CoreUtils } from '@services/utils/utils'; selector: 'page-core-user-participants', templateUrl: 'participants.html', }) -export class CoreUserParticipantsPage implements AfterViewInit, OnDestroy { +export class CoreUserParticipantsPage implements OnInit, AfterViewInit, OnDestroy { participants: CoreUserParticipantsManager; + searchQuery: string | null = null; + searchInProgress = false; + searchEnabled = false; + showSearchBox = false; fetchMoreParticipantsFailed = false; @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent; @@ -44,6 +49,13 @@ export class CoreUserParticipantsPage implements AfterViewInit, OnDestroy { this.participants = new CoreUserParticipantsManager(CoreUserParticipantsPage, courseId); } + /** + * @inheritdoc + */ + async ngOnInit(): Promise { + this.searchEnabled = await CoreUser.instance.canSearchParticipantsInSite(); + } + /** * @inheritdoc */ @@ -61,6 +73,53 @@ export class CoreUserParticipantsPage implements AfterViewInit, OnDestroy { this.participants.destroy(); } + /** + * Show or hide search box. + */ + toggleSearch(): void { + this.showSearchBox = !this.showSearchBox; + + if (this.showSearchBox) { + // Make search bar visible. + this.splitView.menuContent.scrollToTop(); + } else { + this.clearSearch(); + } + } + + /** + * Clear search. + */ + async clearSearch(): Promise { + if (this.searchQuery === null) { + // Nothing to clear. + return; + } + + this.searchQuery = null; + this.searchInProgress = false; + this.participants.resetItems(); + + await this.fetchInitialParticipants(); + } + + /** + * Start a new search. + * + * @param query Text to search for. + */ + async search(query: string): Promise { + CoreApp.instance.closeKeyboard(); + + this.searchInProgress = true; + this.searchQuery = query; + this.participants.resetItems(); + + await this.fetchInitialParticipants(); + + this.searchInProgress = false; + } + /** * Refresh participants. * @@ -108,13 +167,26 @@ export class CoreUserParticipantsPage implements AfterViewInit, OnDestroy { * * @param loadedParticipants Participants list to continue loading from. */ - private async fetchParticipants(loadedParticipants: CoreUserParticipant[] = []): Promise { - const { participants, canLoadMore } = await CoreUser.instance.getParticipants( - this.participants.courseId, - loadedParticipants.length, - ); + private async fetchParticipants(loadedParticipants: CoreUserParticipant[] | CoreUserData[] = []): Promise { + if (this.searchQuery) { + const { participants, canLoadMore } = await CoreUser.instance.searchParticipants( + this.participants.courseId, + this.searchQuery, + true, + Math.ceil(loadedParticipants.length / CoreUserProvider.PARTICIPANTS_LIST_LIMIT), + CoreUserProvider.PARTICIPANTS_LIST_LIMIT, + ); + + this.participants.setItems((loadedParticipants as CoreUserData[]).concat(participants), canLoadMore); + } else { + const { participants, canLoadMore } = await CoreUser.instance.getParticipants( + this.participants.courseId, + loadedParticipants.length, + ); + + this.participants.setItems((loadedParticipants as CoreUserParticipant[]).concat(participants), canLoadMore); + } - this.participants.setItems(loadedParticipants.concat(participants), canLoadMore); this.fetchMoreParticipantsFailed = false; } @@ -123,7 +195,7 @@ export class CoreUserParticipantsPage implements AfterViewInit, OnDestroy { /** * Helper to manage the list of participants. */ -class CoreUserParticipantsManager extends CorePageItemsListManager { +class CoreUserParticipantsManager extends CorePageItemsListManager { courseId: number; @@ -136,7 +208,7 @@ class CoreUserParticipantsManager extends CorePageItemsListManager { + async select(participant: CoreUserParticipant | CoreUserData): Promise { if (CoreScreen.instance.isMobile) { await CoreNavigator.instance.navigateToSitePath('/user/profile', { params: { userId: participant.id } }); @@ -149,7 +221,7 @@ class CoreUserParticipantsManager extends CorePageItemsListManager