forked from EVOgeek/Vmeda.Online
		
	
						commit
						f9ea74067d
					
				@ -1,5 +1,3 @@
 | 
			
		||||
<ion-app>
 | 
			
		||||
    <!-- @todo move /login/init UI here -->
 | 
			
		||||
 | 
			
		||||
    <ion-router-outlet></ion-router-outlet>
 | 
			
		||||
</ion-app>
 | 
			
		||||
 | 
			
		||||
@ -27,6 +27,7 @@ export abstract class CorePageItemsListManager<Item> {
 | 
			
		||||
 | 
			
		||||
    protected itemsList: Item[] | null = null;
 | 
			
		||||
    protected itemsMap: Record<string, Item> | null = null;
 | 
			
		||||
    protected hasMoreItems = true;
 | 
			
		||||
    protected selectedItem: Item | null = null;
 | 
			
		||||
    protected pageComponent: unknown;
 | 
			
		||||
    protected splitView?: CoreSplitViewComponent;
 | 
			
		||||
@ -44,6 +45,10 @@ export abstract class CorePageItemsListManager<Item> {
 | 
			
		||||
        return this.itemsMap !== null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get completed(): boolean {
 | 
			
		||||
        return !this.hasMoreItems;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get empty(): boolean {
 | 
			
		||||
        return this.itemsList === null || this.itemsList.length === 0;
 | 
			
		||||
    }
 | 
			
		||||
@ -90,6 +95,16 @@ export abstract class CorePageItemsListManager<Item> {
 | 
			
		||||
        this.updateSelectedItem(splitView.outletRoute);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Reset items data.
 | 
			
		||||
     */
 | 
			
		||||
    resetItems(): void {
 | 
			
		||||
        this.itemsList = null;
 | 
			
		||||
        this.itemsMap = null;
 | 
			
		||||
        this.hasMoreItems = true;
 | 
			
		||||
        this.selectedItem = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // @todo Implement watchResize.
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -133,8 +148,10 @@ export abstract class CorePageItemsListManager<Item> {
 | 
			
		||||
     * Set the list of items.
 | 
			
		||||
     *
 | 
			
		||||
     * @param items Items.
 | 
			
		||||
     * @param hasMoreItems Whether the list has more items that haven't been loaded.
 | 
			
		||||
     */
 | 
			
		||||
    setItems(items: Item[]): void {
 | 
			
		||||
    setItems(items: Item[], hasMoreItems: boolean = false): void {
 | 
			
		||||
        this.hasMoreItems = hasMoreItems;
 | 
			
		||||
        this.itemsList = items.slice(0);
 | 
			
		||||
        this.itemsMap = items.reduce((map, item) => {
 | 
			
		||||
            map[this.getItemPath(item)] = item;
 | 
			
		||||
 | 
			
		||||
@ -520,12 +520,12 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async selectByIndex(index: number, e?: Event): Promise<void> {
 | 
			
		||||
        if (index < 0 || index >= this.tabs.length) {
 | 
			
		||||
            if (this.selected) {
 | 
			
		||||
                // Invalid index do not change tab.
 | 
			
		||||
        e?.preventDefault();
 | 
			
		||||
        e?.stopPropagation();
 | 
			
		||||
 | 
			
		||||
        if (index < 0 || index >= this.tabs.length) {
 | 
			
		||||
            if (this.selected) {
 | 
			
		||||
                // Invalid index do not change tab.
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -536,9 +536,6 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft
 | 
			
		||||
        const tabToSelect = this.tabs[index];
 | 
			
		||||
        if (!tabToSelect || !tabToSelect.enabled || tabToSelect.id == this.selected) {
 | 
			
		||||
            // Already selected or not enabled.
 | 
			
		||||
            e?.preventDefault();
 | 
			
		||||
            e?.stopPropagation();
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -12,9 +12,12 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Component, Input, Output, EventEmitter, OnChanges, SimpleChange, Optional, ViewChild, ElementRef } from '@angular/core';
 | 
			
		||||
import { Component, Input, Output, EventEmitter, OnChanges, SimpleChange, ViewChild, ElementRef } from '@angular/core';
 | 
			
		||||
import { IonContent, IonInfiniteScroll } from '@ionic/angular';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
 | 
			
		||||
const THRESHOLD = .15; // % of the scroll element height that must be close to the edge to consider loading more items necessary.
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component to show a infinite loading trigger and spinner while more data is being loaded.
 | 
			
		||||
@ -41,12 +44,7 @@ export class CoreInfiniteLoadingComponent implements OnChanges {
 | 
			
		||||
 | 
			
		||||
    loadingMore = false;   // Hide button and avoid loading more.
 | 
			
		||||
 | 
			
		||||
    protected threshold = parseFloat('15%') / 100;
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        protected element: ElementRef,
 | 
			
		||||
        @Optional() protected content: IonContent,
 | 
			
		||||
    ) {
 | 
			
		||||
    constructor(protected element: ElementRef) {
 | 
			
		||||
        this.action = new EventEmitter();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -70,25 +68,30 @@ export class CoreInfiniteLoadingComponent implements OnChanges {
 | 
			
		||||
     * like the Ionic component does.
 | 
			
		||||
     */
 | 
			
		||||
    protected async checkScrollDistance(): Promise<void> {
 | 
			
		||||
        if (this.enabled) {
 | 
			
		||||
            const scrollElement = await this.content.getScrollElement();
 | 
			
		||||
        if (!this.enabled) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Wait until next tick to allow items to render and scroll content to grow.
 | 
			
		||||
        await CoreUtils.instance.nextTick();
 | 
			
		||||
 | 
			
		||||
        // Calculate distance from edge.
 | 
			
		||||
        const content = this.element.nativeElement.closest('ion-content') as IonContent;
 | 
			
		||||
        const scrollElement = await content.getScrollElement();
 | 
			
		||||
 | 
			
		||||
        const infiniteHeight = this.element.nativeElement.getBoundingClientRect().height;
 | 
			
		||||
 | 
			
		||||
        const scrollTop = scrollElement.scrollTop;
 | 
			
		||||
        const height = scrollElement.offsetHeight;
 | 
			
		||||
            const threshold = height * this.threshold;
 | 
			
		||||
 | 
			
		||||
        const threshold = height * THRESHOLD;
 | 
			
		||||
        const distanceFromInfinite = (this.position === 'bottom')
 | 
			
		||||
            ? scrollElement.scrollHeight - infiniteHeight - scrollTop - threshold - height
 | 
			
		||||
            : scrollTop - infiniteHeight - threshold;
 | 
			
		||||
 | 
			
		||||
        // If it's close enough the edge, trigger the action to load more items.
 | 
			
		||||
        if (distanceFromInfinite < 0 && !this.loadingMore && this.enabled) {
 | 
			
		||||
            this.loadMore();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load More items calling the action provided.
 | 
			
		||||
 | 
			
		||||
@ -14,11 +14,11 @@
 | 
			
		||||
 | 
			
		||||
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';
 | 
			
		||||
 | 
			
		||||
enum CoreSplitViewMode {
 | 
			
		||||
export enum CoreSplitViewMode {
 | 
			
		||||
    MenuOnly = 'menu-only', // Hides content.
 | 
			
		||||
    ContentOnly = 'content-only', // Hides menu.
 | 
			
		||||
    MenuAndContent = 'menu-and-content', // Shows both menu and content.
 | 
			
		||||
@ -31,9 +31,11 @@ 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;
 | 
			
		||||
    isNested = false;
 | 
			
		||||
 | 
			
		||||
    private outletRouteSubject: BehaviorSubject<ActivatedRouteSnapshot | null> = new BehaviorSubject(null);
 | 
			
		||||
@ -55,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);
 | 
			
		||||
            }),
 | 
			
		||||
@ -82,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');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -100,12 +102,16 @@ export class CoreSplitViewComponent implements AfterViewInit, OnDestroy {
 | 
			
		||||
     * @return Split view mode.
 | 
			
		||||
     */
 | 
			
		||||
    private getCurrentMode(): CoreSplitViewMode {
 | 
			
		||||
        if (this.mode) {
 | 
			
		||||
            return this.mode;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.isNested) {
 | 
			
		||||
            return CoreSplitViewMode.MenuOnly;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (CoreScreen.instance.isMobile) {
 | 
			
		||||
            return this.outlet.isActivated
 | 
			
		||||
            return this.contentOutlet.isActivated
 | 
			
		||||
                ? CoreSplitViewMode.ContentOnly
 | 
			
		||||
                : CoreSplitViewMode.MenuOnly;
 | 
			
		||||
        }
 | 
			
		||||
@ -119,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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -43,8 +43,8 @@ import { CoreTabBase, CoreTabsBaseComponent } from '@classes/tabs';
 | 
			
		||||
 *
 | 
			
		||||
 * Tab contents will only be shown if that tab is selected.
 | 
			
		||||
 *
 | 
			
		||||
 * @todo: Test behaviour when tabs are added late.
 | 
			
		||||
 * @todo: Test RTL and tab history.
 | 
			
		||||
 * @todo: This should behave like the split-view in relation to routing (maybe we could reuse some code from CoreItemsListManager).
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'core-tabs-outlet',
 | 
			
		||||
 | 
			
		||||
@ -115,7 +115,7 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
 | 
			
		||||
        // Load the course handlers.
 | 
			
		||||
        const handlers = await CoreCourseOptionsDelegate.instance.getHandlersToDisplay(this.course!, false, false);
 | 
			
		||||
 | 
			
		||||
        this.tabs.concat(handlers.map(handler => handler.data));
 | 
			
		||||
        this.tabs = [...this.tabs, ...handlers.map(handler => handler.data)];
 | 
			
		||||
 | 
			
		||||
        let tabToLoad: number | undefined;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										38
									
								
								src/core/features/grades/grades-course-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/core/features/grades/grades-course-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { NgModule } from '@angular/core';
 | 
			
		||||
import { RouterModule, Routes } from '@angular/router';
 | 
			
		||||
 | 
			
		||||
import { CoreGradesCoursePage } from './pages/course/course.page';
 | 
			
		||||
import { CoreGradesCoursePageModule } from './pages/course/course.module';
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: '',
 | 
			
		||||
        component: CoreGradesCoursePage,
 | 
			
		||||
        data: {
 | 
			
		||||
            useSplitView: false,
 | 
			
		||||
            outsideGradesTab: true,
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        RouterModule.forChild(routes),
 | 
			
		||||
        CoreGradesCoursePageModule,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class CoreGradesCourseLazyModule {}
 | 
			
		||||
@ -18,13 +18,14 @@ import { NgModule } from '@angular/core';
 | 
			
		||||
import { RouterModule, Routes } from '@angular/router';
 | 
			
		||||
import { TranslateModule } from '@ngx-translate/core';
 | 
			
		||||
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { conditionalRoutes } from '@/app/app-routing.module';
 | 
			
		||||
import { CoreScreen } from '@services/screen';
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
 | 
			
		||||
import { CoreGradesCoursePage } from './pages/course/course';
 | 
			
		||||
import { CoreGradesCoursePage } from './pages/course/course.page';
 | 
			
		||||
import { CoreGradesCoursePageModule } from './pages/course/course.module';
 | 
			
		||||
import { CoreGradesCoursesPage } from './pages/courses/courses';
 | 
			
		||||
import { CoreGradesGradePage } from './pages/grade/grade';
 | 
			
		||||
import { conditionalRoutes } from '@/app/app-routing.module';
 | 
			
		||||
 | 
			
		||||
const mobileRoutes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
@ -76,10 +77,10 @@ const routes: Routes = [
 | 
			
		||||
        IonicModule,
 | 
			
		||||
        TranslateModule.forChild(),
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
        CoreGradesCoursePageModule,
 | 
			
		||||
    ],
 | 
			
		||||
    declarations: [
 | 
			
		||||
        CoreGradesCoursesPage,
 | 
			
		||||
        CoreGradesCoursePage,
 | 
			
		||||
        CoreGradesGradePage,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@ -15,6 +15,7 @@
 | 
			
		||||
import { APP_INITIALIZER, NgModule } from '@angular/core';
 | 
			
		||||
import { Routes } from '@angular/router';
 | 
			
		||||
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
 | 
			
		||||
import { CoreCourseIndexRoutingModule } from '@features/course/pages/index/index-routing.module';
 | 
			
		||||
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
 | 
			
		||||
import { CoreMainMenuRoutingModule } from '@features/mainmenu/mainmenu-routing.module';
 | 
			
		||||
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
 | 
			
		||||
@ -33,10 +34,18 @@ const routes: Routes = [
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const courseIndexRoutes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: 'grades',
 | 
			
		||||
        loadChildren: () => import('@features/grades/grades-course-lazy.module').then(m => m.CoreGradesCourseLazyModule),
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreMainMenuTabRoutingModule.forChild(routes),
 | 
			
		||||
        CoreMainMenuRoutingModule.forChild({ children: routes }),
 | 
			
		||||
        CoreCourseIndexRoutingModule.forChild({ children: courseIndexRoutes }),
 | 
			
		||||
    ],
 | 
			
		||||
    providers: [
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,7 @@
 | 
			
		||||
    </ion-toolbar>
 | 
			
		||||
</ion-header>
 | 
			
		||||
<ion-content>
 | 
			
		||||
    <core-split-view>
 | 
			
		||||
    <core-split-view [mode]="splitViewMode">
 | 
			
		||||
        <ion-refresher slot="fixed" [disabled]="!grades.loaded" (ionRefresh)="refreshGrades($event.target)">
 | 
			
		||||
            <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
			
		||||
        </ion-refresher>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										35
									
								
								src/core/features/grades/pages/course/course.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/core/features/grades/pages/course/course.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { CommonModule } from '@angular/common';
 | 
			
		||||
import { IonicModule } from '@ionic/angular';
 | 
			
		||||
import { NgModule } from '@angular/core';
 | 
			
		||||
import { TranslateModule } from '@ngx-translate/core';
 | 
			
		||||
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
 | 
			
		||||
import { CoreGradesCoursePage } from './course.page';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        CommonModule,
 | 
			
		||||
        IonicModule,
 | 
			
		||||
        TranslateModule.forChild(),
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
    ],
 | 
			
		||||
    declarations: [
 | 
			
		||||
        CoreGradesCoursePage,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class CoreGradesCoursePageModule {}
 | 
			
		||||
@ -27,9 +27,10 @@ import {
 | 
			
		||||
} from '@features/grades/services/grades-helper';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
 | 
			
		||||
import { CoreSplitViewComponent, CoreSplitViewMode } from '@components/split-view/split-view';
 | 
			
		||||
import { CoreObject } from '@singletons/object';
 | 
			
		||||
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Page that displays a course grades.
 | 
			
		||||
@ -42,14 +43,18 @@ import { CorePageItemsListManager } from '@classes/page-items-list-manager';
 | 
			
		||||
export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
    grades: CoreGradesCourseManager;
 | 
			
		||||
    splitViewMode?: CoreSplitViewMode;
 | 
			
		||||
 | 
			
		||||
    @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
 | 
			
		||||
 | 
			
		||||
    constructor(route: ActivatedRoute) {
 | 
			
		||||
        const courseId = parseInt(route.snapshot.params.courseId);
 | 
			
		||||
        const courseId = parseInt(route.snapshot.params.courseId ?? route.snapshot.queryParams.courseId);
 | 
			
		||||
        const userId = parseInt(route.snapshot.queryParams.userId ?? CoreSites.instance.getCurrentSiteUserId());
 | 
			
		||||
        const useSplitView = route.snapshot.data.useSplitView ?? true;
 | 
			
		||||
        const outsideGradesTab = route.snapshot.data.outsideGradesTab ?? false;
 | 
			
		||||
 | 
			
		||||
        this.grades = new CoreGradesCourseManager(CoreGradesCoursePage, courseId, userId);
 | 
			
		||||
        this.splitViewMode = useSplitView ? undefined : CoreSplitViewMode.MenuOnly;
 | 
			
		||||
        this.grades = new CoreGradesCourseManager(CoreGradesCoursePage, courseId, userId, outsideGradesTab);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -118,11 +123,14 @@ class CoreGradesCourseManager extends CorePageItemsListManager<CoreGradesFormatt
 | 
			
		||||
    columns?: CoreGradesFormattedTableColumn[];
 | 
			
		||||
    rows?: CoreGradesFormattedTableRow[];
 | 
			
		||||
 | 
			
		||||
    constructor(pageComponent: unknown, courseId: number, userId: number) {
 | 
			
		||||
    private outsideGradesTab: boolean;
 | 
			
		||||
 | 
			
		||||
    constructor(pageComponent: unknown, courseId: number, userId: number, outsideGradesTab: boolean) {
 | 
			
		||||
        super(pageComponent);
 | 
			
		||||
 | 
			
		||||
        this.courseId = courseId;
 | 
			
		||||
        this.userId = userId;
 | 
			
		||||
        this.outsideGradesTab = outsideGradesTab;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -137,6 +145,19 @@ class CoreGradesCourseManager extends CorePageItemsListManager<CoreGradesFormatt
 | 
			
		||||
        this.setItems(table.rows.filter(this.isFilledRow));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async select(row: CoreGradesFormattedTableRowFilled): Promise<void> {
 | 
			
		||||
        if (this.outsideGradesTab) {
 | 
			
		||||
            await CoreNavigator.instance.navigateToSitePath(`/grades/${this.courseId}/${row.id}`);
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return super.select(row);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
@ -83,19 +83,14 @@ export class CoreGradesCourseOptionHandlerService implements CoreCourseOptionsHa
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the data needed to render the handler.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Data or promise resolved with the data.
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getDisplayData(): CoreCourseOptionsHandlerData | Promise<CoreCourseOptionsHandlerData> {
 | 
			
		||||
        throw new Error('CoreGradesCourseOptionHandler.getDisplayData is not implemented');
 | 
			
		||||
 | 
			
		||||
        // @todo
 | 
			
		||||
        // return {
 | 
			
		||||
        //     title: 'core.grades.grades',
 | 
			
		||||
        //     class: 'core-grades-course-handler',
 | 
			
		||||
        //     component: CoreGradesCourseComponent,
 | 
			
		||||
        // };
 | 
			
		||||
        return {
 | 
			
		||||
            title: 'core.grades.grades',
 | 
			
		||||
            class: 'core-grades-course-handler',
 | 
			
		||||
            page: 'grades',
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										54
									
								
								src/core/features/user/pages/participants/participants.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/core/features/user/pages/participants/participants.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,54 @@
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <ion-button [hidden]="!searchEnabled" (click)="toggleSearch()" [attr.aria-label]="'core.search' | translate">
 | 
			
		||||
        <ion-icon name="fas-search" slot="icon-only"></ion-icon>
 | 
			
		||||
    </ion-button>
 | 
			
		||||
</core-navbar-buttons>
 | 
			
		||||
 | 
			
		||||
<ion-content>
 | 
			
		||||
    <core-split-view>
 | 
			
		||||
        <ion-refresher slot="fixed" [disabled]="!participants.loaded || searchInProgress" (ionRefresh)="refreshParticipants($event.target)">
 | 
			
		||||
            <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
			
		||||
        </ion-refresher>
 | 
			
		||||
 | 
			
		||||
        <core-search-box *ngIf="showSearchBox"
 | 
			
		||||
            [disabled]="searchInProgress" [spellcheck]="false" [autoFocus]="true" [lengthCheck]="1"
 | 
			
		||||
            autocorrect="off" searchArea="CoreUserParticipants"
 | 
			
		||||
            (onSubmit)="search($event)" (onClear)="clearSearch()">
 | 
			
		||||
        </core-search-box>
 | 
			
		||||
 | 
			
		||||
        <core-loading [hideUntil]="participants.loaded">
 | 
			
		||||
            <core-empty-box *ngIf="participants.empty && !searchInProgress && !searchQuery" icon="person" [message]="'core.user.noparticipants' | translate">
 | 
			
		||||
            </core-empty-box>
 | 
			
		||||
 | 
			
		||||
            <core-empty-box *ngIf="participants.empty && !searchInProgress && searchQuery" icon="search" [message]="'core.noresults' | translate">
 | 
			
		||||
            </core-empty-box>
 | 
			
		||||
 | 
			
		||||
            <ion-list *ngIf="!participants.empty">
 | 
			
		||||
                <ion-item *ngFor="let participant of participants.items"
 | 
			
		||||
                    class="ion-text-wrap" [class.core-selected-item]="participants.isSelected(participant)" [title]="participant.fullname"
 | 
			
		||||
                    (click)="participants.select(participant)">
 | 
			
		||||
 | 
			
		||||
                    <core-user-avatar [user]="participant" [linkProfile]="false" [checkOnline]="true" slot="start">
 | 
			
		||||
                    </core-user-avatar>
 | 
			
		||||
 | 
			
		||||
                    <ion-label>
 | 
			
		||||
                        <ng-container *ngIf="!searchQuery">
 | 
			
		||||
                            <h2>{{ participant.fullname }}</h2>
 | 
			
		||||
                            <p *ngIf="participant.lastcourseaccess"><strong>{{ 'core.lastaccess' | translate }}: </strong>{{ participant.lastcourseaccess | coreTimeAgo }}</p>
 | 
			
		||||
                            <p *ngIf="participant.lastcourseaccess == null && participant.lastaccess"><strong>{{ 'core.lastaccess' | translate }}: </strong>{{ participant.lastaccess | coreTimeAgo }}</p>
 | 
			
		||||
                        </ng-container>
 | 
			
		||||
 | 
			
		||||
                        <ng-container *ngIf="searchQuery">
 | 
			
		||||
                            <h2>
 | 
			
		||||
                                <core-format-text [text]="participant.fullname" [highlight]="searchQuery" [filter]="false"></core-format-text>
 | 
			
		||||
                            </h2>
 | 
			
		||||
                        </ng-container>
 | 
			
		||||
                    </ion-label>
 | 
			
		||||
 | 
			
		||||
                </ion-item>
 | 
			
		||||
            </ion-list>
 | 
			
		||||
            <core-infinite-loading [enabled]="participants.loaded && !participants.completed" (action)="fetchMoreParticipants($event)" [error]="fetchMoreParticipantsFailed">
 | 
			
		||||
            </core-infinite-loading>
 | 
			
		||||
        </core-loading>
 | 
			
		||||
    </core-split-view>
 | 
			
		||||
</ion-content>
 | 
			
		||||
							
								
								
									
										242
									
								
								src/core/features/user/pages/participants/participants.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										242
									
								
								src/core/features/user/pages/participants/participants.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,242 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router';
 | 
			
		||||
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, CoreUserProvider, CoreUserParticipant, CoreUserData } from '@features/user/services/user';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Page that displays the list of course participants.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'page-core-user-participants',
 | 
			
		||||
    templateUrl: 'participants.html',
 | 
			
		||||
})
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
    constructor(route: ActivatedRoute) {
 | 
			
		||||
        const courseId = parseInt(route.snapshot.queryParams.courseId);
 | 
			
		||||
 | 
			
		||||
        this.participants = new CoreUserParticipantsManager(CoreUserParticipantsPage, courseId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async ngOnInit(): Promise<void> {
 | 
			
		||||
        this.searchEnabled = await CoreUser.instance.canSearchParticipantsInSite();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async ngAfterViewInit(): Promise<void> {
 | 
			
		||||
        await this.fetchInitialParticipants();
 | 
			
		||||
 | 
			
		||||
        this.participants.watchSplitViewOutlet(this.splitView);
 | 
			
		||||
        this.participants.start();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    ngOnDestroy(): void {
 | 
			
		||||
        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<void> {
 | 
			
		||||
        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<void> {
 | 
			
		||||
        CoreApp.instance.closeKeyboard();
 | 
			
		||||
 | 
			
		||||
        this.searchInProgress = true;
 | 
			
		||||
        this.searchQuery = query;
 | 
			
		||||
        this.participants.resetItems();
 | 
			
		||||
 | 
			
		||||
        await this.fetchInitialParticipants();
 | 
			
		||||
 | 
			
		||||
        this.searchInProgress = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Refresh participants.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresher Refresher.
 | 
			
		||||
     */
 | 
			
		||||
    async refreshParticipants(refresher: IonRefresher): Promise<void> {
 | 
			
		||||
        await CoreUtils.instance.ignoreErrors(CoreUser.instance.invalidateParticipantsList(this.participants.courseId));
 | 
			
		||||
        await CoreUtils.instance.ignoreErrors(this.fetchParticipants());
 | 
			
		||||
 | 
			
		||||
        refresher?.complete();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load a new batch of participants.
 | 
			
		||||
     *
 | 
			
		||||
     * @param complete Completion callback.
 | 
			
		||||
     */
 | 
			
		||||
    async fetchMoreParticipants(complete: () => void): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            await this.fetchParticipants(this.participants.items);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            CoreDomUtils.instance.showErrorModalDefault(error, 'Error loading more participants');
 | 
			
		||||
 | 
			
		||||
            this.fetchMoreParticipantsFailed = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        complete();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Obtain the initial batch of participants.
 | 
			
		||||
     */
 | 
			
		||||
    private async fetchInitialParticipants(): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            await this.fetchParticipants();
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            CoreDomUtils.instance.showErrorModalDefault(error, 'Error loading participants');
 | 
			
		||||
 | 
			
		||||
            this.participants.setItems([]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the list of participants.
 | 
			
		||||
     *
 | 
			
		||||
     * @param loadedParticipants Participants list to continue loading from.
 | 
			
		||||
     */
 | 
			
		||||
    private async fetchParticipants(loadedParticipants: CoreUserParticipant[] | CoreUserData[] = []): Promise<void> {
 | 
			
		||||
        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.fetchMoreParticipantsFailed = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Helper to manage the list of participants.
 | 
			
		||||
 */
 | 
			
		||||
class CoreUserParticipantsManager extends CorePageItemsListManager<CoreUserParticipant | CoreUserData> {
 | 
			
		||||
 | 
			
		||||
    courseId: number;
 | 
			
		||||
 | 
			
		||||
    constructor(pageComponent: unknown, courseId: number) {
 | 
			
		||||
        super(pageComponent);
 | 
			
		||||
 | 
			
		||||
        this.courseId = courseId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async select(participant: CoreUserParticipant | CoreUserData): Promise<void> {
 | 
			
		||||
        if (CoreScreen.instance.isMobile) {
 | 
			
		||||
            await CoreNavigator.instance.navigateToSitePath('/user/profile', { params: { userId: participant.id } });
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return super.select(participant);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected getItemPath(participant: CoreUserParticipant | CoreUserData): string {
 | 
			
		||||
        return participant.id.toString();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected getSelectedItemPath(route: ActivatedRouteSnapshot): string | null {
 | 
			
		||||
        return route.params.userId ?? null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    protected async logActivity(): Promise<void>  {
 | 
			
		||||
        await CoreUser.instance.logParticipantsView(this.courseId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										116
									
								
								src/core/features/user/services/handlers/course-option.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								src/core/features/user/services/handlers/course-option.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,116 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { CoreCourseProvider } from '@features/course/services/course';
 | 
			
		||||
import {
 | 
			
		||||
    CoreCourseAccess,
 | 
			
		||||
    CoreCourseOptionsHandler,
 | 
			
		||||
    CoreCourseOptionsHandlerData,
 | 
			
		||||
} from '@features/course/services/course-options-delegate';
 | 
			
		||||
import { CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses';
 | 
			
		||||
import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '@features/courses/services/courses-helper';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { CoreUser } from '../user';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Course nav handler.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class CoreUserCourseOptionHandlerService implements CoreCourseOptionsHandler {
 | 
			
		||||
 | 
			
		||||
    name = 'CoreUserParticipants';
 | 
			
		||||
    priority = 600;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Should invalidate the data to determine if the handler is enabled for a certain course.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId The course ID.
 | 
			
		||||
     * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    invalidateEnabledForCourse(courseId: number, navOptions?: CoreCourseUserAdminOrNavOptionIndexed): Promise<void> {
 | 
			
		||||
        if (navOptions && typeof navOptions.participants != 'undefined') {
 | 
			
		||||
            // No need to invalidate anything.
 | 
			
		||||
            return Promise.resolve();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return CoreUser.instance.invalidateParticipantsList(courseId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if the handler is enabled on a site level.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Whether or not the handler is enabled on a site level.
 | 
			
		||||
     */
 | 
			
		||||
    isEnabled(): Promise<boolean> {
 | 
			
		||||
        return Promise.resolve(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether or not the handler is enabled for a certain course.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId The course ID.
 | 
			
		||||
     * @param accessData Access type and data. Default, guest, ...
 | 
			
		||||
     * @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
 | 
			
		||||
     * @return True or promise resolved with true if enabled.
 | 
			
		||||
     */
 | 
			
		||||
    isEnabledForCourse(
 | 
			
		||||
        courseId: number,
 | 
			
		||||
        accessData: CoreCourseAccess,
 | 
			
		||||
        navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
 | 
			
		||||
    ): boolean | Promise<boolean> {
 | 
			
		||||
        if (accessData && accessData.type == CoreCourseProvider.ACCESS_GUEST) {
 | 
			
		||||
            return false; // Not enabled for guests.
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (navOptions && typeof navOptions.participants != 'undefined') {
 | 
			
		||||
            return navOptions.participants;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return CoreUser.instance.isPluginEnabledForCourse(courseId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getDisplayData(): CoreCourseOptionsHandlerData | Promise<CoreCourseOptionsHandlerData> {
 | 
			
		||||
        return {
 | 
			
		||||
            title: 'core.user.participants',
 | 
			
		||||
            class: 'core-user-participants-handler',
 | 
			
		||||
            page: 'participants',
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when a course is downloaded. It should prefetch all the data to be able to see the addon in offline.
 | 
			
		||||
     *
 | 
			
		||||
     * @param course The course.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async prefetch(course: CoreEnrolledCourseDataWithExtraInfoAndOptions): Promise<void> {
 | 
			
		||||
        let offset = 0;
 | 
			
		||||
        let canLoadMore = true;
 | 
			
		||||
 | 
			
		||||
        do {
 | 
			
		||||
            const result = await CoreUser.instance.getParticipants(course.id, offset, undefined, undefined, true);
 | 
			
		||||
 | 
			
		||||
            offset += result.participants.length;
 | 
			
		||||
            canLoadMore = result.canLoadMore;
 | 
			
		||||
        } while (canLoadMore);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CoreUserCourseOptionHandler extends makeSingleton(CoreUserCourseOptionHandlerService) {}
 | 
			
		||||
							
								
								
									
										52
									
								
								src/core/features/user/user-course-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/core/features/user/user-course-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,52 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { CommonModule } from '@angular/common';
 | 
			
		||||
import { NgModule } from '@angular/core';
 | 
			
		||||
import { RouterModule, Routes } from '@angular/router';
 | 
			
		||||
import { IonicModule } from '@ionic/angular';
 | 
			
		||||
import { TranslateModule } from '@ngx-translate/core';
 | 
			
		||||
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { CoreSearchComponentsModule } from '@features/search/components/components.module';
 | 
			
		||||
 | 
			
		||||
import { CoreUserParticipantsPage } from './pages/participants/participants';
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: '',
 | 
			
		||||
        component: CoreUserParticipantsPage,
 | 
			
		||||
        children: [
 | 
			
		||||
            {
 | 
			
		||||
                path: ':userId',
 | 
			
		||||
                loadChildren: () => import('@features/user/pages/profile/profile.module').then(m => m.CoreUserProfilePageModule),
 | 
			
		||||
            },
 | 
			
		||||
        ],
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        RouterModule.forChild(routes),
 | 
			
		||||
        CommonModule,
 | 
			
		||||
        IonicModule,
 | 
			
		||||
        TranslateModule.forChild(),
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
        CoreSearchComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    declarations: [
 | 
			
		||||
        CoreUserParticipantsPage,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class CoreUserCourseLazyModule {}
 | 
			
		||||
@ -27,6 +27,9 @@ import { CoreCronDelegate } from '@services/cron';
 | 
			
		||||
import { CoreUserSyncCronHandler } from './services/handlers/sync-cron';
 | 
			
		||||
import { CoreUserTagAreaHandler } from './services/handlers/tag-area';
 | 
			
		||||
import { CoreTagAreaDelegate } from '@features/tag/services/tag-area-delegate';
 | 
			
		||||
import { CoreCourseIndexRoutingModule } from '@features/course/pages/index/index-routing.module';
 | 
			
		||||
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
 | 
			
		||||
import { CoreUserCourseOptionHandler } from './services/handlers/course-option';
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
@ -35,9 +38,17 @@ const routes: Routes = [
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const courseIndexRoutes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: 'participants',
 | 
			
		||||
        loadChildren: () => import('@features/user/user-course-lazy.module').then(m => m.CoreUserCourseLazyModule),
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreMainMenuTabRoutingModule.forChild(routes),
 | 
			
		||||
        CoreCourseIndexRoutingModule.forChild({ children: courseIndexRoutes }),
 | 
			
		||||
        CoreUserComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    providers: [
 | 
			
		||||
@ -58,6 +69,7 @@ const routes: Routes = [
 | 
			
		||||
                CoreContentLinksDelegate.instance.registerHandler(CoreUserProfileLinkHandler.instance);
 | 
			
		||||
                CoreCronDelegate.instance.register(CoreUserSyncCronHandler.instance);
 | 
			
		||||
                CoreTagAreaDelegate.instance.registerHandler(CoreUserTagAreaHandler.instance);
 | 
			
		||||
                CoreCourseOptionsDelegate.instance.registerHandler(CoreUserCourseOptionHandler.instance);
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
@ -1623,6 +1623,13 @@ export class CoreUtilsProvider {
 | 
			
		||||
        return new Promise(resolve => setTimeout(resolve, milliseconds));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Wait until the next tick.
 | 
			
		||||
     */
 | 
			
		||||
    nextTick(): Promise<void> {
 | 
			
		||||
        return this.wait(0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class CoreUtils extends makeSingleton(CoreUtilsProvider) {}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user