MOBILE-3099 module: Add preview page to show restricted activities

main
Pau Ferrer Ocaña 2021-12-01 12:26:46 +01:00
parent 132007b207
commit 007835b6b8
17 changed files with 323 additions and 148 deletions

View File

@ -19,7 +19,7 @@ import { CoreModuleHandlerBase } from '@features/course/classes/module-base-hand
import { CoreCourse } from '@features/course/services/course'; import { CoreCourse } from '@features/course/services/course';
import { CoreCourseModule } from '@features/course/services/course-helper'; import { CoreCourseModule } from '@features/course/services/course-helper';
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate'; import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate';
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; import { CoreNavigationOptions } from '@services/navigator';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { makeSingleton } from '@singletons'; import { makeSingleton } from '@singletons';
@ -90,12 +90,7 @@ export class AddonModUrlModuleHandlerService extends CoreModuleHandlerBase imple
if (shouldOpen) { if (shouldOpen) {
openUrl(module, courseId); openUrl(module, courseId);
} else { } else {
options = options || {}; this.openActivityPage(module, courseId, options);
options.params = options.params || {};
Object.assign(options.params, { module });
const routeParams = '/' + courseId + '/' + module.id;
CoreNavigator.navigateToSitePath(AddonModUrlModuleHandlerService.PAGE_NAME + routeParams, options);
} }
} finally { } finally {
modal.dismiss(); modal.dismiss();

View File

@ -51,14 +51,31 @@ export class CoreModuleHandlerBase implements Partial<CoreCourseModuleHandler> {
courseId: number, courseId: number,
options?: CoreNavigationOptions, options?: CoreNavigationOptions,
): Promise<void> => { ): Promise<void> => {
options = options || {}; await this.openActivityPage(module, courseId, options);
options.params = options.params || {};
Object.assign(options.params, { module });
const routeParams = '/' + courseId + '/' + module.id;
await CoreNavigator.navigateToSitePath(this.pageName + routeParams, options);
}, },
}; };
} }
/**
* Opens the activity page.
*
* @param module The module object.
* @param courseId The course ID.
* @param options Options for the navigation.
* @return Promise resolved when done.
*/
async openActivityPage(module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions): Promise<void> {
if (!CoreCourse.moduleHasView(module)) {
return;
}
options = options || {};
options.params = options.params || {};
Object.assign(options.params, { module });
const routeParams = '/' + courseId + '/' + module.id;
await CoreNavigator.navigateToSitePath(this.pageName + routeParams, options);
}
} }

View File

@ -13,24 +13,25 @@
<ion-item class="ion-text-wrap" *ngIf="description" lines="none"> <ion-item class="ion-text-wrap" *ngIf="description" lines="none">
<ion-label> <ion-label>
<core-format-text [text]="description" [component]="component" [componentId]="componentId" contextLevel="module" <core-format-text [text]="description" [component]="component" [componentId]="componentId" contextLevel="module"
[contextInstanceId]="module.id" [courseId]="courseId" [maxHeight]="120"> [contextInstanceId]="module.id" [courseId]="courseId" [maxHeight]="expandDescription ? null : 120">
</core-format-text> </core-format-text>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ng-content select="[description]"></ng-content> <ng-content select="[description]"></ng-content>
<ion-item class="ion-text-wrap" lines="none" *ngIf="showCompletion && (module.dates?.length || module.completiondata)"> <ion-item class="ion-text-wrap" lines="none" *ngIf="showCompletion && (module.dates?.length ||
(module.completiondata && (module.completiondata.isautomatic || showManualCompletion) && module.uservisible))">
<ion-label> <ion-label>
<!-- Activity dates. --> <!-- Activity dates. -->
<div *ngIf="module.dates && module.dates.length" class="core-module-dates"> <div *ngIf="module.dates?.length" class="core-module-dates">
<p *ngFor="let date of module.dates"> <p *ngFor="let date of module.dates">
<strong>{{ date.label }}</strong> {{ date.timestamp * 1000 | coreFormatDate:'strftimedatetime' }} <strong>{{ date.label }}</strong> {{ date.timestamp * 1000 | coreFormatDate:'strftimedatetime' }}
</p> </p>
</div> </div>
<!-- Module completion. --> <!-- Module completion. -->
<core-course-module-completion *ngIf="module.completiondata" [completion]="module.completiondata" [moduleName]="module.name" <core-course-module-completion *ngIf="module.completiondata && module.uservisible" [completion]="module.completiondata"
[moduleId]="module.id" [showCompletionConditions]="true" [showManualCompletion]="true" [moduleName]="module.name" [moduleId]="module.id" [showCompletionConditions]="true"
(completionChanged)="completionChanged.emit($event)"> [showManualCompletion]="showManualCompletion" (completionChanged)="completionChanged.emit($event)">
</core-course-module-completion> </core-course-module-completion>
</ion-label> </ion-label>
</ion-item> </ion-item>

View File

@ -36,12 +36,14 @@ import { CoreSites } from '@services/sites';
export class CoreCourseModuleInfoComponent implements OnInit { export class CoreCourseModuleInfoComponent implements OnInit {
@Input() module!: CoreCourseModule; // The module to render. @Input() module!: CoreCourseModule; // The module to render.
@Input() showManualCompletion = true; // Whether to show manual completion, true by default.
@Input() courseId!: number; // The courseId the module belongs to. @Input() courseId!: number; // The courseId the module belongs to.
@Input() component!: string; // Component for format text directive. @Input() component!: string; // Component for format text directive.
@Input() componentId!: string | number; // Component ID to use in conjunction with the component. @Input() componentId!: string | number; // Component ID to use in conjunction with the component.
@Input() description?: string | false; // The description to display. If false, no description will be shown. @Input() description?: string | false; // The description to display. If false, no description will be shown.
@Input() expandDescription = false; // If the description should be expanded by default.
@Input() hasDataToSync = false; // If the activity has any data to be synced. @Input() hasDataToSync = false; // If the activity has any data to be synced.

View File

@ -2,20 +2,22 @@
<ng-container *ngIf="completion.istrackeduser"> <ng-container *ngIf="completion.istrackeduser">
<ng-container *ngIf="completion.state"> <ng-container *ngIf="completion.state">
<ion-button color="success" fill="outline" [attr.aria-label]="accessibleDescription" (click)="completionClicked($event)"> <ion-button color="success" expand="block" fill="outline" [attr.aria-label]="accessibleDescription"
(click)="completionClicked($event)">
<ion-icon name="fas-check" slot="start" aria-hidden="true"></ion-icon> <ion-icon name="fas-check" slot="start" aria-hidden="true"></ion-icon>
{{ 'core.course.completion_manual:done' | translate }} {{ 'core.course.completion_manual:done' | translate }}
</ion-button> </ion-button>
</ng-container> </ng-container>
<ng-container *ngIf="!completion.state"> <ng-container *ngIf="!completion.state">
<ion-button color="light" [attr.aria-label]="accessibleDescription" (click)="completionClicked($event)"> <ion-button color="dark" expand="block" fill="outline" [attr.aria-label]="accessibleDescription"
(click)="completionClicked($event)">
{{ 'core.course.completion_manual:markdone' | translate }} {{ 'core.course.completion_manual:markdone' | translate }}
</ion-button> </ion-button>
</ng-container> </ng-container>
</ng-container> </ng-container>
<ng-container *ngIf="!completion.istrackeduser"> <ng-container *ngIf="!completion.istrackeduser">
<ion-button disabled="true" color="light"> <ion-button disabled="true" color="dark" expand="block" fill="outline">
{{ 'core.course.completion_manual:markdone' | translate }} {{ 'core.course.completion_manual:markdone' | translate }}
</ion-button> </ion-button>
</ng-container> </ng-container>

View File

@ -14,10 +14,11 @@
import { Component, ElementRef, Input, OnDestroy, OnInit } from '@angular/core'; import { Component, ElementRef, Input, OnDestroy, OnInit } from '@angular/core';
import { CoreCourse, CoreCourseProvider, CoreCourseWSSection } from '@features/course/services/course'; import { CoreCourse, CoreCourseProvider, CoreCourseWSSection } from '@features/course/services/course';
import { CoreCourseModule } from '@features/course/services/course-helper'; import { CoreCourseModule, CoreCourseSection } from '@features/course/services/course-helper';
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
import { IonContent } from '@ionic/angular'; import { IonContent } from '@ionic/angular';
import { ScrollDetail } from '@ionic/core'; import { ScrollDetail } from '@ionic/core';
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
@ -41,6 +42,8 @@ export class CoreCourseModuleNavigationComponent implements OnInit, OnDestroy {
nextModule?: CoreCourseModule; nextModule?: CoreCourseModule;
previousModule?: CoreCourseModule; previousModule?: CoreCourseModule;
nextModuleSection?: CoreCourseSection;
previousModuleSection?: CoreCourseSection;
loaded = false; loaded = false;
protected element: HTMLElement; protected element: HTMLElement;
@ -201,9 +204,10 @@ export class CoreCourseModuleNavigationComponent implements OnInit, OnDestroy {
for (let j = startModule; j < section.modules.length && this.nextModule == undefined; j++) { for (let j = startModule; j < section.modules.length && this.nextModule == undefined; j++) {
const module = section.modules[j]; const module = section.modules[j];
const found = await this.isModuleAvailable(module, section.id); const found = await this.isModuleAvailable(module);
if (found) { if (found) {
this.nextModule = module; this.nextModule = module;
this.nextModuleSection = section;
} }
} }
} }
@ -224,9 +228,10 @@ export class CoreCourseModuleNavigationComponent implements OnInit, OnDestroy {
for (let j = startModule; j >= 0 && this.previousModule == undefined; j--) { for (let j = startModule; j >= 0 && this.previousModule == undefined; j--) {
const module = section.modules[j]; const module = section.modules[j];
const found = await this.isModuleAvailable(module, section.id); const found = await this.isModuleAvailable(module);
if (found) { if (found) {
this.previousModule = module; this.previousModule = module;
this.previousModuleSection = section;
} }
} }
} }
@ -237,20 +242,10 @@ export class CoreCourseModuleNavigationComponent implements OnInit, OnDestroy {
* Module is visible by the user and it has a specific view (e.g. not a label). * Module is visible by the user and it has a specific view (e.g. not a label).
* *
* @param module Module to check. * @param module Module to check.
* @param sectionId Section ID the module belongs to.
* @return Wether the module is available to the user or not. * @return Wether the module is available to the user or not.
*/ */
protected async isModuleAvailable(module: CoreCourseModule, sectionId: number): Promise<boolean> { protected async isModuleAvailable(module: CoreCourseModule): Promise<boolean> {
if (module.uservisible === false || !CoreCourse.instance.moduleHasView(module)) { return CoreCourse.instance.moduleHasView(module);
return false;
}
if (!module.handlerData) {
module.handlerData =
await CoreCourseModuleDelegate.getModuleDataFor(module.modname, module, this.courseId, sectionId);
}
return !!module.handlerData?.action;
} }
/** /**
@ -291,11 +286,19 @@ export class CoreCourseModuleNavigationComponent implements OnInit, OnDestroy {
return; return;
} }
if (!module.handlerData?.action) { if (module.uservisible === false) {
return; const section = next ? this.nextModuleSection : this.previousModuleSection;
const options: CoreNavigationOptions = {
replace: true,
params: {
module,
section,
},
};
CoreNavigator.navigateToSitePath('course/' + this.courseId + '/' + module.id +'/module-preview', options);
} else {
CoreCourseModuleDelegate.openActivityPage(module.modname, module, this.courseId, { replace: true });
} }
module.handlerData.action(new Event('click'), module, this.courseId, { replace: true });
} }
/** /**

View File

@ -75,8 +75,8 @@
</div> </div>
<!-- Module completion. --> <!-- Module completion. -->
<core-course-module-completion *ngIf="module.completiondata" [completion]="module.completiondata" [moduleName]="module.name" <core-course-module-completion *ngIf="module.completiondata && module.uservisible" [completion]="module.completiondata"
[moduleId]="module.id" [showCompletionConditions]="showCompletionConditions" [moduleName]="module.name" [moduleId]="module.id" [showCompletionConditions]="showCompletionConditions"
[showManualCompletion]="showManualCompletion" (completionChanged)="completionChanged.emit($event)"> [showManualCompletion]="showManualCompletion" (completionChanged)="completionChanged.emit($event)">
</core-course-module-completion> </core-course-module-completion>

View File

@ -1,6 +1,3 @@
<core-course-module-info [description]="module?.description" [courseId]="courseId" [module]="module">
</core-course-module-info>
<div class="ion-padding"> <div class="ion-padding">
<h2 *ngIf="!isDisabledInSite && isSupportedByTheApp">{{ 'core.whoops' | translate }}</h2> <h2 *ngIf="!isDisabledInSite && isSupportedByTheApp">{{ 'core.whoops' | translate }}</h2>
<h2 *ngIf="isDisabledInSite || !isSupportedByTheApp">{{ 'core.uhoh' | translate }}</h2> <h2 *ngIf="isDisabledInSite || !isSupportedByTheApp">{{ 'core.uhoh' | translate }}</h2>

View File

@ -23,9 +23,9 @@ const routes: Routes = [
loadChildren: () => import('./pages/index/index.module').then( m => m.CoreCourseIndexPageModule), loadChildren: () => import('./pages/index/index.module').then( m => m.CoreCourseIndexPageModule),
}, },
{ {
path: ':courseId/unsupported-module', path: ':courseId/:cmId/module-preview',
loadChildren: () => import('./pages/unsupported-module/unsupported-module.module') loadChildren: () => import('./pages/module-preview/module-preview.module')
.then( m => m.CoreCourseUnsupportedModulePageModule), .then( m => m.CoreCourseModulePreviewPageModule),
}, },
{ {
path: ':courseId/list-mod-type', path: ':courseId/list-mod-type',

View File

@ -0,0 +1,65 @@
<ion-header collapsible>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button [text]="'core.back' | translate"></ion-back-button>
</ion-buttons>
<ion-title>
<h1>
<core-format-text [text]="title" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
</core-format-text>
</h1>
</ion-title>
<ion-buttons slot="end">
<core-context-menu>
<core-context-menu-item [priority]="900" *ngIf="module.url" [href]="module!.url"
[content]="'core.openinbrowser' | translate" iconAction="fas-external-link-alt">
</core-context-menu-item>
</core-context-menu>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="doRefresh($event.target)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="loaded">
<core-course-module-info [module]="module" [courseId]="courseId" [description]="module.description" [component]="module.modname"
[componentId]="module.id" (completionChanged)="onCompletionChange()" [expandDescription]="true"
[showManualCompletion]="showManualCompletion">
<div class="safe-area-padding-horizontal ion-padding" *ngIf="module.handlerData?.extraBadge">
<ion-badge class="ion-text-wrap ion-text-start" [color]="module.handlerData?.extraBadgeColor">
<span [innerHTML]="module.handlerData?.extraBadge"></span>
</ion-badge>
</div>
<div class="safe-area-padding-horizontal ion-padding" *ngIf="module.visible === 0 && (!section || section.visible)">
<ion-badge class="ion-text-wrap">
{{ 'core.course.hiddenfromstudents' | translate }}
</ion-badge>
</div>
<div class="safe-area-padding-horizontal ion-padding" *ngIf="module.visible !== 0 && module.isStealth">
<ion-badge class="ion-text-wrap">
{{ 'core.course.hiddenoncoursepage' | translate }}
</ion-badge>
</div>
<div class="safe-area-padding-horizontal ion-padding core-module-availabilityinfo" *ngIf="module.availabilityinfo">
<ion-badge class="ion-text-wrap">{{ 'core.restricted' | translate }}</ion-badge>
<div>
<core-format-text [text]="module.availabilityinfo" contextLevel="module" [contextInstanceId]="module.id"
[courseId]="courseId" class="ion-text-wrap">
</core-format-text>
</div>
</div>
<div class="safe-area-padding-horizontal ion-padding" *ngIf="module.completiondata?.offline">
<ion-badge color="warning" class="ion-text-wrap">
{{ 'core.course.manualcompletionnotsynced' | translate }}
</ion-badge>
</div>
<core-course-unsupported-module *ngIf="unsupported" [module]="module" [courseId]="courseId"></core-course-unsupported-module>
</core-course-module-info>
</core-loading>
<core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModuleId]="module.id"></core-course-module-navigation>
</ion-content>

View File

@ -16,13 +16,13 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { CoreSharedModule } from '@/core/shared.module'; import { CoreSharedModule } from '@/core/shared.module';
import { CoreCourseUnsupportedModulePage } from './unsupported-module.page'; import { CoreCourseModulePreviewPage } from './module-preview.page';
import { CoreCourseComponentsModule } from '@features/course/components/components.module'; import { CoreCourseComponentsModule } from '@features/course/components/components.module';
const routes: Routes = [ const routes: Routes = [
{ {
path: '', path: '',
component: CoreCourseUnsupportedModulePage, component: CoreCourseModulePreviewPage,
}, },
]; ];
@ -33,8 +33,8 @@ const routes: Routes = [
CoreCourseComponentsModule, CoreCourseComponentsModule,
], ],
declarations: [ declarations: [
CoreCourseUnsupportedModulePage, CoreCourseModulePreviewPage,
], ],
exports: [RouterModule], exports: [RouterModule],
}) })
export class CoreCourseUnsupportedModulePageModule {} export class CoreCourseModulePreviewPageModule { }

View File

@ -0,0 +1,118 @@
// (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, OnInit } from '@angular/core';
import { CoreCourse } from '@features/course/services/course';
import { CoreCourseHelper, CoreCourseModule, CoreCourseSection } from '@features/course/services/course-helper';
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
import { IonRefresher } from '@ionic/angular';
import { CoreNavigator } from '@services/navigator';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
/**
* Page that displays a module preview.
*/
@Component({
selector: 'page-core-course-module-preview',
templateUrl: 'module-preview.html',
})
export class CoreCourseModulePreviewPage implements OnInit {
title!: string;
module!: CoreCourseModule;
section?: CoreCourseSection; // The section the module belongs to.
courseId!: number;
loaded = false;
unsupported = false;
showManualCompletion = false;
protected debouncedUpdateModule?: () => void; // Update the module after a certain time.
/**
* @inheritdoc
*/
async ngOnInit(): Promise<void> {
try {
this.module = CoreNavigator.getRequiredRouteParam<CoreCourseModule>('module');
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
this.section = CoreNavigator.getRouteParam<CoreCourseSection>('section');
} catch (error) {
CoreDomUtils.showErrorModal(error);
CoreNavigator.back();
return;
}
this.debouncedUpdateModule = CoreUtils.debounce(() => {
this.doRefresh();
}, 10000);
await this.fetchModule();
}
/**
* Fetch module.
*
* @return Promise resolved when done.
*/
protected async fetchModule(refresh = false): Promise<void> {
if (refresh) {
this.module = await CoreCourse.getModule(this.module.id, this.courseId);
}
CoreCourseHelper.calculateModuleCompletionData(this.module, this.courseId);
await CoreCourseHelper.loadModuleOfflineCompletion(this.courseId, this.module);
this.unsupported = !CoreCourseModuleDelegate.getHandlerName(this.module.modname);
if (!this.unsupported) {
this.module.handlerData =
await CoreCourseModuleDelegate.getModuleDataFor(this.module.modname, this.module, this.courseId);
}
this.title = this.module.name;
this.showManualCompletion = await CoreCourseModuleDelegate.manualCompletionAlwaysShown(this.module);
this.loaded = true;
}
/**
* Refresh the data.
*
* @param refresher Refresher.
* @return Promise resolved when done.
*/
async doRefresh(refresher?: IonRefresher): Promise<void> {
await CoreCourse.invalidateModule(this.module.id);
this.fetchModule(true);
refresher?.complete();
}
/**
* The completion of the modules has changed.
*
* @return Promise resolved when done.
*/
async onCompletionChange(): Promise<void> {
// Update the module data after a while.
this.debouncedUpdateModule?.();
}
}

View File

@ -1,27 +0,0 @@
<ion-header collapsible>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button [text]="'core.back' | translate"></ion-back-button>
</ion-buttons>
<ion-title>
<h1>
<core-format-text [text]="module?.name" contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId">
</core-format-text>
</h1>
</ion-title>
<ion-buttons slot="end">
<core-context-menu>
<core-context-menu-item [priority]="900" *ngIf="module?.url" [href]="module!.url"
[content]="'core.openinbrowser' | translate" iconAction="fas-external-link-alt">
</core-context-menu-item>
<core-context-menu-item [priority]="800" *ngIf="module?.description" [content]="'core.moduleintro' | translate"
(action)="expandDescription()" iconAction="fas-arrow-right">
</core-context-menu-item>
</core-context-menu>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<core-course-unsupported-module [module]="module" [courseId]="courseId"></core-course-unsupported-module>
</ion-content>

View File

@ -1,54 +0,0 @@
// (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, OnInit } from '@angular/core';
import { CoreCourseWSModule } from '@features/course/services/course';
import { CoreNavigator } from '@services/navigator';
import { CoreTextUtils } from '@services/utils/text';
import { Translate } from '@singletons';
/**
* Page that displays info about an unsupported module.
*/
@Component({
selector: 'page-core-course-unsupported-module',
templateUrl: 'unsupported-module.html',
})
export class CoreCourseUnsupportedModulePage implements OnInit {
module?: CoreCourseWSModule;
courseId?: number;
/**
* @inheritDoc
*/
ngOnInit(): void {
this.module = CoreNavigator.getRouteParam('module');
this.courseId = CoreNavigator.getRouteNumberParam('courseId');
}
/**
* Expand the description.
*/
expandDescription(): void {
CoreTextUtils.viewText(Translate.instant('core.description'), this.module!.description!, {
filter: true,
contextLevel: 'module',
instanceId: this.module!.id,
courseId: this.courseId,
});
}
}

View File

@ -49,14 +49,11 @@ export class CoreCourseModuleDefaultHandler implements CoreCourseModuleHandler {
icon: await CoreCourse.getModuleIconSrc(module.modname, module.modicon), icon: await CoreCourse.getModuleIconSrc(module.modname, module.modicon),
title: module.name, title: module.name,
class: 'core-course-default-handler core-course-module-' + module.modname + '-handler', class: 'core-course-default-handler core-course-module-' + module.modname + '-handler',
action: (event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions) => { action: async (event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
options = options || {}; await this.openActivityPage(module, courseId, options);
options.params = { module };
CoreNavigator.navigateToSitePath('course/' + courseId + '/unsupported-module', options);
}, },
}; };
@ -92,4 +89,15 @@ export class CoreCourseModuleDefaultHandler implements CoreCourseModuleHandler {
return true; return true;
} }
/**
* @inheritdoc
*/
async openActivityPage(module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions): Promise<void> {
options = options || {};
options.params = options.params || {};
Object.assign(options.params, { module });
await CoreNavigator.navigateToSitePath('course/' + courseId + '/' + module.id +'/module-preview', options);
}
} }

View File

@ -102,6 +102,16 @@ export interface CoreCourseModuleHandler extends CoreDelegateHandler {
* @return Promise resolved with boolean: whether the manual completion should always be displayed. * @return Promise resolved with boolean: whether the manual completion should always be displayed.
*/ */
manualCompletionAlwaysShown?(module: CoreCourseModule): Promise<boolean>; manualCompletionAlwaysShown?(module: CoreCourseModule): Promise<boolean>;
/**
* Opens the activity page.
*
* @param module The module object.
* @param courseId The course ID.
* @param options Options for the navigation.
* @return Promise resolved when done.
*/
openActivityPage(module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions): Promise<void>;
} }
/** /**
@ -295,6 +305,27 @@ export class CoreCourseModuleDelegateService extends CoreDelegate<CoreCourseModu
); );
} }
/**
* Opens the activity page.
*
* @param module The module object.
* @param courseId The course ID.
* @param options Options for the navigation.
* @return Promise resolved when done.
*/
async openActivityPage(
modname: string,
module: CoreCourseModule,
courseId: number,
options?: CoreNavigationOptions,
): Promise<void> {
return await this.executeFunctionOnEnabled<void>(
modname,
'openActivityPage',
[module, courseId, options],
);
}
/** /**
* Check if a certain module type is disabled in a site. * Check if a certain module type is disabled in a site.
* *

View File

@ -15,7 +15,7 @@
import { Type } from '@angular/core'; import { Type } from '@angular/core';
import { CoreConstants } from '@/core/constants'; import { CoreConstants } from '@/core/constants';
import { CoreCourseAnyModuleData, CoreCourseWSModule } from '@features/course/services/course'; import { CoreCourse, CoreCourseAnyModuleData, CoreCourseWSModule } from '@features/course/services/course';
import { CoreCourseModule } from '@features/course/services/course-helper'; import { CoreCourseModule } from '@features/course/services/course-helper';
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate'; import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate';
import { CoreSitePluginsModuleIndexComponent } from '@features/siteplugins/components/module-index/module-index'; import { CoreSitePluginsModuleIndexComponent } from '@features/siteplugins/components/module-index/module-index';
@ -92,17 +92,16 @@ export class CoreSitePluginsModuleHandler extends CoreSitePluginsBaseHandler imp
if (this.handlerSchema.method) { if (this.handlerSchema.method) {
// There is a method, add an action. // There is a method, add an action.
handlerData.action = (event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions) => { handlerData.action = async (
event: Event,
module: CoreCourseModule,
courseId: number,
options?: CoreNavigationOptions,
) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
options = options || {}; await this.openActivityPage(module, courseId, options);
options.params = {
title: module.name,
module,
};
CoreNavigator.navigateToSitePath(`siteplugins/module/${courseId}/${module.id}`, options);
}; };
} }
@ -229,4 +228,22 @@ export class CoreSitePluginsModuleHandler extends CoreSitePluginsBaseHandler imp
return false; return false;
} }
/**
* @inheritdoc
*/
async openActivityPage(module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions): Promise<void> {
if (!CoreCourse.moduleHasView(module)) {
return;
}
options = options || {};
options.params = options.params || {};
Object.assign(options.params, {
title: module.name,
module,
});
CoreNavigator.navigateToSitePath(`siteplugins/module/${courseId}/${module.id}`, options);
}
} }