MOBILE-3757 course: Display dates and completion inside activity
parent
3f825db799
commit
61a908216d
|
@ -30,6 +30,11 @@
|
|||
<!-- Content. -->
|
||||
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||
|
||||
<!-- Activity info. -->
|
||||
<core-course-module-info *ngIf="showCompletion" [module]="module" [showManualCompletion]="true"
|
||||
(completionChanged)="onCompletionChange()">
|
||||
</core-course-module-info>
|
||||
|
||||
<!-- Description and intro attachments. -->
|
||||
<ion-card *ngIf="description">
|
||||
<ion-item class="ion-text-wrap">
|
||||
|
@ -141,5 +146,4 @@
|
|||
<addon-mod-assign-submission *ngIf="loaded && !canViewAllSubmissions && canViewOwnSubmission" [courseId]="courseId"
|
||||
[moduleId]="module.id">
|
||||
</addon-mod-assign-submission>
|
||||
|
||||
</core-loading>
|
||||
|
|
|
@ -120,7 +120,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
|
|||
AddonModAssignProvider.SUBMITTED_FOR_GRADING_EVENT,
|
||||
(data) => {
|
||||
if (this.assign && data.assignmentId == this.assign.id && data.userId == this.currentUserId) {
|
||||
// Assignment submitted, check completion.
|
||||
// Assignment submitted, check completion.
|
||||
CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata);
|
||||
|
||||
// Reload data since it can have offline data now.
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="fromDate && !isSubmittedForGrading">
|
||||
<ion-item class="ion-text-wrap" *ngIf="showDates && fromDate && !isSubmittedForGrading">
|
||||
<ion-label>
|
||||
<p *ngIf="assign!.intro"
|
||||
[innerHTML]="'addon.mod_assign.allowsubmissionsfromdatesummary' | translate: {'$a': fromDate}">
|
||||
|
@ -66,7 +66,7 @@
|
|||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="assign!.duedate && !isSubmittedForGrading">
|
||||
<ion-item class="ion-text-wrap" *ngIf="showDates && assign!.duedate && !isSubmittedForGrading">
|
||||
<ion-label>
|
||||
<h2>{{ 'addon.mod_assign.duedate' | translate }}</h2>
|
||||
<p *ngIf="assign!.duedate" >{{ assign!.duedate * 1000 | coreFormatDate }}</p>
|
||||
|
|
|
@ -122,6 +122,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
|
|||
allowAddAttempt = false; // Allow adding a new attempt when grading.
|
||||
gradeUrl?: string; // URL to grade in browser.
|
||||
isPreviousAttemptEmpty = true; // Whether the previous attempt contains an empty submission.
|
||||
showDates = false; // Whether to show some dates.
|
||||
|
||||
// Some constants.
|
||||
statusNew = AddonModAssignProvider.SUBMISSION_STATUS_NEW;
|
||||
|
@ -181,6 +182,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
|
|||
*/
|
||||
ngOnInit(): void {
|
||||
this.isSubmittedForGrading = !!this.submitId;
|
||||
this.showDates = !CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.11');
|
||||
|
||||
this.loadData(true);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,11 @@
|
|||
<!-- Content. -->
|
||||
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||
|
||||
<!-- Activity info. -->
|
||||
<core-course-module-info *ngIf="showCompletion" [module]="module" [showManualCompletion]="true"
|
||||
(completionChanged)="onCompletionChange()">
|
||||
</core-course-module-info>
|
||||
|
||||
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
|
||||
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-course-module-description>
|
||||
|
||||
|
|
|
@ -26,6 +26,11 @@
|
|||
<!-- Content. -->
|
||||
<core-loading [hideUntil]="loaded" class="core-loading-center safe-area-page">
|
||||
|
||||
<!-- Activity info. -->
|
||||
<core-course-module-info *ngIf="showCompletion" [module]="module" [showManualCompletion]="true"
|
||||
(completionChanged)="onCompletionChange()">
|
||||
</core-course-module-info>
|
||||
|
||||
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
|
||||
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
|
||||
</core-course-module-description>
|
||||
|
|
|
@ -28,6 +28,11 @@
|
|||
<!-- Content. -->
|
||||
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||
|
||||
<!-- Activity info. -->
|
||||
<core-course-module-info *ngIf="showCompletion" [module]="module" [showManualCompletion]="true"
|
||||
(completionChanged)="onCompletionChange()">
|
||||
</core-course-module-info>
|
||||
|
||||
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
|
||||
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
|
||||
</core-course-module-description>
|
||||
|
|
|
@ -39,6 +39,11 @@
|
|||
<!-- Content. -->
|
||||
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||
|
||||
<!-- Activity info. -->
|
||||
<core-course-module-info *ngIf="showCompletion" [module]="module" [showManualCompletion]="true"
|
||||
(completionChanged)="onCompletionChange()">
|
||||
</core-course-module-info>
|
||||
|
||||
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
|
||||
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
|
||||
</core-course-module-description>
|
||||
|
|
|
@ -28,6 +28,11 @@
|
|||
<!-- Content. -->
|
||||
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||
|
||||
<!-- Activity info. -->
|
||||
<core-course-module-info *ngIf="showCompletion" [module]="module" [showManualCompletion]="true"
|
||||
(completionChanged)="onCompletionChange()">
|
||||
</core-course-module-info>
|
||||
|
||||
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
|
||||
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
|
||||
</core-course-module-description>
|
||||
|
|
|
@ -25,6 +25,11 @@
|
|||
<!-- Content. -->
|
||||
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||
|
||||
<!-- Activity info. -->
|
||||
<core-course-module-info *ngIf="showCompletion" [module]="module" [showManualCompletion]="true"
|
||||
(completionChanged)="onCompletionChange()">
|
||||
</core-course-module-info>
|
||||
|
||||
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
|
||||
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
|
||||
</core-course-module-description>
|
||||
|
|
|
@ -42,6 +42,11 @@
|
|||
</ion-refresher>
|
||||
|
||||
<core-loading [hideUntil]="discussions.loaded" class="core-loading-center">
|
||||
<!-- Activity info. -->
|
||||
<core-course-module-info *ngIf="showCompletion" [module]="module" [showManualCompletion]="true"
|
||||
(completionChanged)="onCompletionChange()">
|
||||
</core-course-module-info>
|
||||
|
||||
<core-course-module-description *ngIf="forum && forum.type != 'single'"
|
||||
[description]="description" [component]="component" [componentId]="componentId" [note]="descriptionNote"
|
||||
contextLevel="module" [contextInstanceId]="module && module.id" [courseId]="courseId">
|
||||
|
|
|
@ -50,6 +50,11 @@
|
|||
</core-search-box>
|
||||
|
||||
<core-loading [hideUntil]="entries.loaded" class="core-loading-center">
|
||||
<!-- Activity info. -->
|
||||
<core-course-module-info *ngIf="showCompletion" [module]="module" [showManualCompletion]="true"
|
||||
(completionChanged)="onCompletionChange()">
|
||||
</core-course-module-info>
|
||||
|
||||
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
|
||||
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
|
||||
</core-course-module-description>
|
||||
|
|
|
@ -32,6 +32,11 @@
|
|||
<!-- Content. -->
|
||||
<core-loading [hideUntil]="loaded" class="core-loading-center safe-area-page">
|
||||
|
||||
<!-- Activity info. -->
|
||||
<core-course-module-info *ngIf="showCompletion" [module]="module" [showManualCompletion]="true"
|
||||
(completionChanged)="onCompletionChange()">
|
||||
</core-course-module-info>
|
||||
|
||||
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
|
||||
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
|
||||
</core-course-module-description>
|
||||
|
|
|
@ -28,6 +28,11 @@
|
|||
<!-- Content. -->
|
||||
<core-loading [hideUntil]="loaded" class="core-loading-center safe-area-page">
|
||||
|
||||
<!-- Activity info. -->
|
||||
<core-course-module-info *ngIf="showCompletion" [module]="module" [showManualCompletion]="true"
|
||||
(completionChanged)="onCompletionChange()">
|
||||
</core-course-module-info>
|
||||
|
||||
<ion-card class="core-warning-card" *ngIf="warning">
|
||||
<ion-item>
|
||||
<ion-icon name="fas-exclamation-triangle" slot="start" aria-hidden="true"></ion-icon>
|
||||
|
|
|
@ -31,6 +31,12 @@
|
|||
<!-- Index/Preview tab. -->
|
||||
<core-tab [title]="'addon.mod_lesson.preview' | translate" (ionSelect)="indexSelected()">
|
||||
<ng-template>
|
||||
|
||||
<!-- Activity info. -->
|
||||
<core-course-module-info *ngIf="showCompletion" [module]="module" [showManualCompletion]="true"
|
||||
(completionChanged)="onCompletionChange()">
|
||||
</core-course-module-info>
|
||||
|
||||
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
|
||||
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
|
||||
</core-course-module-description>
|
||||
|
|
|
@ -18,6 +18,12 @@
|
|||
|
||||
<!-- Content. -->
|
||||
<core-loading [hideUntil]="loaded" class="core-loading-center safe-area-page">
|
||||
|
||||
<!-- Activity info. -->
|
||||
<core-course-module-info *ngIf="showCompletion" [module]="module" [showManualCompletion]="true"
|
||||
(completionChanged)="onCompletionChange()">
|
||||
</core-course-module-info>
|
||||
|
||||
<core-course-module-description *ngIf="lti && lti.showdescriptionlaunch" [description]="description" [component]="component"
|
||||
[componentId]="componentId" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
|
||||
</core-course-module-description>
|
||||
|
|
|
@ -25,6 +25,11 @@
|
|||
<!-- Content. -->
|
||||
<core-loading [hideUntil]="loaded" class="core-loading-center safe-area-page">
|
||||
|
||||
<!-- Activity info. -->
|
||||
<core-course-module-info *ngIf="showCompletion" [module]="module" [showManualCompletion]="true"
|
||||
(completionChanged)="onCompletionChange()">
|
||||
</core-course-module-info>
|
||||
|
||||
<core-course-module-description *ngIf="displayDescription" [description]="description" [component]="component"
|
||||
[componentId]="componentId" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
|
||||
</core-course-module-description>
|
||||
|
|
|
@ -27,6 +27,12 @@
|
|||
|
||||
<!-- Content. -->
|
||||
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||
|
||||
<!-- Activity info. -->
|
||||
<core-course-module-info *ngIf="showCompletion" [module]="module" [showManualCompletion]="true"
|
||||
(completionChanged)="onCompletionChange()">
|
||||
</core-course-module-info>
|
||||
|
||||
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
|
||||
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
|
||||
</core-course-module-description>
|
||||
|
|
|
@ -20,6 +20,11 @@
|
|||
<!-- Content. -->
|
||||
<core-loading [hideUntil]="loaded" class="core-loading-center safe-area-page">
|
||||
|
||||
<!-- Activity info. -->
|
||||
<core-course-module-info *ngIf="showCompletion" [module]="module" [showManualCompletion]="true"
|
||||
(completionChanged)="onCompletionChange()">
|
||||
</core-course-module-info>
|
||||
|
||||
<core-course-module-description *ngIf="mode != 'iframe' && (mode != 'embedded' || displayDescription)"
|
||||
[description]="description" [component]="component" [componentId]="componentId" contextLevel="module"
|
||||
[contextInstanceId]="module.id" [courseId]="courseId">
|
||||
|
|
|
@ -28,6 +28,11 @@
|
|||
<!-- Content. -->
|
||||
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||
|
||||
<!-- Activity info. -->
|
||||
<core-course-module-info *ngIf="showCompletion" [module]="module" [showManualCompletion]="true"
|
||||
(completionChanged)="onCompletionChange()">
|
||||
</core-course-module-info>
|
||||
|
||||
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
|
||||
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
|
||||
</core-course-module-description>
|
||||
|
|
|
@ -29,6 +29,11 @@
|
|||
<!-- Content. -->
|
||||
<core-loading [hideUntil]="loaded" class="core-loading-center safe-area-page">
|
||||
|
||||
<!-- Activity info. -->
|
||||
<core-course-module-info *ngIf="showCompletion" [module]="module" [showManualCompletion]="true"
|
||||
(completionChanged)="onCompletionChange()">
|
||||
</core-course-module-info>
|
||||
|
||||
<core-course-module-description *ngIf="survey && !survey.surveydone && !hasOffline" [description]="description"
|
||||
[component]="component" [componentId]="componentId" contextLevel="module" [contextInstanceId]="module.id"
|
||||
[courseId]="courseId">
|
||||
|
|
|
@ -15,6 +15,11 @@
|
|||
<!-- Content. -->
|
||||
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||
|
||||
<!-- Activity info. -->
|
||||
<core-course-module-info *ngIf="showCompletion" [module]="module" [showManualCompletion]="true"
|
||||
(completionChanged)="onCompletionChange()">
|
||||
</core-course-module-info>
|
||||
|
||||
<core-course-module-description *ngIf="displayDescription" [description]="description" [component]="component"
|
||||
[componentId]="componentId" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
|
||||
</core-course-module-description>
|
||||
|
|
|
@ -47,6 +47,12 @@
|
|||
|
||||
<!-- Content. -->
|
||||
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||
|
||||
<!-- Activity info. -->
|
||||
<core-course-module-info *ngIf="showCompletion" [module]="module" [showManualCompletion]="true"
|
||||
(completionChanged)="onCompletionChange()">
|
||||
</core-course-module-info>
|
||||
|
||||
<div *ngIf="description || pageIsOffline || hasOffline || pageWarning">
|
||||
<core-course-module-description [description]="description" [component]="component" [componentId]="componentId"
|
||||
contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
|
||||
|
|
|
@ -27,6 +27,12 @@
|
|||
|
||||
<!-- Content. -->
|
||||
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||
|
||||
<!-- Activity info. -->
|
||||
<core-course-module-info *ngIf="showCompletion" [module]="module" [showManualCompletion]="true"
|
||||
(completionChanged)="onCompletionChange()">
|
||||
</core-course-module-info>
|
||||
|
||||
<ion-card class="with-borders" *ngIf="phases">
|
||||
<ion-item button (click)="viewPhaseInfo()" detail="true">
|
||||
<ion-label>
|
||||
|
|
|
@ -122,7 +122,14 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
|
|||
this.syncIcon = CoreConstants.ICON_LOADING;
|
||||
|
||||
try {
|
||||
await CoreUtils.ignoreErrors(this.invalidateContent());
|
||||
await CoreUtils.ignoreErrors(Promise.all([
|
||||
this.invalidateContent(),
|
||||
this.showCompletion ? CoreCourse.invalidateModule(this.module.id) : undefined,
|
||||
]));
|
||||
|
||||
if (this.showCompletion) {
|
||||
this.module = await CoreCourse.getModule(this.module.id, this.courseId);
|
||||
}
|
||||
|
||||
await this.loadContent(true, sync, showErrors);
|
||||
} finally {
|
||||
|
|
|
@ -70,12 +70,14 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
|||
isDestroyed = false; // Whether the component is destroyed, used when calling fillContextMenu.
|
||||
contextMenuStatusObserver?: CoreEventObserver; // Observer of package status, used when calling fillContextMenu.
|
||||
contextFileStatusObserver?: CoreEventObserver; // Observer of file status, used when calling fillContextMenu.
|
||||
showCompletion = false; // Whether to show completion inside the activity.
|
||||
|
||||
protected fetchContentDefaultError = 'core.course.errorgetmodule'; // Default error to show when loading contents.
|
||||
protected isCurrentView = false; // Whether the component is in the current view.
|
||||
protected siteId?: string; // Current Site ID.
|
||||
protected statusObserver?: CoreEventObserver; // Observer of package status. Only if setStatusListener is called.
|
||||
protected currentStatus?: string; // The current status of the module. Only if setStatusListener is called.
|
||||
protected completionObserver?: CoreEventObserver;
|
||||
protected logger: CoreLogger;
|
||||
|
||||
constructor(
|
||||
|
@ -94,6 +96,18 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
|||
this.componentId = this.module.id;
|
||||
this.externalUrl = this.module.url;
|
||||
this.courseId = this.courseId || this.module.course!;
|
||||
this.showCompletion = !!CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.11');
|
||||
|
||||
if (this.showCompletion) {
|
||||
this.completionObserver = CoreEvents.on(CoreEvents.COMPLETION_MODULE_VIEWED, async (data) => {
|
||||
if (data && data.cmId == this.module.id) {
|
||||
await CoreCourse.invalidateModule(this.module.id);
|
||||
|
||||
this.module = await CoreCourse.getModule(this.module.id, this.courseId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.blog = await AddonBlog.isPluginEnabled();
|
||||
}
|
||||
|
||||
|
@ -140,7 +154,14 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
|||
this.refreshIcon = CoreConstants.ICON_LOADING;
|
||||
|
||||
try {
|
||||
await CoreUtils.ignoreErrors(this.invalidateContent());
|
||||
await CoreUtils.ignoreErrors(Promise.all([
|
||||
this.invalidateContent(),
|
||||
this.showCompletion ? CoreCourse.invalidateModule(this.module.id) : undefined,
|
||||
]));
|
||||
|
||||
if (this.showCompletion) {
|
||||
this.module = await CoreCourse.getModule(this.module.id, this.courseId);
|
||||
}
|
||||
|
||||
await this.loadContent(true);
|
||||
} finally {
|
||||
|
@ -376,6 +397,16 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* The completion of the modules has changed.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async onCompletionChange(): Promise<void> {
|
||||
// Nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being destroyed.
|
||||
*/
|
||||
|
@ -384,6 +415,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
|||
this.contextMenuStatusObserver?.off();
|
||||
this.contextFileStatusObserver?.off();
|
||||
this.statusObserver?.off();
|
||||
this.completionObserver?.off();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,8 +14,6 @@
|
|||
|
||||
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';
|
||||
|
||||
/**
|
||||
|
@ -32,7 +30,7 @@ export class CoreCourseModuleCompletionBaseComponent implements OnChanges {
|
|||
@Output() completionChanged = new EventEmitter<CoreCourseModuleCompletionData>(); // Notify when completion changes.
|
||||
|
||||
/**
|
||||
* Detect changes on input properties.
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
||||
if (changes.completion && this.completion) {
|
||||
|
@ -47,47 +45,4 @@ export class CoreCourseModuleCompletionBaseComponent implements OnChanges {
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ import { CoreCourseSectionSelectorComponent } from './section-selector/section-s
|
|||
import { CoreCourseTagAreaComponent } from './tag-area/tag-area';
|
||||
import { CoreCourseUnsupportedModuleComponent } from './unsupported-module/unsupported-module';
|
||||
import { CoreCourseModuleCompletionLegacyComponent } from './module-completion-legacy/module-completion-legacy';
|
||||
import { CoreCourseModuleInfoComponent } from './module-info/module-info';
|
||||
import { CoreCourseModuleManualCompletionComponent } from './module-manual-completion/module-manual-completion';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -32,6 +34,8 @@ import { CoreCourseModuleCompletionLegacyComponent } from './module-completion-l
|
|||
CoreCourseModuleCompletionComponent,
|
||||
CoreCourseModuleCompletionLegacyComponent,
|
||||
CoreCourseModuleDescriptionComponent,
|
||||
CoreCourseModuleInfoComponent,
|
||||
CoreCourseModuleManualCompletionComponent,
|
||||
CoreCourseSectionSelectorComponent,
|
||||
CoreCourseTagAreaComponent,
|
||||
CoreCourseUnsupportedModuleComponent,
|
||||
|
@ -46,6 +50,8 @@ import { CoreCourseModuleCompletionLegacyComponent } from './module-completion-l
|
|||
CoreCourseModuleCompletionComponent,
|
||||
CoreCourseModuleCompletionLegacyComponent,
|
||||
CoreCourseModuleDescriptionComponent,
|
||||
CoreCourseModuleInfoComponent,
|
||||
CoreCourseModuleManualCompletionComponent,
|
||||
CoreCourseSectionSelectorComponent,
|
||||
CoreCourseTagAreaComponent,
|
||||
CoreCourseUnsupportedModuleComponent,
|
||||
|
|
|
@ -19,6 +19,7 @@ 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';
|
||||
import { CoreCourseHelper } from '@features/course/services/course-helper';
|
||||
|
||||
/**
|
||||
* Component to handle activity completion in sites previous to 3.11.
|
||||
|
@ -115,4 +116,21 @@ export class CoreCourseModuleCompletionLegacyComponent extends CoreCourseModuleC
|
|||
this.completionDescription = Translate.instant(langKey, translateParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Completion clicked.
|
||||
*
|
||||
* @param event The click event.
|
||||
*/
|
||||
async completionClicked(event: Event): Promise<void> {
|
||||
if (!this.completion) {
|
||||
return;
|
||||
}
|
||||
|
||||
await CoreCourseHelper.changeManualCompletion(this.completion, event);
|
||||
|
||||
this.calculateData();
|
||||
|
||||
this.completionChanged.emit(this.completion);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,27 +27,6 @@
|
|||
</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>
|
||||
<core-course-module-manual-completion *ngIf="showManualCompletion" [completion]="completion" [moduleName]="moduleName"
|
||||
(completionChanged)="completionChanged.emit($event)">
|
||||
</core-course-module-manual-completion>
|
||||
|
|
|
@ -9,10 +9,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.core-module-manual-completion {
|
||||
ion-button {
|
||||
text-transform: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ import { Translate } from '@singletons';
|
|||
export class CoreCourseModuleCompletionComponent extends CoreCourseModuleCompletionBaseComponent {
|
||||
|
||||
@Input() showCompletionConditions = false; // Whether to show activity completion conditions.
|
||||
@Input() showManualCompletion = false; // Whether to show manual completion when completion conditions are disabled.
|
||||
@Input() showManualCompletion = false; // Whether to show manual completion.
|
||||
|
||||
details?: CompletionRule[];
|
||||
accessibleDescription: string | null = null;
|
||||
|
@ -48,21 +48,6 @@ export class CoreCourseModuleCompletionComponent extends CoreCourseModuleComplet
|
|||
return;
|
||||
}
|
||||
|
||||
// Set an accessible description for manual completions with overridden completion state.
|
||||
if (!this.completion.isautomatic && this.completion.overrideby) {
|
||||
const setByData = {
|
||||
$a: {
|
||||
activityname: this.moduleName,
|
||||
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 });
|
||||
}
|
||||
|
||||
// Format rules.
|
||||
this.details = this.completion.details.map((rule: CompletionRule) => {
|
||||
rule.statuscomplete = rule.rulevalue.status == CoreCourseProvider.COMPLETION_COMPLETE ||
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<ion-card *ngIf="module.dates?.length || (module.completiondata && (module.completiondata.isautomatic || showManualCompletion))">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<!-- Activity dates. -->
|
||||
<div *ngIf="module.dates && module.dates.length" class="core-module-dates">
|
||||
<p *ngFor="let date of module.dates">
|
||||
<strong>{{ date.label }}</strong> {{ date.timestamp * 1000 | coreFormatDate:'strftimedatetime' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Module completion. -->
|
||||
<core-course-module-completion *ngIf="module.completiondata" [completion]="module.completiondata"
|
||||
[moduleName]="module.name" [moduleId]="module.id" [showCompletionConditions]="true"
|
||||
[showManualCompletion]="showManualCompletion" (completionChanged)="completionChanged.emit($event)">
|
||||
</core-course-module-completion>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-card>
|
|
@ -0,0 +1,31 @@
|
|||
// (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, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { CoreCourseModule, CoreCourseModuleCompletionData } from '@features/course/services/course-helper';
|
||||
|
||||
/**
|
||||
* Display info about a module: dates and completion.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'core-course-module-info',
|
||||
templateUrl: 'core-course-module-info.html',
|
||||
})
|
||||
export class CoreCourseModuleInfoComponent {
|
||||
|
||||
@Input() module!: CoreCourseModule; // The module to render.
|
||||
@Input() showManualCompletion = false; // Whether to show manual completion.
|
||||
@Output() completionChanged = new EventEmitter<CoreCourseModuleCompletionData>(); // Notify when completion changes.
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<div *ngIf="completion && !completion.isautomatic" 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>
|
|
@ -0,0 +1,5 @@
|
|||
:host {
|
||||
ion-button {
|
||||
text-transform: none;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
// (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, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChange } from '@angular/core';
|
||||
|
||||
import { CoreCourseHelper, CoreCourseModuleCompletionData } from '@features/course/services/course-helper';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
|
||||
/**
|
||||
* Component to display a button for manual completion.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'core-course-module-manual-completion',
|
||||
templateUrl: 'core-course-module-manual-completion.html',
|
||||
styleUrls: ['module-manual-completion.scss'],
|
||||
})
|
||||
export class CoreCourseModuleManualCompletionComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
@Input() completion?: CoreCourseModuleCompletionData; // The completion status.
|
||||
@Input() moduleName?: string; // The name of the module this completion affects.
|
||||
@Output() completionChanged = new EventEmitter<CoreCourseModuleCompletionData>(); // Notify when completion changes.
|
||||
|
||||
accessibleDescription: string | null = null;
|
||||
|
||||
protected manualChangedObserver?: CoreEventObserver;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.manualChangedObserver = CoreEvents.on(CoreEvents.MANUAL_COMPLETION_CHANGED, (data) => {
|
||||
if (this.completion && this.completion.cmid == data.completion.cmid) {
|
||||
this.completion = data.completion;
|
||||
this.calculateData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
||||
if (changes.completion && this.completion) {
|
||||
this.calculateData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected calculateData(): void {
|
||||
if (!this.completion?.isautomatic) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set an accessible description for manual completions with overridden completion state.
|
||||
if (this.completion.overrideby) {
|
||||
const setByData = {
|
||||
$a: {
|
||||
activityname: this.moduleName,
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Completion clicked.
|
||||
*
|
||||
* @param event The click event.
|
||||
*/
|
||||
async completionClicked(event: Event): Promise<void> {
|
||||
if (!this.completion) {
|
||||
return;
|
||||
}
|
||||
|
||||
await CoreCourseHelper.changeManualCompletion(this.completion, event);
|
||||
|
||||
this.calculateData();
|
||||
|
||||
CoreEvents.trigger(CoreEvents.MANUAL_COMPLETION_CHANGED, { completion: this.completion });
|
||||
this.completionChanged.emit(this.completion);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.manualChangedObserver?.off();
|
||||
}
|
||||
|
||||
}
|
|
@ -67,6 +67,7 @@ import { CoreNetworkError } from '@classes/errors/network-error';
|
|||
import { CoreSiteHome } from '@features/sitehome/services/sitehome';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreSiteHomeHomeHandlerService } from '@features/sitehome/services/handlers/sitehome-home';
|
||||
import { CoreStatusWithWarningsWSResponse } from '@services/ws';
|
||||
|
||||
/**
|
||||
* Prefetch info of a module.
|
||||
|
@ -1890,6 +1891,52 @@ export class CoreCourseHelperProvider {
|
|||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Completion clicked.
|
||||
*
|
||||
* @param completion The completion.
|
||||
* @param event The click event.
|
||||
* @return Promise resolved with the result.
|
||||
*/
|
||||
async changeManualCompletion(
|
||||
completion: CoreCourseModuleCompletionData,
|
||||
event?: Event,
|
||||
): Promise<CoreStatusWithWarningsWSResponse | void> {
|
||||
if (!completion) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof completion.cmid == 'undefined' || completion.tracking !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
event?.preventDefault();
|
||||
event?.stopPropagation();
|
||||
|
||||
const modal = await CoreDomUtils.showModalLoading();
|
||||
completion.state = completion.state === 1 ? 0 : 1;
|
||||
|
||||
try {
|
||||
const response = await CoreCourse.markCompletedManually(
|
||||
completion.cmid,
|
||||
completion.state === 1,
|
||||
completion.courseId!,
|
||||
completion.courseName,
|
||||
);
|
||||
|
||||
if (response.offline) {
|
||||
completion.offline = true;
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
completion.state = completion.state === 1 ? 0 : 1;
|
||||
CoreDomUtils.showErrorModalDefault(error, 'core.errorchangecompletion', true);
|
||||
} finally {
|
||||
modal.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const CoreCourseHelper = makeSingleton(CoreCourseHelperProvider);
|
||||
|
|
|
@ -150,9 +150,12 @@ export class CoreCourseProvider {
|
|||
* @param completion Completion status of the module.
|
||||
*/
|
||||
checkModuleCompletion(courseId: number, completion?: CoreCourseModuleCompletionData): void {
|
||||
if (completion && completion.tracking === 2 && completion.state === 0) {
|
||||
if (completion && completion.tracking === CoreCourseProvider.COMPLETION_TRACKING_AUTOMATIC && completion.state === 0) {
|
||||
this.invalidateSections(courseId).finally(() => {
|
||||
CoreEvents.trigger(CoreEvents.COMPLETION_MODULE_VIEWED, { courseId: courseId });
|
||||
CoreEvents.trigger(CoreEvents.COMPLETION_MODULE_VIEWED, {
|
||||
courseId: courseId,
|
||||
cmId: completion.cmid,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -969,6 +972,9 @@ export class CoreCourseProvider {
|
|||
// Ignore errors, shouldn't happen.
|
||||
}
|
||||
|
||||
// Invalidate module now, completion has changed.
|
||||
await this.invalidateModule(cmId, siteId);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
if (CoreUtils.isWebServiceError(error) || !courseId) {
|
||||
|
|
|
@ -18,6 +18,7 @@ import { CoreLogger } from '@singletons/logger';
|
|||
import { CoreSite, CoreSiteInfoResponse, CoreSitePublicConfigResponse } from '@classes/site';
|
||||
import { CoreFilepoolComponentFileEventData } from '@services/filepool';
|
||||
import { CoreNavigationOptions } from '@services/navigator';
|
||||
import { CoreCourseModuleCompletionData } from '@features/course/services/course-helper';
|
||||
|
||||
/**
|
||||
* Observer instance to stop listening to an event.
|
||||
|
@ -45,6 +46,7 @@ export interface CoreEventsData {
|
|||
[CoreEvents.NOTIFICATION_SOUND_CHANGED]: CoreEventNotificationSoundChangedData;
|
||||
[CoreEvents.SELECT_COURSE_TAB]: CoreEventSelectCourseTabData;
|
||||
[CoreEvents.COMPLETION_MODULE_VIEWED]: CoreEventCompletionModuleViewedData;
|
||||
[CoreEvents.MANUAL_COMPLETION_CHANGED]: CoreEventManualCompletionChangedData;
|
||||
[CoreEvents.SECTION_STATUS_CHANGED]: CoreEventSectionStatusChangedData;
|
||||
[CoreEvents.ACTIVITY_DATA_SENT]: CoreEventActivityDataSentData;
|
||||
[CoreEvents.IAB_LOAD_START]: InAppBrowserEvent;
|
||||
|
@ -53,7 +55,7 @@ export interface CoreEventsData {
|
|||
[CoreEvents.COMPONENT_FILE_ACTION]: CoreFilepoolComponentFileEventData;
|
||||
[CoreEvents.FILE_SHARED]: CoreEventFileSharedData;
|
||||
[CoreEvents.APP_LAUNCHED_URL]: CoreEventAppLaunchedData;
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Service to send and listen to events.
|
||||
|
@ -72,6 +74,7 @@ export class CoreEvents {
|
|||
static readonly SITE_UPDATED = 'site_updated';
|
||||
static readonly SITE_DELETED = 'site_deleted';
|
||||
static readonly COMPLETION_MODULE_VIEWED = 'completion_module_viewed';
|
||||
static readonly MANUAL_COMPLETION_CHANGED = 'manual_completion_changed';
|
||||
static readonly USER_DELETED = 'user_deleted';
|
||||
static readonly PACKAGE_STATUS_CHANGED = 'package_status_changed';
|
||||
static readonly COURSE_STATUS_CHANGED = 'course_status_changed';
|
||||
|
@ -330,7 +333,15 @@ export type CoreEventSelectCourseTabData = {
|
|||
* Data passed to COMPLETION_MODULE_VIEWED event.
|
||||
*/
|
||||
export type CoreEventCompletionModuleViewedData = {
|
||||
courseId?: number;
|
||||
courseId: number;
|
||||
cmId?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Data passed to MANUAL_COMPLETION_CHANGED event.
|
||||
*/
|
||||
export type CoreEventManualCompletionChangedData = {
|
||||
completion: CoreCourseModuleCompletionData;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue