MOBILE-3757 course: Display completion in course page
parent
962cd43d9e
commit
3f825db799
|
@ -71,5 +71,12 @@ export class AddonModLabelModuleHandlerService implements CoreCourseModuleHandle
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
manualCompletionAlwaysShown(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
export const AddonModLabelModuleHandler = makeSingleton(AddonModLabelModuleHandlerService);
|
export const AddonModLabelModuleHandler = makeSingleton(AddonModLabelModuleHandlerService);
|
||||||
|
|
|
@ -1826,15 +1826,15 @@ export class CoreSite {
|
||||||
* @return Object with major and minor. Returns false if invalid version.
|
* @return Object with major and minor. Returns false if invalid version.
|
||||||
*/
|
*/
|
||||||
protected getMajorAndMinor(version: string): {major: string; minor: number} | false {
|
protected getMajorAndMinor(version: string): {major: string; minor: number} | false {
|
||||||
const match = version.match(/(\d)+(?:\.(\d)+)?(?:\.(\d)+)?/);
|
const match = version.match(/^(\d+)(\.(\d+)(\.\d+)?)?/);
|
||||||
if (!match || !match[1]) {
|
if (!match || !match[1]) {
|
||||||
// Invalid version.
|
// Invalid version.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
major: match[1] + '.' + (match[2] || '0'),
|
major: match[1] + '.' + (match[3] || '0'),
|
||||||
minor: parseInt(match[3], 10) || 0,
|
minor: parseInt(match[5], 10) || 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
// (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, Input, Output, EventEmitter, OnChanges, SimpleChange } from '@angular/core';
|
||||||
|
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { CoreCourse } from '@features/course/services/course';
|
||||||
|
import { CoreCourseModuleCompletionData } from '@features/course/services/course-helper';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for completion components.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
template: '',
|
||||||
|
})
|
||||||
|
export class CoreCourseModuleCompletionBaseComponent implements OnChanges {
|
||||||
|
|
||||||
|
@Input() completion?: CoreCourseModuleCompletionData; // The completion status.
|
||||||
|
@Input() moduleId?: number; // The name of the module this completion affects.
|
||||||
|
@Input() moduleName?: string; // The name of the module this completion affects.
|
||||||
|
@Output() completionChanged = new EventEmitter<CoreCourseModuleCompletionData>(); // Notify when completion changes.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect changes on input properties.
|
||||||
|
*/
|
||||||
|
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
||||||
|
if (changes.completion && this.completion) {
|
||||||
|
this.calculateData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate data to render the completion.
|
||||||
|
*/
|
||||||
|
protected calculateData(): void {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completion clicked.
|
||||||
|
*
|
||||||
|
* @param e The click event.
|
||||||
|
*/
|
||||||
|
async completionClicked(e: Event): Promise<void> {
|
||||||
|
if (!this.completion) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof this.completion.cmid == 'undefined' || this.completion.tracking !== 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const modal = await CoreDomUtils.showModalLoading();
|
||||||
|
this.completion.state = this.completion.state === 1 ? 0 : 1;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await CoreCourse.markCompletedManually(
|
||||||
|
this.completion.cmid,
|
||||||
|
this.completion.state === 1,
|
||||||
|
this.completion.courseId!,
|
||||||
|
this.completion.courseName,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.completion.valueused === false) {
|
||||||
|
this.calculateData();
|
||||||
|
if (response.offline) {
|
||||||
|
this.completion.offline = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.completionChanged.emit(this.completion);
|
||||||
|
} catch (error) {
|
||||||
|
this.completion.state = this.completion.state === 1 ? 0 : 1;
|
||||||
|
CoreDomUtils.showErrorModalDefault(error, 'core.errorchangecompletion', true);
|
||||||
|
} finally {
|
||||||
|
modal.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -23,12 +23,14 @@ import { CoreCourseModuleDescriptionComponent } from './module-description/modul
|
||||||
import { CoreCourseSectionSelectorComponent } from './section-selector/section-selector';
|
import { CoreCourseSectionSelectorComponent } from './section-selector/section-selector';
|
||||||
import { CoreCourseTagAreaComponent } from './tag-area/tag-area';
|
import { CoreCourseTagAreaComponent } from './tag-area/tag-area';
|
||||||
import { CoreCourseUnsupportedModuleComponent } from './unsupported-module/unsupported-module';
|
import { CoreCourseUnsupportedModuleComponent } from './unsupported-module/unsupported-module';
|
||||||
|
import { CoreCourseModuleCompletionLegacyComponent } from './module-completion-legacy/module-completion-legacy';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
CoreCourseFormatComponent,
|
CoreCourseFormatComponent,
|
||||||
CoreCourseModuleComponent,
|
CoreCourseModuleComponent,
|
||||||
CoreCourseModuleCompletionComponent,
|
CoreCourseModuleCompletionComponent,
|
||||||
|
CoreCourseModuleCompletionLegacyComponent,
|
||||||
CoreCourseModuleDescriptionComponent,
|
CoreCourseModuleDescriptionComponent,
|
||||||
CoreCourseSectionSelectorComponent,
|
CoreCourseSectionSelectorComponent,
|
||||||
CoreCourseTagAreaComponent,
|
CoreCourseTagAreaComponent,
|
||||||
|
@ -42,6 +44,7 @@ import { CoreCourseUnsupportedModuleComponent } from './unsupported-module/unsup
|
||||||
CoreCourseFormatComponent,
|
CoreCourseFormatComponent,
|
||||||
CoreCourseModuleComponent,
|
CoreCourseModuleComponent,
|
||||||
CoreCourseModuleCompletionComponent,
|
CoreCourseModuleCompletionComponent,
|
||||||
|
CoreCourseModuleCompletionLegacyComponent,
|
||||||
CoreCourseModuleDescriptionComponent,
|
CoreCourseModuleDescriptionComponent,
|
||||||
CoreCourseSectionSelectorComponent,
|
CoreCourseSectionSelectorComponent,
|
||||||
CoreCourseTagAreaComponent,
|
CoreCourseTagAreaComponent,
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
<img *ngIf="completion && completion.tracking !== 1" [src]="completionImage" [alt]="completionDescription">
|
||||||
|
|
||||||
|
<ion-button
|
||||||
|
fill="clear"
|
||||||
|
*ngIf="completion && completion.tracking === 1"
|
||||||
|
(click)="completionClicked($event)"
|
||||||
|
[title]="completionDescription">
|
||||||
|
<img [src]="completionImage" role="presentation" alt="">
|
||||||
|
</ion-button>
|
|
@ -0,0 +1,20 @@
|
||||||
|
:host {
|
||||||
|
min-width: var(--a11y-min-target-size);
|
||||||
|
min-height: var(--a11y-min-target-size);
|
||||||
|
--size: 30px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
padding: 5px;
|
||||||
|
width: var(--size);
|
||||||
|
vertical-align: middle;
|
||||||
|
max-width: none;
|
||||||
|
margin: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-button {
|
||||||
|
--padding-top: 0;
|
||||||
|
--padding-start: 0;
|
||||||
|
--padding-end: 0;
|
||||||
|
--padding-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 } from '@angular/core';
|
||||||
|
|
||||||
|
import { CoreUser } from '@features/user/services/user';
|
||||||
|
import { CoreCourseProvider } from '@features/course/services/course';
|
||||||
|
import { CoreFilterHelper } from '@features/filter/services/filter-helper';
|
||||||
|
import { Translate } from '@singletons';
|
||||||
|
import { CoreCourseModuleCompletionBaseComponent } from '@features/course/classes/module-completion';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to handle activity completion in sites previous to 3.11.
|
||||||
|
* It shows a checkbox with the current status, and allows manually changing the completion if it's allowed.
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
*
|
||||||
|
* <core-course-module-completion-legacy [completion]="module.completiondata" [moduleName]="module.name"
|
||||||
|
* (completionChanged)="completionChanged()"></core-course-module-completion-legacy>
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'core-course-module-completion-legacy',
|
||||||
|
templateUrl: 'core-course-module-completion-legacy.html',
|
||||||
|
styleUrls: ['module-completion-legacy.scss'],
|
||||||
|
})
|
||||||
|
export class CoreCourseModuleCompletionLegacyComponent extends CoreCourseModuleCompletionBaseComponent {
|
||||||
|
|
||||||
|
completionImage?: string;
|
||||||
|
completionDescription?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected async calculateData(): Promise<void> {
|
||||||
|
if (!this.completion) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleName = this.moduleName || '';
|
||||||
|
let langKey: string | undefined;
|
||||||
|
let image: string | undefined;
|
||||||
|
|
||||||
|
if (this.completion.tracking === CoreCourseProvider.COMPLETION_TRACKING_MANUAL &&
|
||||||
|
this.completion.state === CoreCourseProvider.COMPLETION_INCOMPLETE) {
|
||||||
|
image = 'completion-manual-n';
|
||||||
|
langKey = 'core.completion-alt-manual-n';
|
||||||
|
} else if (this.completion.tracking === CoreCourseProvider.COMPLETION_TRACKING_MANUAL &&
|
||||||
|
this.completion.state === CoreCourseProvider.COMPLETION_COMPLETE) {
|
||||||
|
image = 'completion-manual-y';
|
||||||
|
langKey = 'core.completion-alt-manual-y';
|
||||||
|
} else if (this.completion.tracking === CoreCourseProvider.COMPLETION_TRACKING_AUTOMATIC &&
|
||||||
|
this.completion.state === CoreCourseProvider.COMPLETION_INCOMPLETE) {
|
||||||
|
image = 'completion-auto-n';
|
||||||
|
langKey = 'core.completion-alt-auto-n';
|
||||||
|
} else if (this.completion.tracking === CoreCourseProvider.COMPLETION_TRACKING_AUTOMATIC &&
|
||||||
|
this.completion.state === CoreCourseProvider.COMPLETION_COMPLETE) {
|
||||||
|
image = 'completion-auto-y';
|
||||||
|
langKey = 'core.completion-alt-auto-y';
|
||||||
|
} else if (this.completion.tracking === CoreCourseProvider.COMPLETION_TRACKING_AUTOMATIC &&
|
||||||
|
this.completion.state === CoreCourseProvider.COMPLETION_COMPLETE_PASS) {
|
||||||
|
image = 'completion-auto-pass';
|
||||||
|
langKey = 'core.completion-alt-auto-pass';
|
||||||
|
} else if (this.completion.tracking === CoreCourseProvider.COMPLETION_TRACKING_AUTOMATIC &&
|
||||||
|
this.completion.state === CoreCourseProvider.COMPLETION_COMPLETE_FAIL) {
|
||||||
|
image = 'completion-auto-fail';
|
||||||
|
langKey = 'core.completion-alt-auto-fail';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image) {
|
||||||
|
if (this.completion.overrideby && this.completion.overrideby > 0) {
|
||||||
|
image += '-override';
|
||||||
|
}
|
||||||
|
this.completionImage = 'assets/img/completion/' + image + '.svg';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!moduleName || !this.moduleId || !langKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await CoreFilterHelper.getFiltersAndFormatText(
|
||||||
|
moduleName,
|
||||||
|
'module',
|
||||||
|
this.moduleId,
|
||||||
|
{ clean: true, singleLine: true, shortenLength: 50, courseId: this.completion.courseId },
|
||||||
|
);
|
||||||
|
|
||||||
|
let translateParams: Record<string, unknown> = {
|
||||||
|
$a: result.text,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.completion.overrideby && this.completion.overrideby > 0) {
|
||||||
|
langKey += '-override';
|
||||||
|
|
||||||
|
const profile = await CoreUser.getProfile(this.completion.overrideby, this.completion.courseId, true);
|
||||||
|
|
||||||
|
translateParams = {
|
||||||
|
$a: {
|
||||||
|
overrideuser: profile.fullname,
|
||||||
|
modname: result.text,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.completionDescription = Translate.instant(langKey, translateParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,9 +1,53 @@
|
||||||
<img *ngIf="completion && completion.tracking !== 1" [src]="completionImage" [alt]="completionDescription">
|
<div *ngIf="showCompletionConditions && completion && completion.isautomatic" class="core-module-automatic-completion-conditions"
|
||||||
|
role="list" [attr.aria-label]="'core.course.completionrequirements' | translate:{ $a: moduleName }">
|
||||||
|
|
||||||
<ion-button
|
<ng-container *ngIf="completion.istrackeduser">
|
||||||
fill="clear"
|
<ng-container *ngFor="let rule of details">
|
||||||
*ngIf="completion && completion.tracking === 1"
|
<ion-badge *ngIf="rule.statuscomplete" color="success" role="listitem"
|
||||||
(click)="completionClicked($event)"
|
[attr.aria-label]="rule.accessibleDescription">
|
||||||
[title]="completionDescription">
|
<strong>{{ 'core.course.completion_automatic:done' | translate }}</strong> {{ rule.rulevalue.description }}
|
||||||
<img [src]="completionImage" role="presentation" alt="">
|
</ion-badge>
|
||||||
</ion-button>
|
|
||||||
|
<ion-badge *ngIf="rule.statuscompletefail" color="danger" role="listitem"
|
||||||
|
[attr.aria-label]="rule.accessibleDescription">
|
||||||
|
<strong>{{ 'core.course.completion_automatic:failed' | translate }}</strong> {{ rule.rulevalue.description }}
|
||||||
|
</ion-badge>
|
||||||
|
|
||||||
|
<ion-badge *ngIf="rule.statusincomplete" color="medium" role="listitem"
|
||||||
|
[attr.aria-label]="rule.accessibleDescription">
|
||||||
|
<strong>{{ 'core.course.completion_automatic:todo' | translate }}</strong> {{ rule.rulevalue.description }}
|
||||||
|
</ion-badge>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="!completion.istrackeduser">
|
||||||
|
<ion-badge *ngFor="let rule of details" color="light" role="listitem">
|
||||||
|
{{ rule.rulevalue.description }}
|
||||||
|
</ion-badge>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="completion && !completion.isautomatic && (showCompletionConditions || showManualCompletion)"
|
||||||
|
class="core-module-manual-completion">
|
||||||
|
|
||||||
|
<ng-container *ngIf="completion.istrackeduser">
|
||||||
|
<ng-container *ngIf="completion.state">
|
||||||
|
<ion-button color="success" fill="outline" [attr.aria-label]="accessibleDescription"
|
||||||
|
(click)="completionClicked($event)">
|
||||||
|
<ion-icon name="fas-check" slot="start" aria-hidden="true"></ion-icon>
|
||||||
|
{{ 'core.course.completion_manual:done' | translate }}
|
||||||
|
</ion-button>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="!completion.state">
|
||||||
|
<ion-button color="light" [attr.aria-label]="accessibleDescription" (click)="completionClicked($event)">
|
||||||
|
{{ 'core.course.completion_manual:markdone' | translate }}
|
||||||
|
</ion-button>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="!completion.istrackeduser">
|
||||||
|
<ion-button disabled="true" color="light">
|
||||||
|
{{ 'core.course.completion_manual:markdone' | translate }}
|
||||||
|
</ion-button>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
|
|
@ -1,20 +1,18 @@
|
||||||
:host {
|
:host {
|
||||||
min-width: var(--a11y-min-target-size);
|
.core-module-automatic-completion-conditions {
|
||||||
min-height: var(--a11y-min-target-size);
|
ion-badge {
|
||||||
--size: 30px;
|
font-weight: normal;
|
||||||
|
margin-right: 5px;
|
||||||
|
|
||||||
img {
|
&[color="medium"] {
|
||||||
padding: 5px;
|
color: black;
|
||||||
width: var(--size);
|
}
|
||||||
vertical-align: middle;
|
}
|
||||||
max-width: none;
|
|
||||||
margin: 7px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-button {
|
.core-module-manual-completion {
|
||||||
--padding-top: 0;
|
ion-button {
|
||||||
--padding-start: 0;
|
text-transform: none;
|
||||||
--padding-end: 0;
|
}
|
||||||
--padding-bottom: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,13 +12,10 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, Input, Output, EventEmitter, OnChanges, SimpleChange } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
|
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreCourseModuleCompletionBaseComponent } from '@features/course/classes/module-completion';
|
||||||
import { CoreUser } from '@features/user/services/user';
|
import { CoreCourseModuleWSRuleDetails, CoreCourseProvider } from '@features/course/services/course';
|
||||||
import { CoreCourse, CoreCourseProvider } from '@features/course/services/course';
|
|
||||||
import { CoreFilterHelper } from '@features/filter/services/filter-helper';
|
|
||||||
import { CoreCourseModuleCompletionData } from '@features/course/services/course-helper';
|
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,142 +32,66 @@ import { Translate } from '@singletons';
|
||||||
templateUrl: 'core-course-module-completion.html',
|
templateUrl: 'core-course-module-completion.html',
|
||||||
styleUrls: ['module-completion.scss'],
|
styleUrls: ['module-completion.scss'],
|
||||||
})
|
})
|
||||||
export class CoreCourseModuleCompletionComponent implements OnChanges {
|
export class CoreCourseModuleCompletionComponent extends CoreCourseModuleCompletionBaseComponent {
|
||||||
|
|
||||||
@Input() completion?: CoreCourseModuleCompletionData; // The completion status.
|
@Input() showCompletionConditions = false; // Whether to show activity completion conditions.
|
||||||
@Input() moduleId?: number; // The name of the module this completion affects.
|
@Input() showManualCompletion = false; // Whether to show manual completion when completion conditions are disabled.
|
||||||
@Input() moduleName?: string; // The name of the module this completion affects.
|
|
||||||
@Output() completionChanged = new EventEmitter<CoreCourseModuleCompletionData>(); // Notify when completion changes.
|
|
||||||
|
|
||||||
completionImage?: string;
|
details?: CompletionRule[];
|
||||||
completionDescription?: string;
|
accessibleDescription: string | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detect changes on input properties.
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
protected calculateData(): void {
|
||||||
if (changes.completion && this.completion) {
|
if (!this.completion?.details) {
|
||||||
this.showStatus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Completion clicked.
|
|
||||||
*
|
|
||||||
* @param e The click event.
|
|
||||||
*/
|
|
||||||
async completionClicked(e: Event): Promise<void> {
|
|
||||||
if (!this.completion) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof this.completion.cmid == 'undefined' || this.completion.tracking !== 1) {
|
// Set an accessible description for manual completions with overridden completion state.
|
||||||
return;
|
if (!this.completion.isautomatic && this.completion.overrideby) {
|
||||||
}
|
const setByData = {
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
const modal = await CoreDomUtils.showModalLoading();
|
|
||||||
this.completion.state = this.completion.state === 1 ? 0 : 1;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await CoreCourse.markCompletedManually(
|
|
||||||
this.completion.cmid,
|
|
||||||
this.completion.state === 1,
|
|
||||||
this.completion.courseId!,
|
|
||||||
this.completion.courseName,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.completion.valueused === false) {
|
|
||||||
this.showStatus();
|
|
||||||
if (response.offline) {
|
|
||||||
this.completion.offline = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.completionChanged.emit(this.completion);
|
|
||||||
} catch (error) {
|
|
||||||
this.completion.state = this.completion.state === 1 ? 0 : 1;
|
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'core.errorchangecompletion', true);
|
|
||||||
} finally {
|
|
||||||
modal.dismiss();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set image and description to show as completion icon.
|
|
||||||
*/
|
|
||||||
protected async showStatus(): Promise<void> {
|
|
||||||
if (!this.completion) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const moduleName = this.moduleName || '';
|
|
||||||
let langKey: string | undefined;
|
|
||||||
let image: string | undefined;
|
|
||||||
|
|
||||||
if (this.completion.tracking === CoreCourseProvider.COMPLETION_TRACKING_MANUAL &&
|
|
||||||
this.completion.state === CoreCourseProvider.COMPLETION_INCOMPLETE) {
|
|
||||||
image = 'completion-manual-n';
|
|
||||||
langKey = 'core.completion-alt-manual-n';
|
|
||||||
} else if (this.completion.tracking === CoreCourseProvider.COMPLETION_TRACKING_MANUAL &&
|
|
||||||
this.completion.state === CoreCourseProvider.COMPLETION_COMPLETE) {
|
|
||||||
image = 'completion-manual-y';
|
|
||||||
langKey = 'core.completion-alt-manual-y';
|
|
||||||
} else if (this.completion.tracking === CoreCourseProvider.COMPLETION_TRACKING_AUTOMATIC &&
|
|
||||||
this.completion.state === CoreCourseProvider.COMPLETION_INCOMPLETE) {
|
|
||||||
image = 'completion-auto-n';
|
|
||||||
langKey = 'core.completion-alt-auto-n';
|
|
||||||
} else if (this.completion.tracking === CoreCourseProvider.COMPLETION_TRACKING_AUTOMATIC &&
|
|
||||||
this.completion.state === CoreCourseProvider.COMPLETION_COMPLETE) {
|
|
||||||
image = 'completion-auto-y';
|
|
||||||
langKey = 'core.completion-alt-auto-y';
|
|
||||||
} else if (this.completion.tracking === CoreCourseProvider.COMPLETION_TRACKING_AUTOMATIC &&
|
|
||||||
this.completion.state === CoreCourseProvider.COMPLETION_COMPLETE_PASS) {
|
|
||||||
image = 'completion-auto-pass';
|
|
||||||
langKey = 'core.completion-alt-auto-pass';
|
|
||||||
} else if (this.completion.tracking === CoreCourseProvider.COMPLETION_TRACKING_AUTOMATIC &&
|
|
||||||
this.completion.state === CoreCourseProvider.COMPLETION_COMPLETE_FAIL) {
|
|
||||||
image = 'completion-auto-fail';
|
|
||||||
langKey = 'core.completion-alt-auto-fail';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (image) {
|
|
||||||
if (this.completion.overrideby > 0) {
|
|
||||||
image += '-override';
|
|
||||||
}
|
|
||||||
this.completionImage = 'assets/img/completion/' + image + '.svg';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!moduleName || !this.moduleId || !langKey) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await CoreFilterHelper.getFiltersAndFormatText(
|
|
||||||
moduleName,
|
|
||||||
'module',
|
|
||||||
this.moduleId,
|
|
||||||
{ clean: true, singleLine: true, shortenLength: 50, courseId: this.completion.courseId },
|
|
||||||
);
|
|
||||||
|
|
||||||
let translateParams: Record<string, unknown> = {
|
|
||||||
$a: result.text,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.completion.overrideby > 0) {
|
|
||||||
langKey += '-override';
|
|
||||||
|
|
||||||
const profile = await CoreUser.getProfile(this.completion.overrideby, this.completion.courseId, true);
|
|
||||||
|
|
||||||
translateParams = {
|
|
||||||
$a: {
|
$a: {
|
||||||
overrideuser: profile.fullname,
|
activityname: this.moduleName,
|
||||||
modname: result.text,
|
setby: this.completion.overrideby,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const setByLangKey = this.completion.state ? 'completion_setby:manual:done' : 'completion_setby:manual:markdone';
|
||||||
|
this.accessibleDescription = Translate.instant('core.course.' + setByLangKey, setByData);
|
||||||
|
} else {
|
||||||
|
const langKey = this.completion.state ? 'completion_manual:aria:done' : 'completion_manual:aria:markdone';
|
||||||
|
this.accessibleDescription = Translate.instant('core.course.' + langKey, { $a: this.moduleName });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.completionDescription = Translate.instant(langKey, translateParams);
|
// Format rules.
|
||||||
|
this.details = this.completion.details.map((rule: CompletionRule) => {
|
||||||
|
rule.statuscomplete = rule.rulevalue.status == CoreCourseProvider.COMPLETION_COMPLETE ||
|
||||||
|
rule.rulevalue.status == CoreCourseProvider.COMPLETION_COMPLETE_PASS;
|
||||||
|
rule.statuscompletefail = rule.rulevalue.status == CoreCourseProvider.COMPLETION_COMPLETE_FAIL;
|
||||||
|
rule.statusincomplete = rule.rulevalue.status == CoreCourseProvider.COMPLETION_INCOMPLETE;
|
||||||
|
rule.accessibleDescription = null;
|
||||||
|
|
||||||
|
if (this.completion!.overrideby) {
|
||||||
|
const setByData = {
|
||||||
|
$a: {
|
||||||
|
condition: rule.rulevalue.description,
|
||||||
|
setby: this.completion!.overrideby,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const overrideStatus = rule.statuscomplete ? 'done' : 'todo';
|
||||||
|
|
||||||
|
rule.accessibleDescription = Translate.instant('core.course.completion_setby:auto:' + overrideStatus, setByData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rule;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CompletionRule = CoreCourseModuleWSRuleDetails & {
|
||||||
|
statuscomplete?: boolean;
|
||||||
|
statuscompletefail?: boolean;
|
||||||
|
statusincomplete?: boolean;
|
||||||
|
accessibleDescription?: string | null;
|
||||||
|
};
|
||||||
|
|
|
@ -50,12 +50,13 @@
|
||||||
slot="end"
|
slot="end"
|
||||||
*ngIf="module.uservisible !== false"
|
*ngIf="module.uservisible !== false"
|
||||||
class="buttons core-module-buttons"
|
class="buttons core-module-buttons"
|
||||||
[ngClass]="{'core-button-completion': module.completiondata}"
|
[ngClass]="{'core-button-completion': module.completiondata && showLegacyCompletion}"
|
||||||
>
|
>
|
||||||
<!-- Module completion. -->
|
<!-- Module completion (legacy). -->
|
||||||
<core-course-module-completion *ngIf="module.completiondata" [completion]="module.completiondata"
|
<core-course-module-completion-legacy *ngIf="module.completiondata && showLegacyCompletion"
|
||||||
[moduleName]="module.name" [moduleId]="module.id" (completionChanged)="completionChanged.emit($event)">
|
[completion]="module.completiondata" [moduleName]="module.name" [moduleId]="module.id"
|
||||||
</core-course-module-completion>
|
(completionChanged)="completionChanged.emit($event)">
|
||||||
|
</core-course-module-completion-legacy>
|
||||||
|
|
||||||
<div class="core-module-buttons-more">
|
<div class="core-module-buttons-more">
|
||||||
<core-download-refresh [status]="downloadStatus" [enabled]="downloadEnabled"
|
<core-download-refresh [status]="downloadStatus" [enabled]="downloadEnabled"
|
||||||
|
@ -84,11 +85,19 @@
|
||||||
detail="false"
|
detail="false"
|
||||||
>
|
>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
|
<!-- Activity dates. -->
|
||||||
<div *ngIf="showActivityDates && module.dates && module.dates.length" class="core-module-dates">
|
<div *ngIf="showActivityDates && module.dates && 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. -->
|
||||||
|
<core-course-module-completion *ngIf="module.completiondata" [completion]="module.completiondata"
|
||||||
|
[moduleName]="module.name" [moduleId]="module.id" [showCompletionConditions]="showCompletionConditions"
|
||||||
|
[showManualCompletion]="showManualCompletion" (completionChanged)="completionChanged.emit($event)">
|
||||||
|
</core-course-module-completion>
|
||||||
|
|
||||||
<core-format-text class="core-module-description" *ngIf="module.description" maxHeight="80" [text]="module.description"
|
<core-format-text class="core-module-description" *ngIf="module.description" maxHeight="80" [text]="module.description"
|
||||||
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
|
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
|
||||||
</core-format-text>
|
</core-format-text>
|
||||||
|
|
|
@ -24,7 +24,7 @@ import {
|
||||||
CoreCourseSection,
|
CoreCourseSection,
|
||||||
} from '@features/course/services/course-helper';
|
} from '@features/course/services/course-helper';
|
||||||
import { CoreCourse } from '@features/course/services/course';
|
import { CoreCourse } from '@features/course/services/course';
|
||||||
import { CoreCourseModuleHandlerButton } from '@features/course/services/module-delegate';
|
import { CoreCourseModuleDelegate, CoreCourseModuleHandlerButton } from '@features/course/services/module-delegate';
|
||||||
import {
|
import {
|
||||||
CoreCourseModulePrefetchDelegate,
|
CoreCourseModulePrefetchDelegate,
|
||||||
CoreCourseModulePrefetchHandler,
|
CoreCourseModulePrefetchHandler,
|
||||||
|
@ -48,7 +48,7 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
|
||||||
@Input() courseId?: number; // The course the module belongs to.
|
@Input() courseId?: number; // The course the module belongs to.
|
||||||
@Input() section?: CoreCourseSection; // The section the module belongs to.
|
@Input() section?: CoreCourseSection; // The section the module belongs to.
|
||||||
@Input() showActivityDates = false; // Whether to show activity dates.
|
@Input() showActivityDates = false; // Whether to show activity dates.
|
||||||
@Input() showCompletionConditions = false; // Whether to show activity completion conditions.
|
@Input() showCompletionConditions = false; // Whether to show activity completion conditions.
|
||||||
// eslint-disable-next-line @angular-eslint/no-input-rename
|
// eslint-disable-next-line @angular-eslint/no-input-rename
|
||||||
@Input('downloadEnabled') set enabled(value: boolean) {
|
@Input('downloadEnabled') set enabled(value: boolean) {
|
||||||
this.downloadEnabled = value;
|
this.downloadEnabled = value;
|
||||||
|
@ -74,6 +74,8 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
|
||||||
downloadEnabled?: boolean; // Whether the download of sections and modules is enabled.
|
downloadEnabled?: boolean; // Whether the download of sections and modules is enabled.
|
||||||
modNameTranslated = '';
|
modNameTranslated = '';
|
||||||
hasInfo = false;
|
hasInfo = false;
|
||||||
|
showLegacyCompletion = false; // Whether to show module completion in the old format.
|
||||||
|
showManualCompletion = false; // Whether to show manual completion when completion conditions are disabled.
|
||||||
|
|
||||||
protected prefetchHandler?: CoreCourseModulePrefetchHandler;
|
protected prefetchHandler?: CoreCourseModulePrefetchHandler;
|
||||||
protected statusObserver?: CoreEventObserver;
|
protected statusObserver?: CoreEventObserver;
|
||||||
|
@ -86,6 +88,9 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.courseId = this.courseId || this.module.course;
|
this.courseId = this.courseId || this.module.course;
|
||||||
this.modNameTranslated = CoreCourse.translateModuleName(this.module.modname) || '';
|
this.modNameTranslated = CoreCourse.translateModuleName(this.module.modname) || '';
|
||||||
|
this.showLegacyCompletion = !CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.11');
|
||||||
|
this.showManualCompletion =
|
||||||
|
this.showCompletionConditions || CoreCourseModuleDelegate.manualCompletionAlwaysShown(this.module);
|
||||||
|
|
||||||
if (!this.module.handlerData) {
|
if (!this.module.handlerData) {
|
||||||
return;
|
return;
|
||||||
|
@ -94,7 +99,8 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
|
||||||
this.module.handlerData.a11yTitle = this.module.handlerData.a11yTitle ?? this.module.handlerData.title;
|
this.module.handlerData.a11yTitle = this.module.handlerData.a11yTitle ?? this.module.handlerData.title;
|
||||||
this.hasInfo = !!(
|
this.hasInfo = !!(
|
||||||
this.module.description ||
|
this.module.description ||
|
||||||
(this.showActivityDates && this.module.dates && this.module.dates.length)
|
(this.showActivityDates && this.module.dates && this.module.dates.length) ||
|
||||||
|
this.module.completiondata
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.module.handlerData.showDownloadButton) {
|
if (this.module.handlerData.showDownloadButton) {
|
||||||
|
|
|
@ -6,6 +6,18 @@
|
||||||
"askadmintosupport": "Contact the site administrator and tell them you want to use this activity with the Moodle Mobile app.",
|
"askadmintosupport": "Contact the site administrator and tell them you want to use this activity with the Moodle Mobile app.",
|
||||||
"availablespace": " You currently have about {{available}} free space.",
|
"availablespace": " You currently have about {{available}} free space.",
|
||||||
"cannotdeletewhiledownloading": "Files cannot be deleted while the activity is being downloaded. Please wait for the download to finish.",
|
"cannotdeletewhiledownloading": "Files cannot be deleted while the activity is being downloaded. Please wait for the download to finish.",
|
||||||
|
"completion_automatic:done": "Done:",
|
||||||
|
"completion_automatic:failed": "Failed:",
|
||||||
|
"completion_automatic:todo": "To do:",
|
||||||
|
"completion_manual:aria:done": "{{$a}} is marked as done. Press to undo.",
|
||||||
|
"completion_manual:aria:markdone": "Mark {{$a}} as done",
|
||||||
|
"completion_manual:done": "Done",
|
||||||
|
"completion_manual:markdone": "Mark as done",
|
||||||
|
"completion_setby:auto:done": "Done: {{$a.condition}} (set by {{$a.setby}})",
|
||||||
|
"completion_setby:auto:todo": "To do: {{$a.condition}} (set by {{$a.setby}})",
|
||||||
|
"completion_setby:manual:done": "{{$a.activityname}} is marked by {{$a.setby}} as done. Press to undo.",
|
||||||
|
"completion_setby:manual:markdone": "{{$a.activityname}} is marked by {{$a.setby}} as not done. Press to mark as done.",
|
||||||
|
"completionrequirements": "Completion requirements for {{$a}}",
|
||||||
"confirmdeletemodulefiles": "Are you sure you want to delete these files?",
|
"confirmdeletemodulefiles": "Are you sure you want to delete these files?",
|
||||||
"confirmdeletestoreddata": "Are you sure you want to delete the stored data?",
|
"confirmdeletestoreddata": "Are you sure you want to delete the stored data?",
|
||||||
"confirmdownload": "You are about to download {{size}}.{{availableSpace}} Are you sure you want to continue?",
|
"confirmdownload": "You are about to download {{size}}.{{availableSpace}} Are you sure you want to continue?",
|
||||||
|
|
|
@ -353,6 +353,9 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
||||||
const shouldReload = typeof completionData.valueused == 'undefined' || completionData.valueused;
|
const shouldReload = typeof completionData.valueused == 'undefined' || completionData.valueused;
|
||||||
|
|
||||||
if (!shouldReload) {
|
if (!shouldReload) {
|
||||||
|
// Invalidate the completion.
|
||||||
|
await CoreUtils.ignoreErrors(CoreCourse.invalidateSections(this.course.id));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1296,14 +1296,25 @@ export type CoreCourseCompletionActivityStatusWSResponse = {
|
||||||
* Activity status.
|
* Activity status.
|
||||||
*/
|
*/
|
||||||
export type CoreCourseCompletionActivityStatus = {
|
export type CoreCourseCompletionActivityStatus = {
|
||||||
cmid: number; // Comment ID.
|
cmid: number; // Course module ID.
|
||||||
modname: string; // Activity module name.
|
modname: string; // Activity module name.
|
||||||
instance: number; // Instance ID.
|
instance: number; // Instance ID.
|
||||||
state: number; // Completion state value: 0 means incomplete, 1 complete, 2 complete pass, 3 complete fail.
|
state: number; // Completion state value: 0 means incomplete, 1 complete, 2 complete pass, 3 complete fail.
|
||||||
timecompleted: number; // Timestamp for completed activity.
|
timecompleted: number; // Timestamp for completed activity.
|
||||||
tracking: number; // Type of tracking: 0 means none, 1 manual, 2 automatic.
|
tracking: number; // Type of tracking: 0 means none, 1 manual, 2 automatic.
|
||||||
overrideby?: number; // The user id who has overriden the status, or null.
|
overrideby?: number | null; // The user id who has overriden the status, or null.
|
||||||
valueused?: boolean; // Whether the completion status affects the availability of another activity.
|
valueused?: boolean; // Whether the completion status affects the availability of another activity.
|
||||||
|
hascompletion?: boolean; // @since 3.11. Whether this activity module has completion enabled.
|
||||||
|
isautomatic?: boolean; // @since 3.11. Whether this activity module instance tracks completion automatically.
|
||||||
|
istrackeduser?: boolean; // @since 3.11. Whether completion is being tracked for this user.
|
||||||
|
uservisible?: boolean; // @since 3.11. Whether this activity is visible to the user.
|
||||||
|
details?: { // @since 3.11. An array of completion details containing the description and status.
|
||||||
|
rulename: string; // Rule name.
|
||||||
|
rulevalue: {
|
||||||
|
status: number; // Completion status.
|
||||||
|
description: string; // Completion description.
|
||||||
|
};
|
||||||
|
}[];
|
||||||
offline?: boolean; // Whether the completions is offline and not yet synced.
|
offline?: boolean; // Whether the completions is offline and not yet synced.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1463,8 +1474,24 @@ export type CoreCourseWSModule = {
|
||||||
export type CoreCourseModuleWSCompletionData = {
|
export type CoreCourseModuleWSCompletionData = {
|
||||||
state: number; // Completion state value: 0 means incomplete, 1 complete, 2 complete pass, 3 complete fail.
|
state: number; // Completion state value: 0 means incomplete, 1 complete, 2 complete pass, 3 complete fail.
|
||||||
timecompleted: number; // Timestamp for completion status.
|
timecompleted: number; // Timestamp for completion status.
|
||||||
overrideby: number; // The user id who has overriden the status.
|
overrideby: number | null; // The user id who has overriden the status.
|
||||||
valueused?: boolean; // Whether the completion status affects the availability of another activity.
|
valueused?: boolean; // Whether the completion status affects the availability of another activity.
|
||||||
|
hascompletion?: boolean; // @since 3.11. Whether this activity module has completion enabled.
|
||||||
|
isautomatic?: boolean; // @since 3.11. Whether this activity module instance tracks completion automatically.
|
||||||
|
istrackeduser?: boolean; // @since 3.11. Whether completion is being tracked for this user.
|
||||||
|
uservisible?: boolean; // @since 3.11. Whether this activity is visible to the user.
|
||||||
|
details?: CoreCourseModuleWSRuleDetails[]; // @since 3.11. An array of completion details.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module completion rule details.
|
||||||
|
*/
|
||||||
|
export type CoreCourseModuleWSRuleDetails = {
|
||||||
|
rulename: string; // Rule name.
|
||||||
|
rulevalue: {
|
||||||
|
status: number; // Completion status.
|
||||||
|
description: string; // Completion description.
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CoreCourseModuleContentFile = {
|
export type CoreCourseModuleContentFile = {
|
||||||
|
|
|
@ -92,6 +92,15 @@ export interface CoreCourseModuleHandler extends CoreDelegateHandler {
|
||||||
* @return The result of the supports check.
|
* @return The result of the supports check.
|
||||||
*/
|
*/
|
||||||
supportsFeature?(feature: string): unknown;
|
supportsFeature?(feature: string): unknown;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true to show the manual completion regardless of the course's showcompletionconditions setting.
|
||||||
|
* Returns false by default.
|
||||||
|
*
|
||||||
|
* @param module Module.
|
||||||
|
* @return Whether the manual completion should always be displayed.
|
||||||
|
*/
|
||||||
|
manualCompletionAlwaysShown?(module: CoreCourseModule): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -366,6 +375,17 @@ export class CoreCourseModuleDelegateService extends CoreDelegate<CoreCourseModu
|
||||||
return result ?? defaultValue;
|
return result ?? defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true to show the manual completion regardless of the course's showcompletionconditions setting.
|
||||||
|
* Returns false by default.
|
||||||
|
*
|
||||||
|
* @param module Module.
|
||||||
|
* @return Whether the manual completion should always be displayed.
|
||||||
|
*/
|
||||||
|
manualCompletionAlwaysShown(module: CoreCourseModule): boolean {
|
||||||
|
return !!this.executeFunctionOnEnabled<boolean>(module.modname, 'manualCompletionAlwaysShown', [module]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CoreCourseModuleDelegate = makeSingleton(CoreCourseModuleDelegateService);
|
export const CoreCourseModuleDelegate = makeSingleton(CoreCourseModuleDelegateService);
|
||||||
|
|
|
@ -165,4 +165,20 @@ export class CoreSitePluginsModuleHandler extends CoreSitePluginsBaseHandler imp
|
||||||
return CoreSitePluginsModuleIndexComponent;
|
return CoreSitePluginsModuleIndexComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
manualCompletionAlwaysShown(module: CoreCourseModule): boolean {
|
||||||
|
if (this.handlerSchema.manualcompletionalwaysshown !== undefined) {
|
||||||
|
return this.handlerSchema.manualcompletionalwaysshown;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.initResult?.jsResult && this.initResult.jsResult.manualCompletionAlwaysShown) {
|
||||||
|
// The init result defines a function to check if a feature is supported, use it.
|
||||||
|
return this.initResult.jsResult.manualCompletionAlwaysShown(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -848,6 +848,7 @@ export type CoreSitePluginsCourseModuleHandlerData = CoreSitePluginsHandlerCommo
|
||||||
coursepagemethod?: string;
|
coursepagemethod?: string;
|
||||||
ptrenabled?: boolean;
|
ptrenabled?: boolean;
|
||||||
supportedfeatures?: Record<string, unknown>;
|
supportedfeatures?: Record<string, unknown>;
|
||||||
|
manualcompletionalwaysshown?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue