forked from CIT/Vmeda.Online
		
	MOBILE-3659 course: Implement index and contents pages
This commit is contained in:
		
							parent
							
								
									a91c042cc2
								
							
						
					
					
						commit
						183a033dc8
					
				@ -106,7 +106,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe
 | 
			
		||||
    protected unregisterBackButtonAction: any;
 | 
			
		||||
    protected languageChangedSubscription: Subscription;
 | 
			
		||||
    protected isInTransition = false; // Weather Slides is in transition.
 | 
			
		||||
    protected slidesSwiper: any;
 | 
			
		||||
    protected slidesSwiper: any; // eslint-disable-line @typescript-eslint/no-explicit-any
 | 
			
		||||
    protected slidesSwiperLoaded = false;
 | 
			
		||||
    protected stackEventsSubscription?: Subscription;
 | 
			
		||||
 | 
			
		||||
@ -338,7 +338,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.firstSelectedTab = selectedTab.id;
 | 
			
		||||
        this.firstSelectedTab = selectedTab.id!;
 | 
			
		||||
        this.selectTab(this.firstSelectedTab);
 | 
			
		||||
 | 
			
		||||
        // Setup tab scrolling.
 | 
			
		||||
@ -548,18 +548,31 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Tab selected.
 | 
			
		||||
     * Select a tab by ID.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tabId Selected tab index.
 | 
			
		||||
     * @param tabId Tab ID.
 | 
			
		||||
     * @param e Event.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async selectTab(tabId: string, e?: Event): Promise<void> {
 | 
			
		||||
        let index = this.tabs.findIndex((tab) => tabId == tab.id);
 | 
			
		||||
        const index = this.tabs.findIndex((tab) => tabId == tab.id);
 | 
			
		||||
 | 
			
		||||
        return this.selectByIndex(index, e);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Select a tab by index.
 | 
			
		||||
     *
 | 
			
		||||
     * @param index Index to select.
 | 
			
		||||
     * @param e Event.
 | 
			
		||||
     * @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 && e.preventDefault();
 | 
			
		||||
                e && e.stopPropagation();
 | 
			
		||||
                e?.preventDefault();
 | 
			
		||||
                e?.stopPropagation();
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
@ -568,12 +581,11 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe
 | 
			
		||||
            index = 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const selectedTab = this.tabs[index];
 | 
			
		||||
        if (tabId == this.selected || !selectedTab || !selectedTab.enabled) {
 | 
			
		||||
        const tabToSelect = this.tabs[index];
 | 
			
		||||
        if (!tabToSelect || !tabToSelect.enabled || tabToSelect.id == this.selected) {
 | 
			
		||||
            // Already selected or not enabled.
 | 
			
		||||
 | 
			
		||||
            e && e.preventDefault();
 | 
			
		||||
            e && e.stopPropagation();
 | 
			
		||||
            e?.preventDefault();
 | 
			
		||||
            e?.stopPropagation();
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
@ -583,17 +595,17 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const pageParams: NavigationOptions = {};
 | 
			
		||||
        if (selectedTab.pageParams) {
 | 
			
		||||
            pageParams.queryParams = selectedTab.pageParams;
 | 
			
		||||
        if (tabToSelect.pageParams) {
 | 
			
		||||
            pageParams.queryParams = tabToSelect.pageParams;
 | 
			
		||||
        }
 | 
			
		||||
        const ok = await this.navCtrl.navigateForward(selectedTab.page, pageParams);
 | 
			
		||||
        const ok = await this.navCtrl.navigateForward(tabToSelect.page, pageParams);
 | 
			
		||||
 | 
			
		||||
        if (ok !== false) {
 | 
			
		||||
            this.selectHistory.push(tabId);
 | 
			
		||||
            this.selected = tabId;
 | 
			
		||||
            this.selectHistory.push(tabToSelect.id!);
 | 
			
		||||
            this.selected = tabToSelect.id;
 | 
			
		||||
            this.selectedIndex = index;
 | 
			
		||||
 | 
			
		||||
            this.ionChange.emit(selectedTab);
 | 
			
		||||
            this.ionChange.emit(tabToSelect);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -644,16 +656,14 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe
 | 
			
		||||
/**
 | 
			
		||||
 * Core Tab class.
 | 
			
		||||
 */
 | 
			
		||||
class CoreTab {
 | 
			
		||||
 | 
			
		||||
    id = ''; // Unique tab id.
 | 
			
		||||
    class = ''; // Class, if needed.
 | 
			
		||||
    title = ''; // The translatable tab title.
 | 
			
		||||
export type CoreTab = {
 | 
			
		||||
    page: string; // Page to navigate to.
 | 
			
		||||
    title: string; // The translatable tab title.
 | 
			
		||||
    id?: string; // Unique tab id.
 | 
			
		||||
    class?: string; // Class, if needed.
 | 
			
		||||
    icon?: string; // The tab icon.
 | 
			
		||||
    badge?: string; // A badge to add in the tab.
 | 
			
		||||
    badgeStyle?: string; // The badge color.
 | 
			
		||||
    enabled = true; // Whether the tab is enabled.
 | 
			
		||||
    page = ''; // Page to navigate to.
 | 
			
		||||
    enabled?: boolean; // Whether the tab is enabled.
 | 
			
		||||
    pageParams?: Params; // Page params.
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
<div class="ion-padding">
 | 
			
		||||
    <core-course-module-description [description]="module && module.description" contextLevel="module"
 | 
			
		||||
        [contextInstanceId]="module.id" [courseId]="courseId">
 | 
			
		||||
    <core-course-module-description [description]="module?.description" contextLevel="module"
 | 
			
		||||
        [contextInstanceId]="module?.id" [courseId]="courseId">
 | 
			
		||||
    </core-course-module-description>
 | 
			
		||||
 | 
			
		||||
    <h2 *ngIf="!isDisabledInSite && isSupportedByTheApp">{{ 'core.whoops' | translate }}</h2>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										33
									
								
								src/core/features/course/course-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/core/features/course/course-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
			
		||||
// (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';
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: '',
 | 
			
		||||
        redirectTo: 'index',
 | 
			
		||||
        pathMatch: 'full',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        path: 'index',
 | 
			
		||||
        loadChildren: () => import('./pages/index/index.module').then( m => m.CoreCourseIndexPageModule),
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [RouterModule.forChild(routes)],
 | 
			
		||||
})
 | 
			
		||||
export class CoreCourseLazyModule {}
 | 
			
		||||
@ -13,18 +13,38 @@
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { NgModule } from '@angular/core';
 | 
			
		||||
import { Routes } from '@angular/router';
 | 
			
		||||
 | 
			
		||||
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
 | 
			
		||||
import { CORE_SITE_SCHEMAS } from '@services/sites';
 | 
			
		||||
import { CoreCourseComponentsModule } from './components/components.module';
 | 
			
		||||
import { CoreCourseFormatModule } from './format/formats.module';
 | 
			
		||||
import { SITE_SCHEMA, OFFLINE_SITE_SCHEMA } from './services/database/course';
 | 
			
		||||
import { SITE_SCHEMA as LOG_SITE_SCHEMA } from './services/database/log';
 | 
			
		||||
import { CoreCourseIndexRoutingModule } from './pages/index/index-routing.module';
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: 'course',
 | 
			
		||||
        loadChildren: () => import('@features/course/course-lazy.module').then(m => m.CoreCourseLazyModule),
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const courseIndexRoutes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: 'contents',
 | 
			
		||||
        loadChildren: () => import('./pages/contents/contents.module').then(m => m.CoreCourseContentsPageModule),
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreCourseIndexRoutingModule.forChild({ children: courseIndexRoutes }),
 | 
			
		||||
        CoreMainMenuTabRoutingModule.forChild(routes),
 | 
			
		||||
        CoreCourseFormatModule,
 | 
			
		||||
        CoreCourseComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    exports: [CoreCourseIndexRoutingModule],
 | 
			
		||||
    providers: [
 | 
			
		||||
        {
 | 
			
		||||
            provide: CORE_SITE_SCHEMAS,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										29
									
								
								src/core/features/course/pages/contents/contents.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/core/features/course/pages/contents/contents.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item [hidden]="!displayEnableDownload" [priority]="2000" [iconAction]="downloadEnabledIcon"
 | 
			
		||||
            [content]="'core.settings.showdownloadoptions' | translate" (action)="toggleDownload()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item [hidden]="!downloadCourseEnabled" [priority]="1900"
 | 
			
		||||
            [content]="prefetchCourseData.statusTranslatable | translate" (action)="prefetchCourse()"
 | 
			
		||||
            [iconAction]="prefetchCourseData.icon" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item [priority]="1800" [content]="'core.course.coursesummary' | translate" (action)="openCourseSummary()"
 | 
			
		||||
            iconAction="fa-graduation-cap">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngFor="let item of courseMenuHandlers" [priority]="item.priority" (action)="openMenuItem(item)"
 | 
			
		||||
            [content]="item.data.title | translate" [iconAction]="item.data.icon" [class]="item.data.class">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
</core-navbar-buttons>
 | 
			
		||||
<ion-content>
 | 
			
		||||
    <ion-refresher slot="fixed" [disabled]="!dataLoaded || !displayRefresher" (ionRefresh)="doRefresh($event)">
 | 
			
		||||
        <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
			
		||||
    </ion-refresher>
 | 
			
		||||
 | 
			
		||||
    <core-loading [hideUntil]="dataLoaded">
 | 
			
		||||
        <!-- <core-course-format [course]="course" [sections]="sections" [initialSectionId]="sectionId"
 | 
			
		||||
            [initialSectionNumber]="sectionNumber" [downloadEnabled]="downloadEnabled" [moduleId]="moduleId"
 | 
			
		||||
            (completionChanged)="onCompletionChange($event)" class="core-course-format-{{course.format}}">
 | 
			
		||||
        </core-course-format> -->
 | 
			
		||||
    </core-loading>
 | 
			
		||||
</ion-content>
 | 
			
		||||
							
								
								
									
										46
									
								
								src/core/features/course/pages/contents/contents.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/core/features/course/pages/contents/contents.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
			
		||||
// (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 { CommonModule } from '@angular/common';
 | 
			
		||||
import { RouterModule, Routes } from '@angular/router';
 | 
			
		||||
import { IonicModule } from '@ionic/angular';
 | 
			
		||||
import { TranslateModule } from '@ngx-translate/core';
 | 
			
		||||
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { CoreCourseContentsPage } from './contents';
 | 
			
		||||
import { CoreCourseComponentsModule } from '@features/course/components/components.module';
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: '',
 | 
			
		||||
        component: CoreCourseContentsPage,
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        RouterModule.forChild(routes),
 | 
			
		||||
        CommonModule,
 | 
			
		||||
        IonicModule,
 | 
			
		||||
        TranslateModule.forChild(),
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
        CoreCourseComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    declarations: [
 | 
			
		||||
        CoreCourseContentsPage,
 | 
			
		||||
    ],
 | 
			
		||||
    exports: [RouterModule],
 | 
			
		||||
})
 | 
			
		||||
export class CoreCourseContentsPageModule {}
 | 
			
		||||
							
								
								
									
										533
									
								
								src/core/features/course/pages/contents/contents.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										533
									
								
								src/core/features/course/pages/contents/contents.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,533 @@
 | 
			
		||||
// (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 { Component, ViewChild, OnInit, OnDestroy } from '@angular/core';
 | 
			
		||||
import { ActivatedRoute } from '@angular/router';
 | 
			
		||||
import { IonContent, IonRefresher, NavController } from '@ionic/angular';
 | 
			
		||||
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreCourses, CoreCourseAnyCourseData } from '@features/courses/services/courses';
 | 
			
		||||
import {
 | 
			
		||||
    CoreCourse,
 | 
			
		||||
    CoreCourseCompletionActivityStatus,
 | 
			
		||||
    CoreCourseModuleCompletionData,
 | 
			
		||||
    CoreCourseProvider,
 | 
			
		||||
} from '@features/course/services/course';
 | 
			
		||||
import { CoreCourseHelper, CoreCourseSectionFormatted, CorePrefetchStatusInfo } from '@features/course/services/course-helper';
 | 
			
		||||
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
 | 
			
		||||
// import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
 | 
			
		||||
import {
 | 
			
		||||
    CoreCourseOptionsDelegate,
 | 
			
		||||
    CoreCourseOptionsMenuHandlerToDisplay,
 | 
			
		||||
} from '@features/course/services/course-options-delegate';
 | 
			
		||||
// import { CoreCourseSyncProvider } from '../../providers/sync';
 | 
			
		||||
// import { CoreCourseFormatComponent } from '../../components/format/format';
 | 
			
		||||
import { CoreFilterHelper } from '@features/filter/services/filter-helper';
 | 
			
		||||
import {
 | 
			
		||||
    CoreEvents,
 | 
			
		||||
    CoreEventObserver,
 | 
			
		||||
    CoreEventCourseStatusChanged,
 | 
			
		||||
    CoreEventCompletionModuleViewedData,
 | 
			
		||||
} from '@singletons/events';
 | 
			
		||||
import { Translate } from '@singletons';
 | 
			
		||||
import { CoreNavHelper } from '@services/nav-helper';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Page that displays the contents of a course.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'page-core-course-contents',
 | 
			
		||||
    templateUrl: 'contents.html',
 | 
			
		||||
})
 | 
			
		||||
export class CoreCourseContentsPage implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
    @ViewChild(IonContent) content?: IonContent;
 | 
			
		||||
    // @ViewChild(CoreCourseFormatComponent) formatComponent: CoreCourseFormatComponent;
 | 
			
		||||
 | 
			
		||||
    course!: CoreCourseAnyCourseData;
 | 
			
		||||
    sections?: Section[];
 | 
			
		||||
    sectionId?: number;
 | 
			
		||||
    sectionNumber?: number;
 | 
			
		||||
    courseMenuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = [];
 | 
			
		||||
    dataLoaded = false;
 | 
			
		||||
    downloadEnabled = false;
 | 
			
		||||
    downloadEnabledIcon = 'far-square'; // Disabled by default.
 | 
			
		||||
    downloadCourseEnabled = false;
 | 
			
		||||
    moduleId?: number;
 | 
			
		||||
    displayEnableDownload = false;
 | 
			
		||||
    displayRefresher = false;
 | 
			
		||||
    prefetchCourseData: CorePrefetchStatusInfo = {
 | 
			
		||||
        icon: 'spinner',
 | 
			
		||||
        statusTranslatable: 'core.course.downloadcourse',
 | 
			
		||||
        status: '',
 | 
			
		||||
        loading: true,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    protected formatOptions?: Record<string, unknown>;
 | 
			
		||||
    protected completionObserver?: CoreEventObserver;
 | 
			
		||||
    protected courseStatusObserver?: CoreEventObserver;
 | 
			
		||||
    protected syncObserver?: CoreEventObserver;
 | 
			
		||||
    protected isDestroyed = false;
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        protected route: ActivatedRoute,
 | 
			
		||||
        protected navCtrl: NavController,
 | 
			
		||||
    ) { }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Component being initialized.
 | 
			
		||||
     */
 | 
			
		||||
    async ngOnInit(): Promise<void> {
 | 
			
		||||
        // Get params.
 | 
			
		||||
        this.course = this.route.snapshot.queryParams['course'];
 | 
			
		||||
        this.sectionId = this.route.snapshot.queryParams['sectionId'];
 | 
			
		||||
        this.sectionNumber = this.route.snapshot.queryParams['sectionNumber'];
 | 
			
		||||
        this.moduleId = this.route.snapshot.queryParams['moduleId'];
 | 
			
		||||
 | 
			
		||||
        if (!this.course) {
 | 
			
		||||
            CoreDomUtils.instance.showErrorModal('Missing required course parameter.');
 | 
			
		||||
            this.navCtrl.pop();
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.displayEnableDownload = !CoreSites.instance.getCurrentSite()?.isOfflineDisabled() &&
 | 
			
		||||
            CoreCourseFormatDelegate.instance.displayEnableDownload(this.course);
 | 
			
		||||
        this.downloadCourseEnabled = !CoreCourses.instance.isDownloadCourseDisabledInSite();
 | 
			
		||||
 | 
			
		||||
        this.initListeners();
 | 
			
		||||
 | 
			
		||||
        await this.loadData(false, true);
 | 
			
		||||
 | 
			
		||||
        this.dataLoaded = true;
 | 
			
		||||
 | 
			
		||||
        this.initPrefetch();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Init listeners.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async initListeners(): Promise<void> {
 | 
			
		||||
        if (this.downloadCourseEnabled) {
 | 
			
		||||
            // Listen for changes in course status.
 | 
			
		||||
            this.courseStatusObserver = CoreEvents.on<CoreEventCourseStatusChanged>(CoreEvents.COURSE_STATUS_CHANGED, (data) => {
 | 
			
		||||
                if (data.courseId == this.course.id || data.courseId == CoreCourseProvider.ALL_COURSES_CLEARED) {
 | 
			
		||||
                    this.updateCourseStatus(data.status);
 | 
			
		||||
                }
 | 
			
		||||
            }, CoreSites.instance.getCurrentSiteId());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Check if the course format requires the view to be refreshed when completion changes.
 | 
			
		||||
        const shouldRefresh = await CoreCourseFormatDelegate.instance.shouldRefreshWhenCompletionChanges(this.course);
 | 
			
		||||
        if (!shouldRefresh) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.completionObserver = CoreEvents.on<CoreEventCompletionModuleViewedData>(
 | 
			
		||||
            CoreEvents.COMPLETION_MODULE_VIEWED,
 | 
			
		||||
            (data) => {
 | 
			
		||||
                if (data && data.courseId == this.course.id) {
 | 
			
		||||
                    this.refreshAfterCompletionChange(true);
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // @todo this.syncObserver = CoreEvents.on(CoreCourseSyncProvider.AUTO_SYNCED, (data) => {
 | 
			
		||||
        //     if (data && data.courseId == this.course.id) {
 | 
			
		||||
        //         this.refreshAfterCompletionChange(false);
 | 
			
		||||
 | 
			
		||||
        //         if (data.warnings && data.warnings[0]) {
 | 
			
		||||
        //             CoreDomUtils.instance.showErrorModal(data.warnings[0]);
 | 
			
		||||
        //         }
 | 
			
		||||
        //     }
 | 
			
		||||
        // });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Init prefetch data if needed.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async initPrefetch(): Promise<void> {
 | 
			
		||||
        if (!this.downloadCourseEnabled) {
 | 
			
		||||
            // Cannot download the whole course, stop.
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Determine the course prefetch status.
 | 
			
		||||
        await this.determineCoursePrefetchIcon();
 | 
			
		||||
 | 
			
		||||
        if (this.prefetchCourseData.icon != 'spinner') {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Course is being downloaded. Get the download promise.
 | 
			
		||||
        const promise = CoreCourseHelper.instance.getCourseDownloadPromise(this.course.id);
 | 
			
		||||
        if (promise) {
 | 
			
		||||
            // There is a download promise. Show an error if it fails.
 | 
			
		||||
            promise.catch((error) => {
 | 
			
		||||
                if (!this.isDestroyed) {
 | 
			
		||||
                    CoreDomUtils.instance.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            // No download, this probably means that the app was closed while downloading. Set previous status.
 | 
			
		||||
            const status = await CoreCourse.instance.setCoursePreviousStatus(this.course.id);
 | 
			
		||||
 | 
			
		||||
            this.updateCourseStatus(status);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fetch and load all the data required for the view.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresh If it's refreshing content.
 | 
			
		||||
     * @param sync If it should try to sync.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async loadData(refresh?: boolean, sync?: boolean): Promise<void> {
 | 
			
		||||
        // First of all, get the course because the data might have changed.
 | 
			
		||||
        const result = await CoreUtils.instance.ignoreErrors(CoreCourseHelper.instance.getCourse(this.course.id));
 | 
			
		||||
 | 
			
		||||
        if (result) {
 | 
			
		||||
            if (this.course.id === result.course.id && 'displayname' in this.course && !('displayname' in result.course)) {
 | 
			
		||||
                result.course.displayname = this.course.displayname;
 | 
			
		||||
            }
 | 
			
		||||
            this.course = result.course;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // @todo: Get the overview files. Maybe move it to format component?
 | 
			
		||||
        // if ('overviewfiles' in this.course && this.course.overviewfiles) {
 | 
			
		||||
        //     this.course.imageThumb = this.course.overviewfiles[0] && this.course.overviewfiles[0].fileurl;
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        if (sync) {
 | 
			
		||||
            // Try to synchronize the course data.
 | 
			
		||||
            // @todo return this.syncProvider.syncCourse(this.course.id).then((result) => {
 | 
			
		||||
            //     if (result.warnings && result.warnings.length) {
 | 
			
		||||
            //         CoreDomUtils.instance.showErrorModal(result.warnings[0]);
 | 
			
		||||
            //     }
 | 
			
		||||
            // }).catch(() => {
 | 
			
		||||
            //     // For now we don't allow manual syncing, so ignore errors.
 | 
			
		||||
            // });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await Promise.all([
 | 
			
		||||
                this.loadSections(refresh),
 | 
			
		||||
                this.loadMenuHandlers(refresh),
 | 
			
		||||
                this.loadCourseFormatOptions(),
 | 
			
		||||
            ]);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            CoreDomUtils.instance.showErrorModalDefault(error, 'core.course.couldnotloadsectioncontent', true);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load course sections.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresh If it's refreshing content.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async loadSections(refresh?: boolean): Promise<void> {
 | 
			
		||||
        // Get all the sections.
 | 
			
		||||
        const sections = await CoreCourse.instance.getSections(this.course.id, false, true);
 | 
			
		||||
 | 
			
		||||
        if (refresh) {
 | 
			
		||||
            // Invalidate the recently downloaded module list. To ensure info can be prefetched.
 | 
			
		||||
            // const modules = CoreCourse.instance.getSectionsModules(sections);
 | 
			
		||||
 | 
			
		||||
            // @todo await this.prefetchDelegate.invalidateModules(modules, this.course.id);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let completionStatus: Record<string, CoreCourseCompletionActivityStatus> = {};
 | 
			
		||||
 | 
			
		||||
        // Get the completion status.
 | 
			
		||||
        if (this.course.enablecompletion !== false) {
 | 
			
		||||
            const sectionWithModules = sections.find((section) => section.modules.length > 0);
 | 
			
		||||
 | 
			
		||||
            if (sectionWithModules && typeof sectionWithModules.modules[0].completion != 'undefined') {
 | 
			
		||||
                // The module already has completion (3.6 onwards). Load the offline completion.
 | 
			
		||||
                await CoreUtils.instance.ignoreErrors(CoreCourseHelper.instance.loadOfflineCompletion(this.course.id, sections));
 | 
			
		||||
            } else {
 | 
			
		||||
                const fetchedData = await CoreUtils.instance.ignoreErrors(
 | 
			
		||||
                    CoreCourse.instance.getActivitiesCompletionStatus(this.course.id),
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
                completionStatus = fetchedData || completionStatus;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Add handlers
 | 
			
		||||
        const result = CoreCourseHelper.instance.addHandlerDataForModules(
 | 
			
		||||
            sections,
 | 
			
		||||
            this.course.id,
 | 
			
		||||
            completionStatus,
 | 
			
		||||
            this.course.fullname,
 | 
			
		||||
            true,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Format the name of each section.
 | 
			
		||||
        result.sections.forEach(async (section: Section) => {
 | 
			
		||||
            const result = await CoreFilterHelper.instance.getFiltersAndFormatText(
 | 
			
		||||
                section.name.trim(),
 | 
			
		||||
                'course',
 | 
			
		||||
                this.course.id,
 | 
			
		||||
                { clean: true, singleLine: true },
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            section.formattedName = result.text;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        this.sections = result.sections;
 | 
			
		||||
 | 
			
		||||
        if (CoreCourseFormatDelegate.instance.canViewAllSections(this.course)) {
 | 
			
		||||
            // Add a fake first section (all sections).
 | 
			
		||||
            this.sections.unshift({
 | 
			
		||||
                id: CoreCourseProvider.ALL_SECTIONS_ID,
 | 
			
		||||
                name: Translate.instance.instant('core.course.allsections'),
 | 
			
		||||
                hasContent: true,
 | 
			
		||||
                summary: '',
 | 
			
		||||
                summaryformat: 1,
 | 
			
		||||
                modules: [],
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Get whether to show the refresher now that we have sections.
 | 
			
		||||
        this.displayRefresher = CoreCourseFormatDelegate.instance.displayRefresher(this.course, this.sections);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load the course menu handlers.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresh If it's refreshing content.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async loadMenuHandlers(refresh?: boolean): Promise<void> {
 | 
			
		||||
        this.courseMenuHandlers = await CoreCourseOptionsDelegate.instance.getMenuHandlersToDisplay(this.course, refresh);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load course format options if needed.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async loadCourseFormatOptions(): Promise<void> {
 | 
			
		||||
 | 
			
		||||
        // Load the course format options when course completion is enabled to show completion progress on sections.
 | 
			
		||||
        if (!this.course.enablecompletion || !CoreCourses.instance.isGetCoursesByFieldAvailable()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ('courseformatoptions' in this.course && this.course.courseformatoptions) {
 | 
			
		||||
            // Already loaded.
 | 
			
		||||
            this.formatOptions = CoreUtils.instance.objectToKeyValueMap(this.course.courseformatoptions, 'name', 'value');
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const course = await CoreUtils.instance.ignoreErrors(CoreCourses.instance.getCourseByField('id', this.course.id));
 | 
			
		||||
 | 
			
		||||
        course && Object.assign(this.course, course);
 | 
			
		||||
 | 
			
		||||
        if (course?.courseformatoptions) {
 | 
			
		||||
            this.formatOptions = CoreUtils.instance.objectToKeyValueMap(course.courseformatoptions, 'name', 'value');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Refresh the data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresher Refresher.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async doRefresh(refresher?: CustomEvent<IonRefresher>): Promise<void> {
 | 
			
		||||
        await CoreUtils.instance.ignoreErrors(this.invalidateData());
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await this.loadData(true, true);
 | 
			
		||||
        } finally {
 | 
			
		||||
            // Do not call doRefresh on the format component if the refresher is defined in the format component
 | 
			
		||||
            // to prevent an inifinite loop.
 | 
			
		||||
            if (this.displayRefresher) {
 | 
			
		||||
                // @todo await CoreUtils.instance.ignoreErrors(this.formatComponent.doRefresh(refresher));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            refresher?.detail.complete();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The completion of any of the modules has changed.
 | 
			
		||||
     *
 | 
			
		||||
     * @param completionData Completion data.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async onCompletionChange(completionData: CoreCourseModuleCompletionData): Promise<void> {
 | 
			
		||||
        const shouldReload = typeof completionData.valueused == 'undefined' || completionData.valueused;
 | 
			
		||||
 | 
			
		||||
        if (!shouldReload) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await CoreUtils.instance.ignoreErrors(this.invalidateData());
 | 
			
		||||
 | 
			
		||||
        await this.refreshAfterCompletionChange(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Invalidate the data.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async invalidateData(): Promise<void> {
 | 
			
		||||
        const promises: Promise<void>[] = [];
 | 
			
		||||
 | 
			
		||||
        promises.push(CoreCourse.instance.invalidateSections(this.course.id));
 | 
			
		||||
        promises.push(CoreCourses.instance.invalidateUserCourses());
 | 
			
		||||
        promises.push(CoreCourseFormatDelegate.instance.invalidateData(this.course, this.sections || []));
 | 
			
		||||
 | 
			
		||||
        if (this.sections) {
 | 
			
		||||
            // @todo promises.push(this.prefetchDelegate.invalidateCourseUpdates(this.course.id));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await Promise.all(promises);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Refresh list after a completion change since there could be new activities.
 | 
			
		||||
     *
 | 
			
		||||
     * @param sync If it should try to sync.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async refreshAfterCompletionChange(sync?: boolean): Promise<void> {
 | 
			
		||||
        // Save scroll position to restore it once done.
 | 
			
		||||
        const scrollElement = await this.content?.getScrollElement();
 | 
			
		||||
        const scrollTop = scrollElement?.scrollTop || 0;
 | 
			
		||||
        const scrollLeft = scrollElement?.scrollLeft || 0;
 | 
			
		||||
 | 
			
		||||
        this.dataLoaded = false;
 | 
			
		||||
        this.content?.scrollToTop(0); // Scroll top so the spinner is seen.
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await this.loadData(true, sync);
 | 
			
		||||
 | 
			
		||||
            // @todo await this.formatComponent.doRefresh(undefined, undefined, true);
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.dataLoaded = true;
 | 
			
		||||
 | 
			
		||||
            // Wait for new content height to be calculated and scroll without animation.
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                this.content?.scrollToPoint(scrollLeft, scrollTop, 0);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determines the prefetch icon of the course.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async determineCoursePrefetchIcon(): Promise<void> {
 | 
			
		||||
        this.prefetchCourseData = await CoreCourseHelper.instance.getCourseStatusIconAndTitle(this.course.id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Prefetch the whole course.
 | 
			
		||||
     */
 | 
			
		||||
    prefetchCourse(): void {
 | 
			
		||||
        try {
 | 
			
		||||
            // @todo await CoreCourseHelper.instance.confirmAndPrefetchCourse(
 | 
			
		||||
            //     this.prefetchCourseData,
 | 
			
		||||
            //     this.course,
 | 
			
		||||
            //     this.sections,
 | 
			
		||||
            //     this.courseHandlers,
 | 
			
		||||
            //     this.courseMenuHandlers,
 | 
			
		||||
            // );
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (this.isDestroyed) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            CoreDomUtils.instance.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Toggle download enabled.
 | 
			
		||||
     */
 | 
			
		||||
    toggleDownload(): void {
 | 
			
		||||
        this.downloadEnabled = !this.downloadEnabled;
 | 
			
		||||
        this.downloadEnabledIcon = this.downloadEnabled ? 'far-check-square' : 'far-square';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the course status icon and title.
 | 
			
		||||
     *
 | 
			
		||||
     * @param status Status to show.
 | 
			
		||||
     */
 | 
			
		||||
    protected updateCourseStatus(status: string): void {
 | 
			
		||||
        this.prefetchCourseData = CoreCourseHelper.instance.getCourseStatusIconAndTitleFromStatus(status);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Open the course summary
 | 
			
		||||
     */
 | 
			
		||||
    openCourseSummary(): void {
 | 
			
		||||
        CoreNavHelper.instance.goInCurrentMainMenuTab('/courses/preview', { course: this.course, avoidOpenCourse: true });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Opens a menu item registered to the delegate.
 | 
			
		||||
     *
 | 
			
		||||
     * @param item Item to open
 | 
			
		||||
     */
 | 
			
		||||
    openMenuItem(item: CoreCourseOptionsMenuHandlerToDisplay): void {
 | 
			
		||||
        const params = Object.assign({ course: this.course }, item.data.pageParams);
 | 
			
		||||
        CoreNavHelper.instance.goInCurrentMainMenuTab(item.data.page, params);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Page destroyed.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnDestroy(): void {
 | 
			
		||||
        this.isDestroyed = true;
 | 
			
		||||
        this.completionObserver?.off();
 | 
			
		||||
        this.courseStatusObserver?.off();
 | 
			
		||||
        this.syncObserver?.off();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * User entered the page.
 | 
			
		||||
     */
 | 
			
		||||
    ionViewDidEnter(): void {
 | 
			
		||||
        // @todo this.formatComponent?.ionViewDidEnter();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * User left the page.
 | 
			
		||||
     */
 | 
			
		||||
    ionViewDidLeave(): void {
 | 
			
		||||
        // @todo this.formatComponent?.ionViewDidLeave();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Section = CoreCourseSectionFormatted & {
 | 
			
		||||
    formattedName?: string;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										33
									
								
								src/core/features/course/pages/index/index-routing.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/core/features/course/pages/index/index-routing.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
			
		||||
// (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 { InjectionToken, ModuleWithProviders, NgModule } from '@angular/core';
 | 
			
		||||
 | 
			
		||||
import { ModuleRoutesConfig } from '@/app/app-routing.module';
 | 
			
		||||
 | 
			
		||||
export const COURSE_INDEX_ROUTES = new InjectionToken('COURSE_INDEX_ROUTES');
 | 
			
		||||
 | 
			
		||||
@NgModule()
 | 
			
		||||
export class CoreCourseIndexRoutingModule {
 | 
			
		||||
 | 
			
		||||
    static forChild(routes: ModuleRoutesConfig): ModuleWithProviders<CoreCourseIndexRoutingModule> {
 | 
			
		||||
        return {
 | 
			
		||||
            ngModule: CoreCourseIndexRoutingModule,
 | 
			
		||||
            providers: [
 | 
			
		||||
                { provide: COURSE_INDEX_ROUTES, multi: true, useValue: routes },
 | 
			
		||||
            ],
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								src/core/features/course/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/core/features/course/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
			
		||||
<ion-header>
 | 
			
		||||
    <ion-toolbar>
 | 
			
		||||
        <ion-buttons slot="start">
 | 
			
		||||
            <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
 | 
			
		||||
        </ion-buttons>
 | 
			
		||||
        <ion-title>
 | 
			
		||||
            <core-format-text [text]="title" contextLevel="course" [contextInstanceId]="course?.id"></core-format-text>
 | 
			
		||||
        </ion-title>
 | 
			
		||||
 | 
			
		||||
        <ion-buttons slot="end"></ion-buttons>
 | 
			
		||||
    </ion-toolbar>
 | 
			
		||||
</ion-header>
 | 
			
		||||
<ion-content>
 | 
			
		||||
    <core-tabs [tabs]="tabs" [hideUntil]="loaded"></core-tabs>
 | 
			
		||||
</ion-content>
 | 
			
		||||
							
								
								
									
										54
									
								
								src/core/features/course/pages/index/index.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/core/features/course/pages/index/index.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,54 @@
 | 
			
		||||
// (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 { Injector, NgModule } from '@angular/core';
 | 
			
		||||
import { CommonModule } from '@angular/common';
 | 
			
		||||
import { RouterModule, ROUTES, Routes } from '@angular/router';
 | 
			
		||||
import { IonicModule } from '@ionic/angular';
 | 
			
		||||
import { TranslateModule } from '@ngx-translate/core';
 | 
			
		||||
 | 
			
		||||
import { resolveModuleRoutes } from '@/app/app-routing.module';
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { CoreCourseIndexPage } from './index';
 | 
			
		||||
import { COURSE_INDEX_ROUTES } from './index-routing.module';
 | 
			
		||||
 | 
			
		||||
function buildRoutes(injector: Injector): Routes {
 | 
			
		||||
    const routes = resolveModuleRoutes(injector, COURSE_INDEX_ROUTES);
 | 
			
		||||
 | 
			
		||||
    return [
 | 
			
		||||
        {
 | 
			
		||||
            path: '',
 | 
			
		||||
            component: CoreCourseIndexPage,
 | 
			
		||||
            children: routes.children,
 | 
			
		||||
        },
 | 
			
		||||
        ...routes.siblings,
 | 
			
		||||
    ];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    providers: [
 | 
			
		||||
        { provide: ROUTES, multi: true, useFactory: buildRoutes, deps: [Injector] },
 | 
			
		||||
    ],
 | 
			
		||||
    imports: [
 | 
			
		||||
        CommonModule,
 | 
			
		||||
        IonicModule,
 | 
			
		||||
        TranslateModule.forChild(),
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
    ],
 | 
			
		||||
    declarations: [
 | 
			
		||||
        CoreCourseIndexPage,
 | 
			
		||||
    ],
 | 
			
		||||
    exports: [RouterModule],
 | 
			
		||||
})
 | 
			
		||||
export class CoreCourseIndexPageModule {}
 | 
			
		||||
							
								
								
									
										191
									
								
								src/core/features/course/pages/index/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								src/core/features/course/pages/index/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,191 @@
 | 
			
		||||
// (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 { Component, ViewChild, OnDestroy, OnInit } from '@angular/core';
 | 
			
		||||
import { ActivatedRoute, Params } from '@angular/router';
 | 
			
		||||
 | 
			
		||||
import { CoreTab, CoreTabsComponent } from '@components/tabs/tabs';
 | 
			
		||||
import { CoreCourseFormatDelegate } from '../../services/format-delegate';
 | 
			
		||||
import { CoreCourseOptionsDelegate } from '../../services/course-options-delegate';
 | 
			
		||||
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
 | 
			
		||||
import { CoreEventObserver, CoreEvents, CoreEventSelectCourseTabData } from '@singletons/events';
 | 
			
		||||
import { CoreCourse, CoreCourseModuleData } from '@features/course/services/course';
 | 
			
		||||
import { CoreCourseHelper } from '@features/course/services/course-helper';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreNavHelper } from '@services/nav-helper';
 | 
			
		||||
import { CoreObject } from '@singletons/object';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Page that displays the list of courses the user is enrolled in.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'page-core-course-index',
 | 
			
		||||
    templateUrl: 'index.html',
 | 
			
		||||
})
 | 
			
		||||
export class CoreCourseIndexPage implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
    @ViewChild(CoreTabsComponent) tabsComponent?: CoreTabsComponent;
 | 
			
		||||
 | 
			
		||||
    title?: string;
 | 
			
		||||
    course?: CoreCourseAnyCourseData;
 | 
			
		||||
    tabs: CourseTab[] = [];
 | 
			
		||||
    loaded = false;
 | 
			
		||||
 | 
			
		||||
    protected currentPagePath = '';
 | 
			
		||||
    protected selectTabObserver: CoreEventObserver;
 | 
			
		||||
    protected firstTabName?: string;
 | 
			
		||||
    protected contentsTab: CoreTab = {
 | 
			
		||||
        page: 'contents',
 | 
			
		||||
        title: 'core.course.contents',
 | 
			
		||||
        pageParams: {},
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        protected route: ActivatedRoute,
 | 
			
		||||
    ) {
 | 
			
		||||
        this.selectTabObserver = CoreEvents.on<CoreEventSelectCourseTabData>(CoreEvents.SELECT_COURSE_TAB, (data) => {
 | 
			
		||||
            if (!data.name) {
 | 
			
		||||
                // If needed, set sectionId and sectionNumber. They'll only be used if the content tabs hasn't been loaded yet.
 | 
			
		||||
                if (data.sectionId) {
 | 
			
		||||
                    this.contentsTab.pageParams!.sectionId = data.sectionId;
 | 
			
		||||
                }
 | 
			
		||||
                if (data.sectionNumber) {
 | 
			
		||||
                    this.contentsTab.pageParams!.sectionNumber = data.sectionNumber;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Select course contents.
 | 
			
		||||
                this.tabsComponent?.selectByIndex(0);
 | 
			
		||||
            } else if (this.tabs) {
 | 
			
		||||
                const index = this.tabs.findIndex((tab) => tab.name == data.name);
 | 
			
		||||
 | 
			
		||||
                if (index >= 0) {
 | 
			
		||||
                    this.tabsComponent?.selectByIndex(index + 1);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Component being initialized.
 | 
			
		||||
     */
 | 
			
		||||
    async ngOnInit(): Promise<void> {
 | 
			
		||||
        // Get params.
 | 
			
		||||
        this.course = this.route.snapshot.queryParams['course'];
 | 
			
		||||
        this.firstTabName = this.route.snapshot.queryParams['selectedTab'];
 | 
			
		||||
        const module: CoreCourseModuleData | undefined = this.route.snapshot.queryParams['module'];
 | 
			
		||||
        const modParams: Params | undefined = this.route.snapshot.queryParams['modParams'];
 | 
			
		||||
 | 
			
		||||
        this.currentPagePath = CoreNavHelper.instance.getCurrentPage();
 | 
			
		||||
        this.contentsTab.page = CoreTextUtils.instance.concatenatePaths(this.currentPagePath, this.contentsTab.page);
 | 
			
		||||
        this.contentsTab.pageParams = CoreObject.removeUndefined({
 | 
			
		||||
            course: this.course,
 | 
			
		||||
            sectionId: this.route.snapshot.queryParams['sectionId'],
 | 
			
		||||
            sectionNumber: this.route.snapshot.queryParams['sectionNumber'],
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if (module) {
 | 
			
		||||
            this.contentsTab.pageParams!.moduleId = module.id;
 | 
			
		||||
            CoreCourseHelper.instance.openModule(module, this.course!.id, this.contentsTab.pageParams!.sectionId, modParams);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.tabs.push(this.contentsTab);
 | 
			
		||||
        this.loaded = true;
 | 
			
		||||
 | 
			
		||||
        await Promise.all([
 | 
			
		||||
            this.loadCourseHandlers(),
 | 
			
		||||
            this.loadTitle(),
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load course option handlers.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async loadCourseHandlers(): Promise<void> {
 | 
			
		||||
        // Load the course handlers.
 | 
			
		||||
        const handlers = await CoreCourseOptionsDelegate.instance.getHandlersToDisplay(this.course!, false, false);
 | 
			
		||||
 | 
			
		||||
        this.tabs.concat(handlers.map(handler => handler.data));
 | 
			
		||||
 | 
			
		||||
        let tabToLoad: number | undefined;
 | 
			
		||||
 | 
			
		||||
        // Add the courseId to the handler component data.
 | 
			
		||||
        handlers.forEach((handler, index) => {
 | 
			
		||||
            handler.data.page = CoreTextUtils.instance.concatenatePaths(this.currentPagePath, handler.data.page);
 | 
			
		||||
            handler.data.pageParams = handler.data.pageParams || {};
 | 
			
		||||
            handler.data.pageParams.courseId = this.course!.id;
 | 
			
		||||
 | 
			
		||||
            // Check if this handler should be the first selected tab.
 | 
			
		||||
            if (this.firstTabName && handler.name == this.firstTabName) {
 | 
			
		||||
                tabToLoad = index + 1;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Select the tab if needed.
 | 
			
		||||
        this.firstTabName = undefined;
 | 
			
		||||
        if (tabToLoad) {
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                this.tabsComponent?.selectByIndex(tabToLoad!);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load title for the page.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async loadTitle(): Promise<void> {
 | 
			
		||||
        // Get the title to display initially.
 | 
			
		||||
        this.title =  CoreCourseFormatDelegate.instance.getCourseTitle(this.course!);
 | 
			
		||||
 | 
			
		||||
        // Load sections.
 | 
			
		||||
        const sections = await CoreUtils.instance.ignoreErrors(CoreCourse.instance.getSections(this.course!.id, false, true));
 | 
			
		||||
 | 
			
		||||
        if (!sections) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Get the title again now that we have sections.
 | 
			
		||||
        this.title = CoreCourseFormatDelegate.instance.getCourseTitle(this.course!, sections);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Page destroyed.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnDestroy(): void {
 | 
			
		||||
        this.selectTabObserver?.off();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * User entered the page.
 | 
			
		||||
     */
 | 
			
		||||
    ionViewDidEnter(): void {
 | 
			
		||||
        this.tabsComponent?.ionViewDidEnter();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * User left the page.
 | 
			
		||||
     */
 | 
			
		||||
    ionViewDidLeave(): void {
 | 
			
		||||
        this.tabsComponent?.ionViewDidLeave();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CourseTab = CoreTab & {
 | 
			
		||||
    name?: string;
 | 
			
		||||
};
 | 
			
		||||
@ -122,7 +122,6 @@ export class CoreCourseHelperProvider {
 | 
			
		||||
    protected logger: CoreLogger;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
 | 
			
		||||
        this.logger = CoreLogger.getInstance('CoreCourseHelperProvider');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -138,17 +137,24 @@ export class CoreCourseHelperProvider {
 | 
			
		||||
     * @return Whether the sections have content.
 | 
			
		||||
     */
 | 
			
		||||
    addHandlerDataForModules(
 | 
			
		||||
        sections: CoreCourseSectionFormatted[],
 | 
			
		||||
        sections: CoreCourseSection[],
 | 
			
		||||
        courseId: number,
 | 
			
		||||
        completionStatus?: Record<string, CoreCourseCompletionActivityStatus>,
 | 
			
		||||
        courseName?: string,
 | 
			
		||||
        forCoursePage = false,
 | 
			
		||||
    ): boolean {
 | 
			
		||||
    ): { hasContent: boolean; sections: CoreCourseSectionFormatted[] } {
 | 
			
		||||
 | 
			
		||||
        const formattedSections: CoreCourseSectionFormatted[] = sections;
 | 
			
		||||
        let hasContent = false;
 | 
			
		||||
 | 
			
		||||
        sections.forEach((section) => {
 | 
			
		||||
            if (!section || !this.sectionHasContent(section) || !section.modules) {
 | 
			
		||||
        formattedSections.forEach((section) => {
 | 
			
		||||
            if (!section || !section.modules) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            section.hasContent = this.sectionHasContent(section);
 | 
			
		||||
 | 
			
		||||
            if (!section.hasContent) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -189,7 +195,7 @@ export class CoreCourseHelperProvider {
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return hasContent;
 | 
			
		||||
        return { hasContent, sections: formattedSections };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -821,8 +827,24 @@ export class CoreCourseHelperProvider {
 | 
			
		||||
     * @param modParams Params to pass to the module
 | 
			
		||||
     * @param True if module can be opened, false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    openModule(): void {
 | 
			
		||||
        // @todo params and logic
 | 
			
		||||
    openModule(module: CoreCourseModuleDataFormatted, courseId: number, sectionId?: number, modParams?: Params): boolean {
 | 
			
		||||
        if (!module.handlerData) {
 | 
			
		||||
            module.handlerData = CoreCourseModuleDelegate.instance.getModuleDataFor(
 | 
			
		||||
                module.modname,
 | 
			
		||||
                module,
 | 
			
		||||
                courseId,
 | 
			
		||||
                sectionId,
 | 
			
		||||
                false,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (module.handlerData?.action) {
 | 
			
		||||
            module.handlerData.action(new Event('click'), module, courseId, { animated: false }, modParams);
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -1054,6 +1076,7 @@ export class CoreCourseHelper extends makeSingleton(CoreCourseHelperProvider) {}
 | 
			
		||||
 * Section with calculated data.
 | 
			
		||||
 */
 | 
			
		||||
export type CoreCourseSectionFormatted = Omit<CoreCourseSection, 'modules'> & {
 | 
			
		||||
    hasContent?: boolean;
 | 
			
		||||
    modules: CoreCourseModuleDataFormatted[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -13,12 +13,18 @@
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
// @todo test delegate
 | 
			
		||||
 | 
			
		||||
import { Injectable, Type } from '@angular/core';
 | 
			
		||||
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { CoreDelegate, CoreDelegateHandler, CoreDelegateToDisplay } from '@classes/delegate';
 | 
			
		||||
import { CoreEvents } from '@singletons/events';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreUtils, PromiseDefer } from '@services/utils/utils';
 | 
			
		||||
import { CoreCourses, CoreCoursesProvider, CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses';
 | 
			
		||||
import {
 | 
			
		||||
    CoreCourseAnyCourseData,
 | 
			
		||||
    CoreCourseAnyCourseDataWithOptions,
 | 
			
		||||
    CoreCourses,
 | 
			
		||||
    CoreCoursesProvider,
 | 
			
		||||
    CoreCourseUserAdminOrNavOptionIndexed,
 | 
			
		||||
} from '@features/courses/services/courses';
 | 
			
		||||
import { CoreCourseProvider } from './course';
 | 
			
		||||
import { Params } from '@angular/router';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
@ -61,7 +67,7 @@ export interface CoreCourseOptionsHandler extends CoreDelegateHandler {
 | 
			
		||||
     * @return Data or promise resolved with the data.
 | 
			
		||||
     */
 | 
			
		||||
    getDisplayData?(
 | 
			
		||||
        course: CoreEnrolledCourseDataWithExtraInfoAndOptions,
 | 
			
		||||
        course: CoreCourseAnyCourseDataWithOptions,
 | 
			
		||||
    ): CoreCourseOptionsHandlerData | Promise<CoreCourseOptionsHandlerData>;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -98,7 +104,7 @@ export interface CoreCourseOptionsMenuHandler extends CoreCourseOptionsHandler {
 | 
			
		||||
     * @return Data or promise resolved with data.
 | 
			
		||||
     */
 | 
			
		||||
    getMenuDisplayData(
 | 
			
		||||
        course: CoreEnrolledCourseDataWithExtraInfoAndOptions,
 | 
			
		||||
        course: CoreCourseAnyCourseDataWithOptions,
 | 
			
		||||
    ): CoreCourseOptionsMenuHandlerData | Promise<CoreCourseOptionsMenuHandlerData>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -117,15 +123,14 @@ export interface CoreCourseOptionsHandlerData {
 | 
			
		||||
    class?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The component to render the handler. It must be the component class, not the name or an instance.
 | 
			
		||||
     * When the component is created, it will receive the courseId as input.
 | 
			
		||||
     * Path of the page to load for the handler.
 | 
			
		||||
     */
 | 
			
		||||
    component: Type<unknown>;
 | 
			
		||||
    page: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Data to pass to the component. All the properties in this object will be passed to the component as inputs.
 | 
			
		||||
     * Params to pass to the page (other than 'courseId' which is always sent).
 | 
			
		||||
     */
 | 
			
		||||
    componentData?: Record<string | number, unknown>;
 | 
			
		||||
    pageParams?: Params;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -143,7 +148,7 @@ export interface CoreCourseOptionsMenuHandlerData {
 | 
			
		||||
    class?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Name of the page to load for the handler.
 | 
			
		||||
     * Path of the page to load for the handler.
 | 
			
		||||
     */
 | 
			
		||||
    page: string;
 | 
			
		||||
 | 
			
		||||
@ -161,22 +166,12 @@ export interface CoreCourseOptionsMenuHandlerData {
 | 
			
		||||
/**
 | 
			
		||||
 * Data returned by the delegate for each handler.
 | 
			
		||||
 */
 | 
			
		||||
export interface CoreCourseOptionsHandlerToDisplay {
 | 
			
		||||
export interface CoreCourseOptionsHandlerToDisplay extends CoreDelegateToDisplay {
 | 
			
		||||
    /**
 | 
			
		||||
     * Data to display.
 | 
			
		||||
     */
 | 
			
		||||
    data: CoreCourseOptionsHandlerData;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Name of the handler, or name and sub context (AddonMessages, AddonMessages:blockContact, ...).
 | 
			
		||||
     */
 | 
			
		||||
    name: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The highest priority is displayed first.
 | 
			
		||||
     */
 | 
			
		||||
    priority?: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when a course is downloaded. It should prefetch all the data to be able to see the addon in offline.
 | 
			
		||||
     *
 | 
			
		||||
@ -368,7 +363,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
 | 
			
		||||
     * @return Promise resolved with array of handlers.
 | 
			
		||||
     */
 | 
			
		||||
    getHandlersToDisplay(
 | 
			
		||||
        course: CoreEnrolledCourseDataWithExtraInfoAndOptions,
 | 
			
		||||
        course: CoreCourseAnyCourseData,
 | 
			
		||||
        refresh = false,
 | 
			
		||||
        isGuest = false,
 | 
			
		||||
        navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
 | 
			
		||||
@ -390,7 +385,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
 | 
			
		||||
     * @return Promise resolved with array of handlers.
 | 
			
		||||
     */
 | 
			
		||||
    getMenuHandlersToDisplay(
 | 
			
		||||
        course: CoreEnrolledCourseDataWithExtraInfoAndOptions,
 | 
			
		||||
        course: CoreCourseAnyCourseData,
 | 
			
		||||
        refresh = false,
 | 
			
		||||
        isGuest = false,
 | 
			
		||||
        navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
 | 
			
		||||
@ -414,28 +409,31 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
 | 
			
		||||
     */
 | 
			
		||||
    protected async getHandlersToDisplayInternal(
 | 
			
		||||
        menu: boolean,
 | 
			
		||||
        course: CoreEnrolledCourseDataWithExtraInfoAndOptions,
 | 
			
		||||
        course: CoreCourseAnyCourseData,
 | 
			
		||||
        refresh = false,
 | 
			
		||||
        isGuest = false,
 | 
			
		||||
        navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
 | 
			
		||||
        admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
 | 
			
		||||
    ): Promise<CoreCourseOptionsHandlerToDisplay[] | CoreCourseOptionsMenuHandlerToDisplay[]> {
 | 
			
		||||
 | 
			
		||||
        const courseWithOptions: CoreCourseAnyCourseDataWithOptions = course;
 | 
			
		||||
        const accessData = {
 | 
			
		||||
            type: isGuest ? CoreCourseProvider.ACCESS_GUEST : CoreCourseProvider.ACCESS_DEFAULT,
 | 
			
		||||
        };
 | 
			
		||||
        const handlersToDisplay: CoreCourseOptionsHandlerToDisplay[] | CoreCourseOptionsMenuHandlerToDisplay[] = [];
 | 
			
		||||
 | 
			
		||||
        if (navOptions) {
 | 
			
		||||
            course.navOptions = navOptions;
 | 
			
		||||
            courseWithOptions.navOptions = navOptions;
 | 
			
		||||
        }
 | 
			
		||||
        if (admOptions) {
 | 
			
		||||
            course.admOptions = admOptions;
 | 
			
		||||
            courseWithOptions.admOptions = admOptions;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await this.loadCourseOptions(course, refresh);
 | 
			
		||||
        await this.loadCourseOptions(courseWithOptions, refresh);
 | 
			
		||||
 | 
			
		||||
        // Call getHandlersForAccess to make sure the handlers have been loaded.
 | 
			
		||||
        await this.getHandlersForAccess(course.id, refresh, accessData, course.navOptions, course.admOptions);
 | 
			
		||||
        await this.getHandlersForAccess(course.id, refresh, accessData, courseWithOptions.navOptions, courseWithOptions.admOptions);
 | 
			
		||||
 | 
			
		||||
        const promises: Promise<void>[] = [];
 | 
			
		||||
 | 
			
		||||
        let handlerList: CoreCourseOptionsMenuHandler[] | CoreCourseOptionsHandler[];
 | 
			
		||||
@ -450,7 +448,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
 | 
			
		||||
                ? (handler as CoreCourseOptionsMenuHandler).getMenuDisplayData
 | 
			
		||||
                : (handler as CoreCourseOptionsHandler).getDisplayData;
 | 
			
		||||
 | 
			
		||||
            promises.push(Promise.resolve(getFunction!.call(handler, course)).then((data) => {
 | 
			
		||||
            promises.push(Promise.resolve(getFunction!.call(handler, courseWithOptions)).then((data) => {
 | 
			
		||||
                handlersToDisplay.push({
 | 
			
		||||
                    data: data,
 | 
			
		||||
                    priority: handler.priority,
 | 
			
		||||
@ -587,7 +585,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate<CoreCourseOpt
 | 
			
		||||
     * @param refresh True if it should refresh the list.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async loadCourseOptions(course: CoreEnrolledCourseDataWithExtraInfoAndOptions, refresh = false): Promise<void> {
 | 
			
		||||
    protected async loadCourseOptions(course: CoreCourseAnyCourseDataWithOptions, refresh = false): Promise<void> {
 | 
			
		||||
        if (CoreCourses.instance.canGetAdminAndNavOptions() &&
 | 
			
		||||
                (typeof course.navOptions == 'undefined' || typeof course.admOptions == 'undefined' || refresh)) {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -981,7 +981,7 @@ export class CoreCourseProvider {
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async openCourse(course: CoreCourseAnyCourseData | { id: number }, params?: Params): Promise<void> {
 | 
			
		||||
        // @todo const loading = await CoreDomUtils.instance.showModalLoading();
 | 
			
		||||
        const loading = await CoreDomUtils.instance.showModalLoading();
 | 
			
		||||
 | 
			
		||||
        // Wait for site plugins to be fetched.
 | 
			
		||||
        // @todo await this.sitePluginsProvider.waitFetchPlugins();
 | 
			
		||||
@ -992,14 +992,13 @@ export class CoreCourseProvider {
 | 
			
		||||
            course = result.course;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /* @todo
 | 
			
		||||
        if (!this.sitePluginsProvider.sitePluginPromiseExists('format_' + course.format)) {
 | 
			
		||||
        if (course) { // @todo Replace with: if (!this.sitePluginsProvider.sitePluginPromiseExists('format_' + course.format)) {
 | 
			
		||||
            // No custom format plugin. We don't need to wait for anything.
 | 
			
		||||
            await CoreCourseFormatDelegate.instance.openCourse(course, params);
 | 
			
		||||
            await CoreCourseFormatDelegate.instance.openCourse(<CoreCourseAnyCourseData> course, params);
 | 
			
		||||
            loading.dismiss();
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        } */
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // This course uses a custom format plugin, wait for the format plugin to finish loading.
 | 
			
		||||
        try {
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@ import { Injectable } from '@angular/core';
 | 
			
		||||
import { Params } from '@angular/router';
 | 
			
		||||
 | 
			
		||||
import { CoreCourseAnyCourseData, CoreCourses } from '@features/courses/services/courses';
 | 
			
		||||
import { CoreNavHelper } from '@services/nav-helper';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreCourseSection } from '../course';
 | 
			
		||||
import { CoreCourseFormatHandler } from '../format-delegate';
 | 
			
		||||
@ -175,7 +176,7 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
 | 
			
		||||
        Object.assign(params, { course: course });
 | 
			
		||||
 | 
			
		||||
        // Don't return the .push promise, we don't want to display a loading modal during the page transition.
 | 
			
		||||
        // @todo navCtrl.push('CoreCourseSectionPage', params);
 | 
			
		||||
        CoreNavHelper.instance.goInCurrentMainMenuTab('course', params);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -49,8 +49,8 @@ export class CoreCourseModuleDefaultHandler implements CoreCourseModuleHandler {
 | 
			
		||||
    getData(
 | 
			
		||||
        module: CoreCourseModuleData | CoreCourseModuleBasicInfo,
 | 
			
		||||
        courseId: number, // eslint-disable-line @typescript-eslint/no-unused-vars
 | 
			
		||||
        sectionId: number, // eslint-disable-line @typescript-eslint/no-unused-vars
 | 
			
		||||
        forCoursePage: boolean, // eslint-disable-line @typescript-eslint/no-unused-vars
 | 
			
		||||
        sectionId?: number, // eslint-disable-line @typescript-eslint/no-unused-vars
 | 
			
		||||
        forCoursePage?: boolean, // eslint-disable-line @typescript-eslint/no-unused-vars
 | 
			
		||||
    ): CoreCourseModuleHandlerData {
 | 
			
		||||
        // Return the default data.
 | 
			
		||||
        const defaultData: CoreCourseModuleHandlerData = {
 | 
			
		||||
 | 
			
		||||
@ -54,8 +54,8 @@ export interface CoreCourseModuleHandler extends CoreDelegateHandler {
 | 
			
		||||
    getData(
 | 
			
		||||
        module: CoreCourseModuleData | CoreCourseModuleBasicInfo,
 | 
			
		||||
        courseId: number,
 | 
			
		||||
        sectionId: number,
 | 
			
		||||
        forCoursePage: boolean,
 | 
			
		||||
        sectionId?: number,
 | 
			
		||||
        forCoursePage?: boolean,
 | 
			
		||||
    ): CoreCourseModuleHandlerData;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -270,7 +270,7 @@ export class CoreCourseModuleDelegateService extends CoreDelegate<CoreCourseModu
 | 
			
		||||
        modname: string,
 | 
			
		||||
        module: CoreCourseModuleData | CoreCourseModuleBasicInfo,
 | 
			
		||||
        courseId: number,
 | 
			
		||||
        sectionId: number,
 | 
			
		||||
        sectionId?: number,
 | 
			
		||||
        forCoursePage?: boolean,
 | 
			
		||||
    ): CoreCourseModuleHandlerData | undefined {
 | 
			
		||||
        return this.executeFunctionOnEnabled<CoreCourseModuleHandlerData>(
 | 
			
		||||
 | 
			
		||||
@ -1589,4 +1589,15 @@ type CoreCourseSetFavouriteCoursesWSParams = {
 | 
			
		||||
    }[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Any of the possible course data.
 | 
			
		||||
 */
 | 
			
		||||
export type CoreCourseAnyCourseData = CoreEnrolledCourseData | CoreCourseSearchedData | CoreCourseGetCoursesData;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Course data with admin and navigation option availability.
 | 
			
		||||
 */
 | 
			
		||||
export type CoreCourseAnyCourseDataWithOptions = CoreCourseAnyCourseData & {
 | 
			
		||||
    navOptions?: CoreCourseUserAdminOrNavOptionIndexed;
 | 
			
		||||
    admOptions?: CoreCourseUserAdminOrNavOptionIndexed;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@ import { Subscription } from 'rxjs';
 | 
			
		||||
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
			
		||||
import { CoreTabsComponent } from '@components/tabs/tabs';
 | 
			
		||||
import { CoreTab, CoreTabsComponent } from '@components/tabs/tabs';
 | 
			
		||||
import { CoreMainMenuHomeDelegate, CoreMainMenuHomeHandlerToDisplay } from '../../services/home-delegate';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -33,7 +33,7 @@ export class CoreMainMenuHomePage implements OnInit {
 | 
			
		||||
    @ViewChild(CoreTabsComponent) tabsComponent?: CoreTabsComponent;
 | 
			
		||||
 | 
			
		||||
    siteName!: string;
 | 
			
		||||
    tabs: CoreMainMenuHomeHandlerToDisplay[] = [];
 | 
			
		||||
    tabs: CoreTab[] = [];
 | 
			
		||||
    loaded = false;
 | 
			
		||||
    selectedTab?: number;
 | 
			
		||||
 | 
			
		||||
@ -68,9 +68,10 @@ export class CoreMainMenuHomePage implements OnInit {
 | 
			
		||||
            const tab = this.tabs.find((tab) => tab.title == handler.title);
 | 
			
		||||
 | 
			
		||||
            return tab || handler;
 | 
			
		||||
        })
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Sort them by priority so new handlers are in the right position.
 | 
			
		||||
            .sort((a, b) => (b.priority || 0) - (a.priority || 0));
 | 
			
		||||
        newTabs.sort((a, b) => (b.priority || 0) - (a.priority || 0));
 | 
			
		||||
 | 
			
		||||
        if (typeof this.selectedTab == 'undefined' && newTabs.length > 0) {
 | 
			
		||||
            let maxPriority = 0;
 | 
			
		||||
 | 
			
		||||
@ -131,15 +131,14 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
 | 
			
		||||
            // Check "Include a topic section" setting from numsections.
 | 
			
		||||
            this.section = config.numsections ? sections.find((section) => section.section == 1) : undefined;
 | 
			
		||||
            if (this.section) {
 | 
			
		||||
                this.section.hasContent = false;
 | 
			
		||||
                this.section.hasContent = CoreCourseHelper.instance.sectionHasContent(this.section);
 | 
			
		||||
                this.hasContent = CoreCourseHelper.instance.addHandlerDataForModules(
 | 
			
		||||
                const result = CoreCourseHelper.instance.addHandlerDataForModules(
 | 
			
		||||
                    [this.section],
 | 
			
		||||
                    this.siteHomeId,
 | 
			
		||||
                    undefined,
 | 
			
		||||
                    undefined,
 | 
			
		||||
                    true,
 | 
			
		||||
                ) || this.hasContent;
 | 
			
		||||
                );
 | 
			
		||||
                this.hasContent = result.hasContent || this.hasContent;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Add log in Moodle.
 | 
			
		||||
 | 
			
		||||
@ -264,10 +264,25 @@ export type CoreEventFormActionData = CoreEventSiteData & {
 | 
			
		||||
    online?: boolean; // Whether the data was sent to server or not. Only when submitting.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data passed to NOTIFICATION_SOUND_CHANGED event.
 | 
			
		||||
 */
 | 
			
		||||
export type CoreEventNotificationSoundChangedData = CoreEventSiteData & {
 | 
			
		||||
    enabled: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data passed to SELECT_COURSE_TAB event.
 | 
			
		||||
 */
 | 
			
		||||
export type CoreEventSelectCourseTabData = CoreEventSiteData & {
 | 
			
		||||
    name?: string; // Name of the tab's handler. If not set, load course contents.
 | 
			
		||||
    sectionId?: number;
 | 
			
		||||
    sectionNumber?: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data passed to COMPLETION_MODULE_VIEWED event.
 | 
			
		||||
 */
 | 
			
		||||
export type CoreEventCompletionModuleViewedData = CoreEventSiteData & {
 | 
			
		||||
    courseId?: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user