commit
						f1c6e5dd28
					
				| @ -8,7 +8,7 @@ | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!badgeLoaded" (ionRefresh)="refreshBadges($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!badgeLoaded" (ionRefresh)="refreshBadges($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
|     <core-loading [hideUntil]="badgeLoaded"> | ||||
|  | ||||
| @ -101,7 +101,7 @@ export class AddonBadgesIssuedBadgePage implements OnInit { | ||||
|      * | ||||
|      * @param refresher Refresher. | ||||
|      */ | ||||
|     async refreshBadges(refresher?: CustomEvent<IonRefresher>): Promise<void> { | ||||
|     async refreshBadges(refresher?: IonRefresher): Promise<void> { | ||||
|         await CoreUtils.ignoreErrors(Promise.all([ | ||||
|             AddonBadges.invalidateUserBadges(this.courseId, this.userId), | ||||
|         ])); | ||||
| @ -110,7 +110,7 @@ export class AddonBadgesIssuedBadgePage implements OnInit { | ||||
|             this.fetchIssuedBadge(), | ||||
|         ])); | ||||
| 
 | ||||
|         refresher?.detail.complete(); | ||||
|         refresher?.complete(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -8,7 +8,7 @@ | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refresh($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refresh($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
|     <core-loading [hideUntil]="loaded" class="core-loading-center"> | ||||
|  | ||||
| @ -250,7 +250,7 @@ export class AddonBlogEntriesPage implements OnInit { | ||||
|      * | ||||
|      * @param refresher Refresher instance. | ||||
|      */ | ||||
|     refresh(refresher?: CustomEvent<IonRefresher>): void { | ||||
|     refresh(refresher?: IonRefresher): void { | ||||
|         const promises = this.entries.map((entry) => | ||||
|             CoreComments.invalidateCommentsData('user', entry.userid, this.component, entry.id, 'format_blog')); | ||||
| 
 | ||||
| @ -269,7 +269,7 @@ export class AddonBlogEntriesPage implements OnInit { | ||||
|         CoreUtils.allPromises(promises).finally(() => { | ||||
|             this.fetchEntries(true).finally(() => { | ||||
|                 if (refresher) { | ||||
|                     refresher?.detail.complete(); | ||||
|                     refresher?.complete(); | ||||
|                 } | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
| @ -21,7 +21,7 @@ | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="doRefresh($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="doRefresh($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
| 
 | ||||
|  | ||||
| @ -431,13 +431,13 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { | ||||
|      * @param done Function to call when done. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async doRefresh(refresher?: CustomEvent<IonRefresher>, done?: () => void): Promise<void> { | ||||
|     async doRefresh(refresher?: IonRefresher, done?: () => void): Promise<void> { | ||||
|         if (!this.loaded) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         await this.refreshData(true).finally(() => { | ||||
|             refresher?.detail.complete(); | ||||
|             refresher?.complete(); | ||||
|             done && done(); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
| @ -7,7 +7,7 @@ | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshData($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshData($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
| 
 | ||||
|  | ||||
| @ -363,7 +363,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave { | ||||
|      * | ||||
|      * @param refresher Refresher. | ||||
|      */ | ||||
|     refreshData(refresher?: CustomEvent<IonRefresher>): void { | ||||
|     refreshData(refresher?: IonRefresher): void { | ||||
|         const promises = [ | ||||
|             AddonCalendar.invalidateAccessInformation(this.courseId), | ||||
|             AddonCalendar.invalidateAllowedEventTypes(this.courseId), | ||||
| @ -384,7 +384,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave { | ||||
| 
 | ||||
|         Promise.all(promises).finally(() => { | ||||
|             this.fetchData().finally(() => { | ||||
|                 refresher?.detail.complete(); | ||||
|                 refresher?.complete(); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
| @ -31,7 +31,7 @@ | ||||
|     </core-context-menu> | ||||
| </core-navbar-buttons> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!eventLoaded" (ionRefresh)="doRefresh($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!eventLoaded" (ionRefresh)="doRefresh($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
|     <core-loading [hideUntil]="eventLoaded"> | ||||
|  | ||||
| @ -402,13 +402,13 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { | ||||
|      * @param showErrors Whether to show sync errors to the user. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async doRefresh(refresher?: CustomEvent<IonRefresher>, done?: () => void, showErrors= false): Promise<void> { | ||||
|     async doRefresh(refresher?: IonRefresher, done?: () => void, showErrors= false): Promise<void> { | ||||
|         if (!this.eventLoaded) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         await this.refreshEvent(true, showErrors).finally(() => { | ||||
|             refresher?.detail.complete(); | ||||
|             refresher?.complete(); | ||||
|             done && done(); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
| @ -26,7 +26,7 @@ | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="doRefresh($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="doRefresh($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
| 
 | ||||
|  | ||||
| @ -265,13 +265,13 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { | ||||
|      * @param showErrors Whether to show sync errors to the user. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async doRefresh(refresher?: CustomEvent<IonRefresher>, done?: () => void, showErrors?: boolean): Promise<void> { | ||||
|     async doRefresh(refresher?: IonRefresher, done?: () => void, showErrors?: boolean): Promise<void> { | ||||
|         if (!this.loaded) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         await this.refreshData(true, showErrors).finally(() => { | ||||
|             refresher?.detail.complete(); | ||||
|             refresher?.complete(); | ||||
|             done && done(); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
| @ -21,7 +21,7 @@ | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <core-split-view> | ||||
|         <ion-refresher slot="fixed" [disabled]="!eventsLoaded" (ionRefresh)="doRefresh($event)"> | ||||
|         <ion-refresher slot="fixed" [disabled]="!eventsLoaded" (ionRefresh)="doRefresh($event.target)"> | ||||
|             <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|         </ion-refresher> | ||||
|         <core-loading [hideUntil]="eventsLoaded"> | ||||
|  | ||||
| @ -549,13 +549,13 @@ export class AddonCalendarListPage implements OnInit, OnDestroy { | ||||
|      * @param showErrors Whether to show sync errors to the user. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async doRefresh(refresher?: CustomEvent<IonRefresher>, done?: () => void, showErrors?: boolean): Promise<void> { | ||||
|     async doRefresh(refresher?: IonRefresher, done?: () => void, showErrors?: boolean): Promise<void> { | ||||
|         if (!this.eventsLoaded) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         await this.refreshEvents(true, showErrors).finally(() => { | ||||
|             refresher?.detail.complete(); | ||||
|             refresher?.complete(); | ||||
|             done && done(); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
| @ -7,7 +7,7 @@ | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!devicesLoaded" (ionRefresh)="refreshDevices($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!devicesLoaded" (ionRefresh)="refreshDevices($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
|     <core-loading [hideUntil]="devicesLoaded"> | ||||
|  | ||||
| @ -106,13 +106,13 @@ export class AddonMessageOutputAirnotifierDevicesPage implements OnInit, OnDestr | ||||
|      * | ||||
|      * @param refresher Refresher. | ||||
|      */ | ||||
|     async refreshDevices(refresher: CustomEvent<IonRefresher>): Promise<void> { | ||||
|     async refreshDevices(refresher: IonRefresher): Promise<void> { | ||||
|         try { | ||||
|             await CoreUtils.ignoreErrors(AddonMessageOutputAirnotifier.invalidateUserDevices()); | ||||
| 
 | ||||
|             await this.fetchDevices(); | ||||
|         } finally { | ||||
|             refresher?.detail.complete(); | ||||
|             refresher?.complete(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -9,7 +9,7 @@ | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshData($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshData($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
| 
 | ||||
|  | ||||
| @ -122,7 +122,7 @@ export class AddonMessagesConversationInfoComponent implements OnInit { | ||||
|      * @param refresher Refresher. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async refreshData(refresher?: CustomEvent<IonRefresher>): Promise<void> { | ||||
|     async refreshData(refresher?: IonRefresher): Promise<void> { | ||||
|         const promises: Promise<void>[] = []; | ||||
| 
 | ||||
|         promises.push(AddonMessages.invalidateConversation(this.conversationId)); | ||||
| @ -131,7 +131,7 @@ export class AddonMessagesConversationInfoComponent implements OnInit { | ||||
|         await Promise.all(promises); | ||||
| 
 | ||||
|         await this.fetchData().finally(() => { | ||||
|             refresher?.detail.complete(); | ||||
|             refresher?.complete(); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -13,7 +13,7 @@ | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <core-split-view> | ||||
|         <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshData($event)"> | ||||
|         <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshData($event.target)"> | ||||
|             <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|         </ion-refresher> | ||||
| 
 | ||||
|  | ||||
| @ -128,7 +128,7 @@ export class AddonMessagesContacts35Page implements OnInit, OnDestroy { | ||||
|      * @param refresher Refresher. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async refreshData(refresher?: CustomEvent<IonRefresher>): Promise<void> { | ||||
|     async refreshData(refresher?: IonRefresher): Promise<void> { | ||||
|         try { | ||||
|             if (this.searchString) { | ||||
|                 // User has searched, update the search.
 | ||||
| @ -139,7 +139,7 @@ export class AddonMessagesContacts35Page implements OnInit, OnDestroy { | ||||
|                 await this.fetchData(); | ||||
|             } | ||||
|         } finally { | ||||
|             refresher?.detail.complete(); | ||||
|             refresher?.complete(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -21,7 +21,7 @@ | ||||
|             <!-- Contacts tab. --> | ||||
|             <core-tab [title]="'addon.messages.contacts' | translate" (ionSelect)="selectTab('confirmed')"> | ||||
|                 <ng-template> | ||||
|                     <ion-refresher slot="fixed" [disabled]="!confirmedLoaded" (ionRefresh)="refreshData($event)"> | ||||
|                     <ion-refresher slot="fixed" [disabled]="!confirmedLoaded" (ionRefresh)="refreshData($event.target)"> | ||||
|                         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|                     </ion-refresher> | ||||
|                     <core-loading [hideUntil]="confirmedLoaded" class="core-loading-center"> | ||||
| @ -57,7 +57,7 @@ | ||||
|             <!-- Requests tab. --> | ||||
|             <core-tab [title]="'addon.messages.requests' | translate" (ionSelect)="selectTab('requests')" [badge]="requestsBadge"> | ||||
|                 <ng-template> | ||||
|                     <ion-refresher slot="fixed" [disabled]="!requestsLoaded" (ionRefresh)="refreshData($event)"> | ||||
|                     <ion-refresher slot="fixed" [disabled]="!requestsLoaded" (ionRefresh)="refreshData($event.target)"> | ||||
|                         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|                     </ion-refresher> | ||||
|                     <core-loading [hideUntil]="requestsLoaded" class="core-loading-center"> | ||||
|  | ||||
| @ -185,7 +185,7 @@ export class AddonMessagesContactsPage implements OnInit, OnDestroy { | ||||
|      * @param refresher Refresher. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async refreshData(refresher?: CustomEvent<IonRefresher>): Promise<void> { | ||||
|     async refreshData(refresher?: IonRefresher): Promise<void> { | ||||
|         try { | ||||
|             if (this.selected == 'confirmed') { | ||||
|                 // No need to invalidate contacts, we always try to get the latest.
 | ||||
| @ -198,7 +198,7 @@ export class AddonMessagesContactsPage implements OnInit, OnDestroy { | ||||
|                 await this.requestsFetchData(true); | ||||
|             } | ||||
|         } finally { | ||||
|             refresher?.detail.complete(); | ||||
|             refresher?.complete(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -13,7 +13,7 @@ | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <core-split-view> | ||||
|         <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshData($event)"> | ||||
|         <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshData($event.target)"> | ||||
|             <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|         </ion-refresher> | ||||
| 
 | ||||
|  | ||||
| @ -168,7 +168,7 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy { | ||||
|      * @param refreshUnreadCounts Whteher to refresh unread counts. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async refreshData(refresher?: CustomEvent<IonRefresher>, refreshUnreadCounts: boolean = true): Promise<void> { | ||||
|     async refreshData(refresher?: IonRefresher, refreshUnreadCounts: boolean = true): Promise<void> { | ||||
|         const promises: Promise<void>[] = []; | ||||
|         promises.push(AddonMessages.invalidateDiscussionsCache(this.siteId)); | ||||
| 
 | ||||
| @ -178,7 +178,7 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy { | ||||
| 
 | ||||
|         await CoreUtils.allPromises(promises).finally(() => this.fetchData().finally(() => { | ||||
|             if (refresher) { | ||||
|                 refresher?.detail.complete(); | ||||
|                 refresher?.complete(); | ||||
|             } | ||||
|         })); | ||||
|     } | ||||
|  | ||||
| @ -19,7 +19,7 @@ | ||||
| </ion-header> | ||||
| <ion-content class="core-expand-max"> | ||||
|     <core-split-view> | ||||
|         <ion-refresher slot="fixed" [disabled]="!loaded || !currentListEl" (ionRefresh)="refreshData($event)"> | ||||
|         <ion-refresher slot="fixed" [disabled]="!loaded || !currentListEl" (ionRefresh)="refreshData($event.target)"> | ||||
|             <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|         </ion-refresher> | ||||
| 
 | ||||
|  | ||||
| @ -700,7 +700,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | ||||
|      * @param refreshUnreadCounts Whether to refresh unread counts. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async refreshData(refresher?: CustomEvent<IonRefresher>, refreshUnreadCounts: boolean = true): Promise<void> { | ||||
|     async refreshData(refresher?: IonRefresher, refreshUnreadCounts: boolean = true): Promise<void> { | ||||
|         // Don't invalidate conversations and so, they always try to get latest data.
 | ||||
|         try { | ||||
|             await AddonMessages.invalidateContactRequestsCountCache(this.siteId); | ||||
| @ -709,7 +709,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { | ||||
|                 await this.fetchData(refreshUnreadCounts); | ||||
|             } finally { | ||||
|                 if (refresher) { | ||||
|                     refresher?.detail.complete(); | ||||
|                     refresher?.complete(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @ -7,7 +7,7 @@ | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!preferencesLoaded" (ionRefresh)="refreshPreferences($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!preferencesLoaded" (ionRefresh)="refreshPreferences($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
|     <core-loading [hideUntil]="preferencesLoaded"> | ||||
|  | ||||
| @ -251,10 +251,10 @@ export class AddonMessagesSettingsPage implements OnInit, OnDestroy { | ||||
|      * | ||||
|      * @param refresher Refresher. | ||||
|      */ | ||||
|     refreshPreferences(refresher?: CustomEvent<IonRefresher>): void { | ||||
|     refreshPreferences(refresher?: IonRefresher): void { | ||||
|         AddonMessages.invalidateMessagePreferences().finally(() => { | ||||
|             this.fetchPreferences().finally(() => { | ||||
|                 refresher?.detail.complete(); | ||||
|                 refresher?.complete(); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
| @ -35,7 +35,7 @@ | ||||
|         <ion-item class="ion-text-wrap"> | ||||
|             <ion-label> | ||||
|                 <core-format-text [text]="description" [component]="component" [componentId]="componentId" maxHeight="120" | ||||
|                     contextLevel="module" [contextInstanceId]="module!.id" [courseId]="courseId" (click)="expandDescription($event)"> | ||||
|                     contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId" (click)="expandDescription($event)"> | ||||
|                 </core-format-text> | ||||
|             </ion-label> | ||||
|         </ion-item> | ||||
| @ -136,7 +136,7 @@ | ||||
| 
 | ||||
|     <!-- If it's a student, display his submission. --> | ||||
|     <addon-mod-assign-submission *ngIf="loaded && !canViewAllSubmissions && canViewOwnSubmission" [courseId]="courseId" | ||||
|         [moduleId]="module!.id"> | ||||
|         [moduleId]="module.id"> | ||||
|     </addon-mod-assign-submission> | ||||
| 
 | ||||
| </core-loading> | ||||
|  | ||||
| @ -120,7 +120,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo | ||||
|             (data) => { | ||||
|                 if (this.assign && data.assignmentId == this.assign.id && data.userId == this.currentUserId) { | ||||
|                 // Assignment submitted, check completion.
 | ||||
|                     CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata); | ||||
|                     CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata); | ||||
| 
 | ||||
|                     // Reload data since it can have offline data now.
 | ||||
|                     this.showLoadingAndRefresh(true, false); | ||||
| @ -140,7 +140,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo | ||||
| 
 | ||||
|         try { | ||||
|             await AddonModAssign.logView(this.assign!.id, this.assign!.name); | ||||
|             CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata); | ||||
|             CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata); | ||||
|         } catch { | ||||
|             // Ignore errors. Just don't check Module completion.
 | ||||
|         } | ||||
| @ -164,11 +164,11 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo | ||||
|         if (this.assign && (this.description || this.assign.introattachments)) { | ||||
|             CoreTextUtils.viewText(Translate.instant('core.description'), this.description || '', { | ||||
|                 component: this.component, | ||||
|                 componentId: this.module!.id, | ||||
|                 componentId: this.module.id, | ||||
|                 files: this.assign.introattachments, | ||||
|                 filter: true, | ||||
|                 contextLevel: 'module', | ||||
|                 instanceId: this.module!.id, | ||||
|                 instanceId: this.module.id, | ||||
|                 courseId: this.courseId, | ||||
|             }); | ||||
|         } | ||||
| @ -186,7 +186,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo | ||||
| 
 | ||||
|         // Get assignment data.
 | ||||
|         try { | ||||
|             this.assign = await AddonModAssign.getAssignment(this.courseId!, this.module!.id); | ||||
|             this.assign = await AddonModAssign.getAssignment(this.courseId, this.module.id); | ||||
| 
 | ||||
|             this.dataRetrieved.emit(this.assign); | ||||
|             this.description = this.assign.intro; | ||||
| @ -200,7 +200,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo | ||||
|             this.hasOffline = await AddonModAssignOffline.hasAssignOfflineData(this.assign.id); | ||||
| 
 | ||||
|             // Get assignment submissions.
 | ||||
|             const submissions = await AddonModAssign.getSubmissions(this.assign.id, { cmId: this.module!.id }); | ||||
|             const submissions = await AddonModAssign.getSubmissions(this.assign.id, { cmId: this.module.id }); | ||||
|             const time = CoreTimeUtils.timestamp(); | ||||
| 
 | ||||
|             this.canViewAllSubmissions = submissions.canviewsubmissions; | ||||
| @ -244,7 +244,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo | ||||
| 
 | ||||
|             try { | ||||
|                 // Check if the user can view their own submission.
 | ||||
|                 await AddonModAssign.getSubmissionStatus(this.assign.id, { cmId: this.module!.id }); | ||||
|                 await AddonModAssign.getSubmissionStatus(this.assign.id, { cmId: this.module.id }); | ||||
|                 this.canViewOwnSubmission = true; | ||||
|             } catch (error) { | ||||
|                 this.canViewOwnSubmission = false; | ||||
| @ -269,7 +269,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo | ||||
| 
 | ||||
|         const submissionStatus = await AddonModAssign.getSubmissionStatus(this.assign!.id, { | ||||
|             groupId: this.group, | ||||
|             cmId: this.module!.id, | ||||
|             cmId: this.module.id, | ||||
|         }); | ||||
| 
 | ||||
|         this.summary = submissionStatus.gradingsummary; | ||||
| @ -345,7 +345,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo | ||||
|     protected async invalidateContent(): Promise<void> { | ||||
|         const promises: Promise<void>[] = []; | ||||
| 
 | ||||
|         promises.push(AddonModAssign.invalidateAssignmentData(this.courseId!)); | ||||
|         promises.push(AddonModAssign.invalidateAssignmentData(this.courseId)); | ||||
| 
 | ||||
|         if (this.assign) { | ||||
|             promises.push(AddonModAssign.invalidateAllSubmissionData(this.assign.id)); | ||||
|  | ||||
| @ -14,7 +14,7 @@ | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!activityComponent?.loaded" (ionRefresh)="activityComponent?.doRefresh($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!activityComponent?.loaded" (ionRefresh)="activityComponent?.doRefresh($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
| 
 | ||||
|  | ||||
| @ -14,7 +14,7 @@ | ||||
| 
 | ||||
| <ion-content> | ||||
|     <core-split-view> | ||||
|         <ion-refresher slot="fixed" [disabled]="!loaded || !submissions.loaded" (ionRefresh)="refreshList($event)"> | ||||
|         <ion-refresher slot="fixed" [disabled]="!loaded || !submissions.loaded" (ionRefresh)="refreshList($event.target)"> | ||||
|             <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|         </ion-refresher> | ||||
|         <core-loading [hideUntil]="loaded && submissions.loaded"> | ||||
|  | ||||
| @ -328,9 +328,9 @@ export class AddonModAssignSubmissionListPage implements AfterViewInit, OnDestro | ||||
|      * | ||||
|      * @param refresher Refresher. | ||||
|      */ | ||||
|     refreshList(refresher?: CustomEvent<IonRefresher>): void { | ||||
|     refreshList(refresher?: IonRefresher): void { | ||||
|         this.refreshAllData(true).finally(() => { | ||||
|             refresher?.detail.complete(); | ||||
|             refresher?.complete(); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -19,7 +19,7 @@ | ||||
| </ion-header> | ||||
| <ion-content> | ||||
| 
 | ||||
|     <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshSubmission($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshSubmission($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
|     <core-loading [hideUntil]="loaded"> | ||||
|  | ||||
| @ -155,9 +155,9 @@ export class AddonModAssignSubmissionReviewPage implements OnInit, CanLeave { | ||||
|      * | ||||
|      * @param refresher Refresher. | ||||
|      */ | ||||
|     refreshSubmission(refresher?: CustomEvent<IonRefresher>): void { | ||||
|     refreshSubmission(refresher?: IonRefresher): void { | ||||
|         this.refreshAllData().finally(() => { | ||||
|             refresher?.detail.complete(); | ||||
|             refresher?.complete(); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -24,7 +24,7 @@ | ||||
| <core-loading [hideUntil]="loaded" class="core-loading-center"> | ||||
| 
 | ||||
|     <core-course-module-description [description]="description" [component]="component" [componentId]="componentId" | ||||
|         contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId"></core-course-module-description> | ||||
|         contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"></core-course-module-description> | ||||
| 
 | ||||
|     <ion-card class="core-warning-card" *ngIf="warning"> | ||||
|         <ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon> | ||||
| @ -38,7 +38,7 @@ | ||||
|         </core-navigation-bar> | ||||
| 
 | ||||
|         <core-format-text [component]="component" [componentId]="componentId" [text]="chapterContent" contextLevel="module" | ||||
|             [contextInstanceId]="module?.id" [courseId]="courseId"></core-format-text> | ||||
|             [contextInstanceId]="module.id" [courseId]="courseId"></core-format-text> | ||||
|         <div class="ion-margin-top" *ngIf="tagsEnabled && tags?.length > 0"> | ||||
|             <strong>{{ 'core.tag.tags' | translate }}: </strong> | ||||
|             <core-tag-list [tags]="tags"></core-tag-list> | ||||
|  | ||||
| @ -87,7 +87,7 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp | ||||
|         const modal = await ModalController.create({ | ||||
|             component: AddonModBookTocComponent, | ||||
|             componentProps: { | ||||
|                 moduleId: this.module!.id, | ||||
|                 moduleId: this.module.id, | ||||
|                 chapters: this.chapters, | ||||
|                 selected: this.currentChapter, | ||||
|                 courseId: this.courseId, | ||||
| @ -129,7 +129,7 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp | ||||
|      * @return Resolved when done. | ||||
|      */ | ||||
|     protected invalidateContent(): Promise<void> { | ||||
|         return AddonModBook.invalidateContent(this.module!.id, this.courseId!); | ||||
|         return AddonModBook.invalidateContent(this.module.id, this.courseId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -143,7 +143,7 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp | ||||
|         let downloadResult: CoreCourseResourceDownloadResult | undefined; | ||||
| 
 | ||||
|         // Try to get the book data. Ignore errors since this WS isn't available in some Moodle versions.
 | ||||
|         promises.push(CoreUtils.ignoreErrors(AddonModBook.getBook(this.courseId!, this.module!.id)) | ||||
|         promises.push(CoreUtils.ignoreErrors(AddonModBook.getBook(this.courseId, this.module.id)) | ||||
|             .then((book) => { | ||||
|                 if (!book) { | ||||
|                     return; | ||||
| @ -169,8 +169,8 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp | ||||
|         try { | ||||
|             await Promise.all(promises); | ||||
| 
 | ||||
|             this.contentsMap = AddonModBook.getContentsMap(this.module!.contents); | ||||
|             this.chapters = AddonModBook.getTocList(this.module!.contents); | ||||
|             this.contentsMap = AddonModBook.getContentsMap(this.module.contents); | ||||
|             this.chapters = AddonModBook.getTocList(this.module.contents); | ||||
| 
 | ||||
|             if (typeof this.currentChapter == 'undefined' && typeof this.initialChapterId != 'undefined' && this.chapters) { | ||||
|                 // Initial chapter set. Validate that the chapter exists.
 | ||||
| @ -211,7 +211,7 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp | ||||
|         this.content?.scrollToTop(); | ||||
| 
 | ||||
|         try { | ||||
|             const content = await AddonModBook.getChapterContent(this.contentsMap, chapterId, this.module!.id); | ||||
|             const content = await AddonModBook.getChapterContent(this.contentsMap, chapterId, this.module.id); | ||||
| 
 | ||||
|             this.tags = this.tagsEnabled ? this.contentsMap[this.currentChapter].tags : []; | ||||
| 
 | ||||
| @ -228,14 +228,14 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp | ||||
| 
 | ||||
|             // Chapter loaded, log view. We don't return the promise because we don't want to block the user for this.
 | ||||
|             await CoreUtils.ignoreErrors(AddonModBook.logView( | ||||
|                 this.module!.instance!, | ||||
|                 this.module.instance!, | ||||
|                 logChapterId ? chapterId : undefined, | ||||
|                 this.module!.name, | ||||
|                 this.module.name, | ||||
|             )); | ||||
| 
 | ||||
|             // Module is completed when last chapter is viewed, so we only check completion if the last is reached.
 | ||||
|             if (!this.nextChapter) { | ||||
|                 CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata); | ||||
|                 CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata); | ||||
|             } | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'addon.mod_book.errorchapter', true); | ||||
|  | ||||
| @ -13,7 +13,7 @@ | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!activityComponent?.loaded" (ionRefresh)="activityComponent?.doRefresh($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!activityComponent?.loaded" (ionRefresh)="activityComponent?.doRefresh($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
| 
 | ||||
|  | ||||
| @ -26,7 +26,7 @@ | ||||
| <core-loading [hideUntil]="loaded" class="core-loading-center"> | ||||
| 
 | ||||
|     <core-course-module-description [description]="description" [component]="component" [componentId]="componentId" | ||||
|         contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId"> | ||||
|         contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"> | ||||
|     </core-course-module-description> | ||||
| 
 | ||||
|     <ion-list *ngIf="subfolder && (subfolder!.files.length + subfolder!.folders.length > 0)"> | ||||
|  | ||||
| @ -55,7 +55,7 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo | ||||
|         this.canGetFolder = AddonModFolder.isGetFolderWSAvailable(); | ||||
| 
 | ||||
|         if (this.subfolder) { | ||||
|             this.description = this.folderInstance ? this.folderInstance.intro : this.module!.description; | ||||
|             this.description = this.folderInstance ? this.folderInstance.intro : this.module.description; | ||||
| 
 | ||||
|             this.loaded = true; | ||||
|             this.refreshIcon = CoreConstants.ICON_REFRESH; | ||||
| @ -67,8 +67,8 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo | ||||
|             await this.loadContent(); | ||||
| 
 | ||||
|             try { | ||||
|                 await AddonModFolder.logView(this.module!.instance!, this.module!.name); | ||||
|                 CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata); | ||||
|                 await AddonModFolder.logView(this.module.instance!, this.module.name); | ||||
|                 CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata); | ||||
|             } catch { | ||||
|                 // Ignore errors.
 | ||||
|             } | ||||
| @ -84,7 +84,7 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo | ||||
|      * @return Resolved when done. | ||||
|      */ | ||||
|     protected async invalidateContent(): Promise<void> { | ||||
|         await AddonModFolder.invalidateContent(this.module!.id, this.courseId!); | ||||
|         await AddonModFolder.invalidateContent(this.module.id, this.courseId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -96,22 +96,22 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo | ||||
|     protected async fetchContent(refresh = false): Promise<void> { | ||||
|         try { | ||||
|             if (this.canGetFolder) { | ||||
|                 this.folderInstance = await AddonModFolder.getFolder(this.courseId!, this.module!.id); | ||||
|                 await CoreCourse.loadModuleContents(this.module!, this.courseId, undefined, false, refresh); | ||||
|                 this.folderInstance = await AddonModFolder.getFolder(this.courseId, this.module.id); | ||||
|                 await CoreCourse.loadModuleContents(this.module, this.courseId, undefined, false, refresh); | ||||
|             } else { | ||||
|                 const module = await CoreCourse.getModule(this.module!.id, this.courseId); | ||||
|                 const module = await CoreCourse.getModule(this.module.id, this.courseId); | ||||
| 
 | ||||
|                 if (!module.contents.length && this.module!.contents.length && !CoreApp.isOnline()) { | ||||
|                 if (!module.contents.length && this.module.contents.length && !CoreApp.isOnline()) { | ||||
|                     // The contents might be empty due to a cached data. Use the old ones.
 | ||||
|                     module.contents = this.module!.contents; | ||||
|                     module.contents = this.module.contents; | ||||
|                 } | ||||
|                 this.module = module; | ||||
|             } | ||||
| 
 | ||||
|             this.dataRetrieved.emit(this.folderInstance || this.module); | ||||
| 
 | ||||
|             this.description = this.folderInstance ? this.folderInstance.intro : this.module!.description; | ||||
|             this.subfolder = AddonModFolderHelper.formatContents(this.module!.contents); | ||||
|             this.description = this.folderInstance ? this.folderInstance.intro : this.module.description; | ||||
|             this.subfolder = AddonModFolderHelper.formatContents(this.module.contents); | ||||
|         } finally { | ||||
|             this.fillContextMenu(refresh); | ||||
|         } | ||||
|  | ||||
| @ -15,7 +15,7 @@ | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="subfolder || !activityComponent?.loaded" | ||||
|         (ionRefresh)="activityComponent?.doRefresh($event)"> | ||||
|         (ionRefresh)="activityComponent?.doRefresh($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
| 
 | ||||
|  | ||||
| @ -37,7 +37,7 @@ | ||||
| 
 | ||||
| <!-- Content. --> | ||||
| <core-split-view> | ||||
|     <ion-refresher slot="fixed" [disabled]="!discussions.loaded" (ionRefresh)="doRefresh($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!discussions.loaded" (ionRefresh)="doRefresh($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
| 
 | ||||
|  | ||||
| @ -135,7 +135,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom | ||||
|             this.eventReceived.bind(this, false), | ||||
|         ); | ||||
|         this.changeDiscObserver = CoreEvents.on(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, data => { | ||||
|             if ((this.forum && this.forum.id === data.forumId) || data.cmId === this.module!.id) { | ||||
|             if ((this.forum && this.forum.id === data.forumId) || data.cmId === this.module.id) { | ||||
|                 AddonModForum.invalidateDiscussionsList(this.forum!.id).finally(() => { | ||||
|                     if (data.discussionId) { | ||||
|                         // Discussion changed, search it in the list of discussions.
 | ||||
| @ -198,7 +198,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom | ||||
|             AddonModForum.instance | ||||
|                 .logView(this.forum.id, this.forum.name) | ||||
|                 .then(async () => { | ||||
|                     CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata); | ||||
|                     CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata); | ||||
| 
 | ||||
|                     return; | ||||
|                 }), | ||||
| @ -324,7 +324,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom | ||||
| 
 | ||||
|         promises.push( | ||||
|             AddonModForum.instance | ||||
|                 .getAccessInformation(this.forum.id, { cmId: this.module!.id }) | ||||
|                 .getAccessInformation(this.forum.id, { cmId: this.module.id }) | ||||
|                 .then(async accessInfo => { | ||||
|                     // Disallow adding discussions if cut-off date is reached and the user has not the
 | ||||
|                     // capability to override it.
 | ||||
| @ -341,7 +341,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom | ||||
|             // Use the canAddDiscussion WS to check if the user can pin discussions.
 | ||||
|             promises.push( | ||||
|                 AddonModForum.instance | ||||
|                     .canAddDiscussionToAll(this.forum.id, { cmId: this.module!.id }) | ||||
|                     .canAddDiscussionToAll(this.forum.id, { cmId: this.module.id }) | ||||
|                     .then(async response => { | ||||
|                         this.canPin = !!response.canpindiscussions; | ||||
| 
 | ||||
| @ -525,7 +525,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom | ||||
|     protected async invalidateContent(): Promise<void> { | ||||
|         const promises: Promise<void>[] = []; | ||||
| 
 | ||||
|         promises.push(AddonModForum.invalidateForumData(this.courseId!)); | ||||
|         promises.push(AddonModForum.invalidateForumData(this.courseId)); | ||||
| 
 | ||||
|         if (this.forum) { | ||||
|             promises.push(AddonModForum.invalidateDiscussionsList(this.forum.id)); | ||||
| @ -546,7 +546,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected sync(): Promise<AddonModForumSyncResult> { | ||||
|         return AddonModForumPrefetchHandler.sync(this.module!, this.courseId!); | ||||
|         return AddonModForumPrefetchHandler.sync(this.module, this.courseId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -582,7 +582,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom | ||||
|         isNewDiscussion: boolean, | ||||
|         data: AddonModForumNewDiscussionData | AddonModForumReplyDiscussionData, | ||||
|     ): void { | ||||
|         if ((this.forum && this.forum.id === data.forumId) || data.cmId === this.module?.id) { | ||||
|         if ((this.forum && this.forum.id === data.forumId) || data.cmId === this.module.id) { | ||||
|             this.showLoadingAndRefresh(false).finally(() => { | ||||
|                 // If it's a new discussion in tablet mode, try to open it.
 | ||||
|                 if (isNewDiscussion && CoreScreen.isTablet) { | ||||
| @ -606,7 +606,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom | ||||
|             }); | ||||
| 
 | ||||
|             // Check completion since it could be configured to complete once the user adds a new discussion or replies.
 | ||||
|             CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata); | ||||
|             CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -668,7 +668,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom | ||||
|             componentProps: { | ||||
|                 discussion, | ||||
|                 forumId: this.forum!.id, | ||||
|                 cmId: this.module!.id, | ||||
|                 cmId: this.module.id, | ||||
|             }, | ||||
|             event, | ||||
|         }); | ||||
| @ -733,7 +733,7 @@ class AddonModForumDiscussionsManager extends CorePageItemsListManager<Discussio | ||||
|     getItemQueryParams(discussion: DiscussionItem): Params { | ||||
|         return { | ||||
|             courseId: this.component.courseId, | ||||
|             cmId: this.component.module!.id, | ||||
|             cmId: this.component.module.id, | ||||
|             forumId: this.component.forum!.id, | ||||
|             ...(this.isOnlineDiscussion(discussion) ? { discussion, trackPosts: this.component.trackPosts } : {}), | ||||
|         }; | ||||
|  | ||||
| @ -61,7 +61,7 @@ | ||||
|     </core-context-menu> | ||||
| </core-navbar-buttons> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!discussionLoaded" (ionRefresh)="doRefresh($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!discussionLoaded" (ionRefresh)="doRefresh($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
| 
 | ||||
|  | ||||
| @ -21,7 +21,7 @@ import { CoreRatingOffline } from '@features/rating/services/rating-offline'; | ||||
| import { CoreRatingSyncProvider } from '@features/rating/services/rating-sync'; | ||||
| import { CoreUser } from '@features/user/services/user'; | ||||
| import { CanLeave } from '@guards/can-leave'; | ||||
| import { IonContent } from '@ionic/angular'; | ||||
| import { IonContent, IonRefresher } from '@ionic/angular'; | ||||
| import { CoreApp } from '@services/app'; | ||||
| import { CoreNavigator } from '@services/navigator'; | ||||
| import { CoreScreen } from '@services/screen'; | ||||
| @ -612,10 +612,10 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes | ||||
|      * @param showErrors If show errors to the user of hide them. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async doRefresh(refresher?: any, done?: () => void, showErrors: boolean = false): Promise<void> { | ||||
|     async doRefresh(refresher?: IonRefresher | null, done?: () => void, showErrors: boolean = false): Promise<void> { | ||||
|         if (this.discussionLoaded) { | ||||
|             await this.refreshPosts(true, showErrors).finally(() => { | ||||
|                 refresher && refresher.complete(); | ||||
|                 refresher?.complete(); | ||||
|                 done && done(); | ||||
|             }); | ||||
|         } | ||||
| @ -651,7 +651,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes | ||||
|      * @param type Sort type. | ||||
|      * @return Promised resolved when done. | ||||
|      */ | ||||
|     changeSort(type: SortType): Promise<any> { | ||||
|     changeSort(type: SortType): Promise<void> { | ||||
|         this.discussionLoaded = false; | ||||
|         this.sort = type; | ||||
|         CoreSites.getCurrentSite()!.setLocalSiteConfig('AddonModForumDiscussionSort', this.sort); | ||||
|  | ||||
| @ -36,7 +36,7 @@ | ||||
|     <div class="addon-mod-imscp-container"> | ||||
|         <core-navigation-bar [previous]="previousItem" [next]="nextItem" (action)="loadItem($event)" [info]="description" | ||||
|             [title]="'core.description' | translate" [component]="component" [componentId]="componentId" contextLevel="module" | ||||
|             [contextInstanceId]="module?.id" [courseId]="courseId"> | ||||
|             [contextInstanceId]="module.id" [courseId]="courseId"> | ||||
|         </core-navigation-bar> | ||||
|         <core-iframe [src]="src"></core-iframe> | ||||
|     </div> | ||||
|  | ||||
| @ -59,8 +59,8 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom | ||||
|         await this.loadContent(); | ||||
| 
 | ||||
|         try { | ||||
|             await AddonModImscp.logView(this.module!.instance!, this.module!.name); | ||||
|             CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata); | ||||
|             await AddonModImscp.logView(this.module.instance!, this.module.name); | ||||
|             CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata); | ||||
|         } catch { | ||||
|             // Ignore errors.
 | ||||
|         } | ||||
| @ -72,7 +72,7 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom | ||||
|      * @return Resolved when done. | ||||
|      */ | ||||
|     protected async invalidateContent(): Promise<void> { | ||||
|         await AddonModImscp.invalidateContent(this.module!.id, this.courseId!); | ||||
|         await AddonModImscp.invalidateContent(this.module.id, this.courseId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -85,7 +85,7 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom | ||||
|         let downloadResult: CoreCourseResourceDownloadResult; | ||||
|         const promises: Promise<void>[] = []; | ||||
| 
 | ||||
|         promises.push(AddonModImscp.getImscp(this.courseId!, this.module!.id).then((imscp) => { | ||||
|         promises.push(AddonModImscp.getImscp(this.courseId, this.module.id).then((imscp) => { | ||||
|             this.description = imscp.intro; | ||||
|             this.dataRetrieved.emit(imscp); | ||||
| 
 | ||||
| @ -101,7 +101,7 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom | ||||
|         try { | ||||
|             await Promise.all(promises); | ||||
| 
 | ||||
|             this.items = AddonModImscp.createItemList(this.module!.contents); | ||||
|             this.items = AddonModImscp.createItemList(this.module.contents); | ||||
| 
 | ||||
|             if (this.items.length && typeof this.currentItem == 'undefined') { | ||||
|                 this.currentItem = this.items[0].href; | ||||
| @ -129,7 +129,7 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async loadItem(itemId?: string): Promise<void> { | ||||
|         const src = await AddonModImscp.getIframeSrc(this.module!, itemId); | ||||
|         const src = await AddonModImscp.getIframeSrc(this.module, itemId); | ||||
|         this.currentItem = itemId; | ||||
|         this.previousItem = itemId ? AddonModImscp.getPreviousItem(this.items, itemId) : ''; | ||||
|         this.nextItem = itemId ? AddonModImscp.getNextItem(this.items, itemId) : ''; | ||||
|  | ||||
| @ -32,7 +32,7 @@ | ||||
|         <core-tab [title]="'addon.mod_lesson.preview' | translate" (ionSelect)="indexSelected()"> | ||||
|             <ng-template> | ||||
|                 <core-course-module-description [description]="description" [component]="component" [componentId]="componentId" | ||||
|                     contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId"> | ||||
|                     contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"> | ||||
|                 </core-course-module-description> | ||||
| 
 | ||||
|                 <!-- Prevent access messages. Only show the first one. --> | ||||
|  | ||||
| @ -146,7 +146,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo | ||||
|             let lessonReady = true; | ||||
|             this.askPassword = false; | ||||
| 
 | ||||
|             this.lesson = await AddonModLesson.getLesson(this.courseId!, this.module!.id); | ||||
|             this.lesson = await AddonModLesson.getLesson(this.courseId, this.module.id); | ||||
| 
 | ||||
|             this.dataRetrieved.emit(this.lesson); | ||||
|             this.description = this.lesson.intro; // Show description only if intro is present.
 | ||||
| @ -156,7 +156,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo | ||||
|                 await this.syncActivity(showErrors); | ||||
|             } | ||||
| 
 | ||||
|             this.accessInfo = await AddonModLesson.getAccessInformation(this.lesson.id, { cmId: this.module!.id }); | ||||
|             this.accessInfo = await AddonModLesson.getAccessInformation(this.lesson.id, { cmId: this.module.id }); | ||||
|             this.canManage = this.accessInfo.canmanage; | ||||
|             this.canViewReports = this.accessInfo.canviewreports; | ||||
|             this.preventReasons = []; | ||||
| @ -227,7 +227,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo | ||||
|         } | ||||
| 
 | ||||
|         const promises: Promise<unknown>[] = []; | ||||
|         const options = { cmId: this.module!.id }; | ||||
|         const options = { cmId: this.module.id }; | ||||
| 
 | ||||
|         // Check if there is offline data.
 | ||||
|         promises.push(AddonModLessonSync.hasDataToSync(this.lesson.id, this.accessInfo.attemptscount).then((hasData) => { | ||||
| @ -293,7 +293,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo | ||||
|     protected hasSyncSucceed(result: AddonModLessonSyncResult): boolean { | ||||
|         if (result.updated || this.dataSent) { | ||||
|             // Check completion status if something was sent.
 | ||||
|             CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata); | ||||
|             CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata); | ||||
|         } | ||||
| 
 | ||||
|         this.dataSent = false; | ||||
| @ -339,7 +339,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo | ||||
|     protected async invalidateContent(): Promise<void> { | ||||
|         const promises: Promise<unknown>[] = []; | ||||
| 
 | ||||
|         promises.push(AddonModLesson.invalidateLessonData(this.courseId!)); | ||||
|         promises.push(AddonModLesson.invalidateLessonData(this.courseId)); | ||||
| 
 | ||||
|         if (this.lesson) { | ||||
|             promises.push(AddonModLesson.invalidateAccessInformation(this.lesson.id)); | ||||
| @ -394,7 +394,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo | ||||
|             AddonModLesson.logViewLesson(this.lesson.id, this.password, this.lesson.name), | ||||
|         ); | ||||
| 
 | ||||
|         CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata); | ||||
|         CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -414,7 +414,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo | ||||
|         if (this.hasOffline) { | ||||
|             if (continueLast) { | ||||
|                 pageId = await AddonModLesson.getLastPageSeen(this.lesson.id, this.accessInfo.attemptscount, { | ||||
|                     cmId: this.module!.id, | ||||
|                     cmId: this.module.id, | ||||
|                 }); | ||||
|             } else { | ||||
|                 pageId = this.accessInfo.firstpageid; | ||||
| @ -589,7 +589,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo | ||||
|         this.showSpinner = true; | ||||
| 
 | ||||
|         try { | ||||
|             await AddonModLessonPrefetchHandler.prefetch(this.module!, this.courseId, true); | ||||
|             await AddonModLessonPrefetchHandler.prefetch(this.module, this.courseId, true); | ||||
| 
 | ||||
|             // Success downloading, open lesson.
 | ||||
|             this.playLesson(continueLast); | ||||
| @ -661,8 +661,8 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo | ||||
|             // The user sent data to server, but not in the sync process. Check if we need to fetch data.
 | ||||
|             await CoreUtils.ignoreErrors(AddonModLessonSync.prefetchAfterUpdate( | ||||
|                 AddonModLessonPrefetchHandler.instance, | ||||
|                 this.module!, | ||||
|                 this.courseId!, | ||||
|                 this.module, | ||||
|                 this.courseId, | ||||
|             )); | ||||
|         } | ||||
| 
 | ||||
| @ -677,7 +677,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo | ||||
|      */ | ||||
|     protected async validatePassword(password: string): Promise<void> { | ||||
|         try { | ||||
|             this.lesson = await AddonModLesson.getLessonWithPassword(this.lesson!.id, { password, cmId: this.module!.id }); | ||||
|             this.lesson = await AddonModLesson.getLessonWithPassword(this.lesson!.id, { password, cmId: this.module.id }); | ||||
| 
 | ||||
|             this.password = password; | ||||
|         } catch (error) { | ||||
|  | ||||
| @ -13,7 +13,7 @@ | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!activityComponent?.loaded" (ionRefresh)="activityComponent?.doRefresh($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!activityComponent?.loaded" (ionRefresh)="activityComponent?.doRefresh($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
| 
 | ||||
|  | ||||
| @ -7,7 +7,7 @@ | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="doRefresh($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="doRefresh($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
| 
 | ||||
|  | ||||
| @ -98,9 +98,9 @@ export class AddonModLessonUserRetakePage implements OnInit { | ||||
|      * | ||||
|      * @param refresher Refresher. | ||||
|      */ | ||||
|     doRefresh(refresher: CustomEvent<IonRefresher>): void { | ||||
|     doRefresh(refresher: IonRefresher): void { | ||||
|         this.refreshData().finally(() => { | ||||
|             refresher?.detail.complete(); | ||||
|             refresher?.complete(); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										33
									
								
								src/addons/mod/lti/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/addons/mod/lti/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { NgModule } from '@angular/core'; | ||||
| 
 | ||||
| import { CoreSharedModule } from '@/core/shared.module'; | ||||
| import { CoreCourseComponentsModule } from '@features/course/components/components.module'; | ||||
| import { AddonModLtiIndexComponent } from './index'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     declarations: [ | ||||
|         AddonModLtiIndexComponent, | ||||
|     ], | ||||
|     imports: [ | ||||
|         CoreSharedModule, | ||||
|         CoreCourseComponentsModule, | ||||
|     ], | ||||
|     exports: [ | ||||
|         AddonModLtiIndexComponent, | ||||
|     ], | ||||
| }) | ||||
| export class AddonModLtiComponentsModule {} | ||||
							
								
								
									
										31
									
								
								src/addons/mod/lti/components/index/addon-mod-lti-index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/addons/mod/lti/components/index/addon-mod-lti-index.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| <!-- Buttons to add to the header. --> | ||||
| <core-navbar-buttons slot="end"> | ||||
|     <core-context-menu> | ||||
|         <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" | ||||
|             [href]="externalUrl" iconAction="fas-external-link-alt"> | ||||
|         </core-context-menu-item> | ||||
|         <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate" | ||||
|             (action)="expandDescription()" iconAction="fas-arrow-right"> | ||||
|         </core-context-menu-item> | ||||
|         <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" | ||||
|             iconAction="far-newspaper" (action)="gotoBlog()"> | ||||
|         </core-context-menu-item> | ||||
|         <core-context-menu-item *ngIf="loaded && isOnline" [priority]="700" [content]="'core.refresh' | translate" | ||||
|             (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false"> | ||||
|         </core-context-menu-item> | ||||
|     </core-context-menu> | ||||
| </core-navbar-buttons> | ||||
| 
 | ||||
| <!-- Content. --> | ||||
| <core-loading [hideUntil]="loaded" class="core-loading-center safe-area-page"> | ||||
|     <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> | ||||
| 
 | ||||
|     <div class="ion-padding"> | ||||
|         <ion-button expand="block" (click)="launch()"> | ||||
|             <ion-icon name="fas-external-link-alt" slot="start"></ion-icon> | ||||
|             {{ 'addon.mod_lti.launchactivity' | translate }} | ||||
|         </ion-button> | ||||
|     </div> | ||||
| </core-loading> | ||||
							
								
								
									
										90
									
								
								src/addons/mod/lti/components/index/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/addons/mod/lti/components/index/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,90 @@ | ||||
| // (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, Optional, OnInit } from '@angular/core'; | ||||
| import { IonContent } from '@ionic/angular'; | ||||
| 
 | ||||
| import { CoreCourseModuleMainActivityComponent } from '@features/course/classes/main-activity-component'; | ||||
| import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; | ||||
| import { AddonModLti, AddonModLtiLti, AddonModLtiProvider } from '../../services/lti'; | ||||
| import { AddonModLtiHelper } from '../../services/lti-helper'; | ||||
| 
 | ||||
| /** | ||||
|  * Component that displays an LTI entry page. | ||||
|  */ | ||||
| @Component({ | ||||
|     selector: 'addon-mod-lti-index', | ||||
|     templateUrl: 'addon-mod-lti-index.html', | ||||
| }) | ||||
| export class AddonModLtiIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit { | ||||
| 
 | ||||
|     component = AddonModLtiProvider.COMPONENT; | ||||
|     moduleName = 'lti'; | ||||
| 
 | ||||
|     lti?: AddonModLtiLti; // The LTI object.
 | ||||
| 
 | ||||
|     protected fetchContentDefaultError = 'addon.mod_lti.errorgetlti'; | ||||
| 
 | ||||
|     constructor( | ||||
|         protected content?: IonContent, | ||||
|         @Optional() courseContentsPage?: CoreCourseContentsPage, | ||||
|     ) { | ||||
|         super('AddonModLtiIndexComponent', content, courseContentsPage); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async ngOnInit(): Promise<void> { | ||||
|         super.ngOnInit(); | ||||
| 
 | ||||
|         this.loadContent(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     protected async fetchContent(refresh: boolean = false): Promise<void> { | ||||
|         try { | ||||
|             this.lti = await AddonModLti.getLti(this.courseId, this.module.id); | ||||
| 
 | ||||
|             this.description = this.lti.intro; | ||||
|             this.dataRetrieved.emit(this.lti); | ||||
|         } finally { | ||||
|             this.fillContextMenu(refresh); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     protected async invalidateContent(): Promise<void> { | ||||
|         const promises: Promise<void>[] = []; | ||||
| 
 | ||||
|         promises.push(AddonModLti.invalidateLti(this.courseId)); | ||||
|         if (this.lti) { | ||||
|             promises.push(AddonModLti.invalidateLtiLaunchData(this.lti.id)); | ||||
|         } | ||||
| 
 | ||||
|         await Promise.all(promises); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Launch the LTI. | ||||
|      */ | ||||
|     launch(): void { | ||||
|         AddonModLtiHelper.getDataAndLaunch(this.courseId, this.module, this.lti); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										6
									
								
								src/addons/mod/lti/lang.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/addons/mod/lti/lang.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| { | ||||
|     "errorgetlti": "Error getting module data.", | ||||
|     "errorinvalidlaunchurl": "The launch URL is not valid.", | ||||
|     "launchactivity": "Launch the activity", | ||||
|     "modulenameplural": "External tools" | ||||
| } | ||||
							
								
								
									
										38
									
								
								src/addons/mod/lti/lti-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/addons/mod/lti/lti-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { CoreSharedModule } from '@/core/shared.module'; | ||||
| import { NgModule } from '@angular/core'; | ||||
| import { RouterModule, Routes } from '@angular/router'; | ||||
| import { AddonModLtiComponentsModule } from './components/components.module'; | ||||
| import { AddonModLtiIndexPage } from './pages/index/index.page'; | ||||
| 
 | ||||
| const routes: Routes = [ | ||||
|     { | ||||
|         path: ':courseId/:cmId', | ||||
|         component: AddonModLtiIndexPage, | ||||
|     }, | ||||
| ]; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     imports: [ | ||||
|         RouterModule.forChild(routes), | ||||
|         CoreSharedModule, | ||||
|         AddonModLtiComponentsModule, | ||||
|     ], | ||||
|     declarations: [ | ||||
|         AddonModLtiIndexPage, | ||||
|     ], | ||||
| }) | ||||
| export class AddonModLtiLazyModule {} | ||||
							
								
								
									
										60
									
								
								src/addons/mod/lti/lti.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/addons/mod/lti/lti.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,60 @@ | ||||
| // (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 { APP_INITIALIZER, NgModule, Type } from '@angular/core'; | ||||
| import { Routes } from '@angular/router'; | ||||
| import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate'; | ||||
| import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; | ||||
| import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate'; | ||||
| import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module'; | ||||
| import { AddonModLtiComponentsModule } from './components/components.module'; | ||||
| import { AddonModLtiIndexLinkHandler } from './services/handlers/index-link'; | ||||
| import { AddonModLtiListLinkHandler } from './services/handlers/list-link'; | ||||
| import { AddonModLtiModuleHandler, AddonModLtiModuleHandlerService } from './services/handlers/module'; | ||||
| import { AddonModLtiPrefetchHandler } from './services/handlers/prefetch'; | ||||
| import { AddonModLtiProvider } from './services/lti'; | ||||
| import { AddonModLtiHelperProvider } from './services/lti-helper'; | ||||
| 
 | ||||
| export const ADDON_MOD_LTI_SERVICES: Type<unknown>[] = [ | ||||
|     AddonModLtiProvider, | ||||
|     AddonModLtiHelperProvider, | ||||
| ]; | ||||
| 
 | ||||
| const routes: Routes = [ | ||||
|     { | ||||
|         path: AddonModLtiModuleHandlerService.PAGE_NAME, | ||||
|         loadChildren: () => import('./lti-lazy.module').then(m => m.AddonModLtiLazyModule), | ||||
|     }, | ||||
| ]; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     imports: [ | ||||
|         CoreMainMenuTabRoutingModule.forChild(routes), | ||||
|         AddonModLtiComponentsModule, | ||||
|     ], | ||||
|     providers: [ | ||||
|         { | ||||
|             provide: APP_INITIALIZER, | ||||
|             multi: true, | ||||
|             deps: [], | ||||
|             useFactory: () => () => { | ||||
|                 CoreCourseModuleDelegate.registerHandler(AddonModLtiModuleHandler.instance); | ||||
|                 CoreContentLinksDelegate.registerHandler(AddonModLtiIndexLinkHandler.instance); | ||||
|                 CoreContentLinksDelegate.registerHandler(AddonModLtiListLinkHandler.instance); | ||||
|                 CoreCourseModulePrefetchDelegate.registerHandler(AddonModLtiPrefetchHandler.instance); | ||||
|             }, | ||||
|         }, | ||||
|     ], | ||||
| }) | ||||
| export class AddonModLtiModule {} | ||||
							
								
								
									
										22
									
								
								src/addons/mod/lti/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/addons/mod/lti/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| <ion-header> | ||||
|     <ion-toolbar> | ||||
|         <ion-buttons slot="start"> | ||||
|             <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button> | ||||
|         </ion-buttons> | ||||
|         <ion-title> | ||||
|             <core-format-text [text]="title" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"> | ||||
|             </core-format-text> | ||||
|         </ion-title> | ||||
| 
 | ||||
|         <ion-buttons slot="end"> | ||||
|             <!-- The buttons defined by the component will be added in here. --> | ||||
|         </ion-buttons> | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!activityComponent?.loaded" (ionRefresh)="activityComponent?.doRefresh($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
| 
 | ||||
|     <addon-mod-lti-index [module]="module" [courseId]="courseId" (dataRetrieved)="updateData($event)"></addon-mod-lti-index> | ||||
| </ion-content> | ||||
							
								
								
									
										31
									
								
								src/addons/mod/lti/pages/index/index.page.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/addons/mod/lti/pages/index/index.page.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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, ViewChild } from '@angular/core'; | ||||
| 
 | ||||
| import { CoreCourseModuleMainActivityPage } from '@features/course/classes/main-activity-page'; | ||||
| import { AddonModLtiIndexComponent } from '../../components/index/index'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays an LTI. | ||||
|  */ | ||||
| @Component({ | ||||
|     selector: 'page-addon-mod-lti-index', | ||||
|     templateUrl: 'index.html', | ||||
| }) | ||||
| export class AddonModLtiIndexPage extends CoreCourseModuleMainActivityPage<AddonModLtiIndexComponent> { | ||||
| 
 | ||||
|     @ViewChild(AddonModLtiIndexComponent) activityComponent?: AddonModLtiIndexComponent; | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										33
									
								
								src/addons/mod/lti/services/handlers/index-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/addons/mod/lti/services/handlers/index-link.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| 
 | ||||
| /** | ||||
|  * Handler to treat links to LTI. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class AddonModLtiIndexLinkHandlerService extends CoreContentLinksModuleIndexHandler { | ||||
| 
 | ||||
|     name = 'AddonModLtiIndexLinkHandlerService'; | ||||
| 
 | ||||
|     constructor() { | ||||
|         super('AddonModLti', 'lti', 'l'); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export const AddonModLtiIndexLinkHandler = makeSingleton(AddonModLtiIndexLinkHandlerService); | ||||
							
								
								
									
										33
									
								
								src/addons/mod/lti/services/handlers/list-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/addons/mod/lti/services/handlers/list-link.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { CoreContentLinksModuleListHandler } from '@features/contentlinks/classes/module-list-handler'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| 
 | ||||
| /** | ||||
|  * Handler to treat links to LTI list page. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class AddonModLtiListLinkHandlerService extends CoreContentLinksModuleListHandler { | ||||
| 
 | ||||
|     name = 'AddonModLtiListLinkHandler'; | ||||
| 
 | ||||
|     constructor() { | ||||
|         super('AddonModLti', 'lti'); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export const AddonModLtiListLinkHandler = makeSingleton(AddonModLtiListLinkHandlerService); | ||||
							
								
								
									
										145
									
								
								src/addons/mod/lti/services/handlers/module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								src/addons/mod/lti/services/handlers/module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,145 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable, Type } from '@angular/core'; | ||||
| import { DomSanitizer } from '@angular/platform-browser'; | ||||
| 
 | ||||
| import { CoreConstants } from '@/core/constants'; | ||||
| import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate'; | ||||
| import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course'; | ||||
| import { CoreCourseModule } from '@features/course/services/course-helper'; | ||||
| import { CoreApp } from '@services/app'; | ||||
| import { CoreFilepool } from '@services/filepool'; | ||||
| import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| import { AddonModLtiHelper } from '../lti-helper'; | ||||
| import { AddonModLti, AddonModLtiProvider } from '../lti'; | ||||
| import { AddonModLtiIndexComponent } from '../../components/index'; | ||||
| 
 | ||||
| /** | ||||
|  * Handler to support LTI modules. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class AddonModLtiModuleHandlerService implements CoreCourseModuleHandler { | ||||
| 
 | ||||
|     static readonly PAGE_NAME = 'mod_lti'; | ||||
| 
 | ||||
|     name = 'AddonModLti'; | ||||
|     modName = 'lti'; | ||||
| 
 | ||||
|     supportedFeatures = { | ||||
|         [CoreConstants.FEATURE_GROUPS]: false, | ||||
|         [CoreConstants.FEATURE_GROUPINGS]: false, | ||||
|         [CoreConstants.FEATURE_MOD_INTRO]: true, | ||||
|         [CoreConstants.FEATURE_COMPLETION_TRACKS_VIEWS]: true, | ||||
|         [CoreConstants.FEATURE_GRADE_HAS_GRADE]: true, | ||||
|         [CoreConstants.FEATURE_GRADE_OUTCOMES]: true, | ||||
|         [CoreConstants.FEATURE_BACKUP_MOODLE2]: true, | ||||
|         [CoreConstants.FEATURE_SHOW_DESCRIPTION]: true, | ||||
|     }; | ||||
| 
 | ||||
|     constructor(protected sanitizer: DomSanitizer) {} | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async isEnabled(): Promise<boolean> { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     getData( | ||||
|         module: CoreCourseAnyModuleData, | ||||
|         courseId: number, | ||||
|     ): CoreCourseModuleHandlerData { | ||||
| 
 | ||||
|         const data: CoreCourseModuleHandlerData = { | ||||
|             icon: CoreCourse.getModuleIconSrc(this.modName, 'modicon' in module ? module.modicon : undefined), | ||||
|             title: module.name, | ||||
|             class: 'addon-mod_lti-handler', | ||||
|             action(event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions): void { | ||||
|                 options = options || {}; | ||||
|                 options.params = options.params || {}; | ||||
|                 Object.assign(options.params, { module }); | ||||
|                 const routeParams = '/' + courseId + '/' + module.id; | ||||
| 
 | ||||
|                 CoreNavigator.navigateToSitePath(AddonModLtiModuleHandlerService.PAGE_NAME + routeParams, options); | ||||
|             }, | ||||
|             buttons: [{ | ||||
|                 icon: 'link', | ||||
|                 label: 'addon.mod_lti.launchactivity', | ||||
|                 action: (event: Event, module: CoreCourseModule, courseId: number): void => { | ||||
|                     // Launch the LTI.
 | ||||
|                     AddonModLtiHelper.getDataAndLaunch(courseId, module); | ||||
|                 }, | ||||
|             }], | ||||
|         }; | ||||
| 
 | ||||
|         // Handle custom icons.
 | ||||
|         CoreUtils.ignoreErrors(this.loadCustomIcon(module, courseId, data)); | ||||
| 
 | ||||
|         return data; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Load the custom icon. | ||||
|      * | ||||
|      * @param module Module. | ||||
|      * @param courseId Course ID. | ||||
|      * @param data Handler data. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected async loadCustomIcon( | ||||
|         module: CoreCourseAnyModuleData, | ||||
|         courseId: number, | ||||
|         handlerData: CoreCourseModuleHandlerData, | ||||
|     ): Promise<void> { | ||||
|         const lti = await AddonModLti.getLti(courseId, module.id); | ||||
| 
 | ||||
|         const icon = lti.secureicon || lti.icon; | ||||
|         if (!icon) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const siteId = CoreSites.getCurrentSiteId(); | ||||
| 
 | ||||
|         try { | ||||
|             await CoreFilepool.downloadUrl(siteId,  icon, false, AddonModLtiProvider.COMPONENT, module.id); | ||||
| 
 | ||||
|             // Get the internal URL.
 | ||||
|             const url = await CoreFilepool.getSrcByUrl(siteId, icon, AddonModLtiProvider.COMPONENT, module.id); | ||||
| 
 | ||||
|             handlerData.icon = this.sanitizer.bypassSecurityTrustUrl(url); | ||||
|         } catch { | ||||
|             // Error downloading. If we're online we'll set the online url.
 | ||||
|             if (CoreApp.isOnline()) { | ||||
|                 handlerData.icon = this.sanitizer.bypassSecurityTrustUrl(icon); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async getMainComponent(): Promise<Type<unknown> | undefined> { | ||||
|         return AddonModLtiIndexComponent; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export const AddonModLtiModuleHandler = makeSingleton(AddonModLtiModuleHandlerService); | ||||
							
								
								
									
										62
									
								
								src/addons/mod/lti/services/handlers/prefetch.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/addons/mod/lti/services/handlers/prefetch.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,62 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| 
 | ||||
| import { CoreCourseActivityPrefetchHandlerBase } from '@features/course/classes/activity-prefetch-handler'; | ||||
| import { CoreCourseAnyModuleData } from '@features/course/services/course'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| import { AddonModLti, AddonModLtiProvider } from '../lti'; | ||||
| 
 | ||||
| /** | ||||
|  * Handler to prefetch LTIs. LTIs cannot be prefetched, but the handler will be used to invalidate some data on course PTR. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class AddonModLtiPrefetchHandlerService extends CoreCourseActivityPrefetchHandlerBase { | ||||
| 
 | ||||
|     name = 'AddonModLti'; | ||||
|     modName = 'lti'; | ||||
|     component = AddonModLtiProvider.COMPONENT; | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async download(): Promise<void> { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     invalidateModule(module: CoreCourseAnyModuleData, courseId: number): Promise<void> { | ||||
|         return AddonModLti.invalidateLti(courseId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async isDownloadable(): Promise<boolean> { | ||||
|         return false; // LTIs aren't downloadable.
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async prefetch(): Promise<void> { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export const AddonModLtiPrefetchHandler = makeSingleton(AddonModLtiPrefetchHandlerService); | ||||
							
								
								
									
										125
									
								
								src/addons/mod/lti/services/lti-helper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								src/addons/mod/lti/services/lti-helper.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,125 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| 
 | ||||
| import { CoreCourse } from '@features/course/services/course'; | ||||
| import { CoreCourseModule } from '@features/course/services/course-helper'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { makeSingleton, Platform } from '@singletons'; | ||||
| import { CoreEvents } from '@singletons/events'; | ||||
| import { AddonModLti, AddonModLtiLti } from './lti'; | ||||
| 
 | ||||
| /** | ||||
|  * Service that provides some helper functions for LTI. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class AddonModLtiHelperProvider { | ||||
| 
 | ||||
|     protected pendingCheckCompletion: {[moduleId: string]: {courseId: number; module: CoreCourseModule}} = {}; | ||||
| 
 | ||||
|     constructor() { | ||||
|         Platform.resume.subscribe(() => { | ||||
|             // User went back to the app, check pending completions.
 | ||||
|             for (const moduleId in this.pendingCheckCompletion) { | ||||
|                 const data = this.pendingCheckCompletion[moduleId]; | ||||
| 
 | ||||
|                 CoreCourse.checkModuleCompletion(data.courseId, data.module.completiondata); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         // Clear pending completion on logout.
 | ||||
|         CoreEvents.on(CoreEvents.LOGOUT, () => { | ||||
|             this.pendingCheckCompletion = {}; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get needed data and launch the LTI. | ||||
|      * | ||||
|      * @param courseId Course ID. | ||||
|      * @param module Module. | ||||
|      * @param lti LTI instance. If not provided it will be obtained. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async getDataAndLaunch(courseId: number, module: CoreCourseModule, lti?: AddonModLtiLti, siteId?: string): Promise<void> { | ||||
|         siteId = siteId || CoreSites.getCurrentSiteId(); | ||||
| 
 | ||||
|         const modal = await CoreDomUtils.showModalLoading(); | ||||
| 
 | ||||
|         try { | ||||
|             const openInBrowser = await AddonModLti.isOpenInAppBrowserDisabled(siteId); | ||||
| 
 | ||||
|             if (openInBrowser) { | ||||
|                 const site = await CoreSites.getSite(siteId); | ||||
| 
 | ||||
|                 // The view event is triggered by the browser, mark the module as pending to check completion.
 | ||||
|                 this.pendingCheckCompletion[module.id] = { | ||||
|                     courseId, | ||||
|                     module, | ||||
|                 }; | ||||
| 
 | ||||
|                 return site.openInBrowserWithAutoLogin(module.url!); | ||||
|             } | ||||
| 
 | ||||
|             // Open in app.
 | ||||
|             if (!lti) { | ||||
|                 lti = await AddonModLti.getLti(courseId, module.id); | ||||
|             } | ||||
| 
 | ||||
|             const launchData = await AddonModLti.getLtiLaunchData(lti.id); | ||||
| 
 | ||||
|             // "View" LTI without blocking the UI.
 | ||||
|             this.logViewAndCheckCompletion(courseId, module, lti.id, lti.name, siteId); | ||||
| 
 | ||||
|             // Launch LTI.
 | ||||
|             return AddonModLti.launch(launchData.endpoint, launchData.parameters); | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'addon.mod_lti.errorgetlti', true); | ||||
|         } finally { | ||||
|             modal.dismiss(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Report the LTI as being viewed and check completion. | ||||
|      * | ||||
|      * @param courseId Course ID. | ||||
|      * @param module Module. | ||||
|      * @param ltiId LTI id. | ||||
|      * @param name Name of the lti. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async logViewAndCheckCompletion( | ||||
|         courseId: number, | ||||
|         module: CoreCourseModule, | ||||
|         ltiId: number, | ||||
|         name?: string, | ||||
|         siteId?: string, | ||||
|     ): Promise<void> { | ||||
|         try { | ||||
|             await AddonModLti.logView(ltiId, name, siteId); | ||||
| 
 | ||||
|             CoreCourse.checkModuleCompletion(courseId, module.completiondata); | ||||
|         } catch (error) { | ||||
|             // Ignore errors.
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export const AddonModLtiHelper = makeSingleton(AddonModLtiHelperProvider); | ||||
							
								
								
									
										344
									
								
								src/addons/mod/lti/services/lti.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										344
									
								
								src/addons/mod/lti/services/lti.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,344 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| 
 | ||||
| import { CoreError } from '@classes/errors/error'; | ||||
| import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; | ||||
| import { CoreCourseLogHelper } from '@features/course/services/log-helper'; | ||||
| import { CoreApp } from '@services/app'; | ||||
| import { CoreFile } from '@services/file'; | ||||
| import { CoreSites, CoreSitesCommonWSOptions } from '@services/sites'; | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| import { CoreUrlUtils } from '@services/utils/url'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws'; | ||||
| import { makeSingleton, Translate } from '@singletons'; | ||||
| 
 | ||||
| const ROOT_CACHE_KEY = 'mmaModLti:'; | ||||
| const LAUNCHER_FILE_NAME = 'lti_launcher.html'; | ||||
| 
 | ||||
| /** | ||||
|  * Service that provides some features for LTI. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class AddonModLtiProvider { | ||||
| 
 | ||||
|     static readonly COMPONENT = 'mmaModLti'; | ||||
| 
 | ||||
|     /** | ||||
|      * Delete launcher. | ||||
|      * | ||||
|      * @return Promise resolved when the launcher file is deleted. | ||||
|      */ | ||||
|     deleteLauncher(): Promise<void> { | ||||
|         return CoreFile.removeFile(LAUNCHER_FILE_NAME); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Generates a launcher file. | ||||
|      * | ||||
|      * @param url Launch URL. | ||||
|      * @param params Launch params. | ||||
|      * @return Promise resolved with the file URL. | ||||
|      */ | ||||
|     async generateLauncher(url: string, params: AddonModLtiParam[]): Promise<string> { | ||||
|         if (!CoreFile.isAvailable()) { | ||||
|             return url; | ||||
|         } | ||||
| 
 | ||||
|         // Generate a form with the params.
 | ||||
|         let text = `<form action="${url}" name="ltiLaunchForm" method="post" encType="application/x-www-form-urlencoded">\n`; | ||||
|         params.forEach((p) => { | ||||
|             if (p.name == 'ext_submit') { | ||||
|                 text += '    <input type="submit"'; | ||||
|             } else { | ||||
|                 text += '    <input type="hidden" name="' + CoreTextUtils.escapeHTML(p.name) + '"'; | ||||
|             } | ||||
|             text += ' value="' + CoreTextUtils.escapeHTML(p.value) + '"/>\n'; | ||||
|         }); | ||||
|         text += '</form>\n'; | ||||
| 
 | ||||
|         // Add an in-line script to automatically submit the form.
 | ||||
|         text += '<script type="text/javascript"> \n' + | ||||
|             '    window.onload = function() { \n' + | ||||
|             '        document.ltiLaunchForm.submit(); \n' + | ||||
|             '    }; \n' + | ||||
|             '</script> \n'; | ||||
| 
 | ||||
|         const entry = await CoreFile.writeFile(LAUNCHER_FILE_NAME, text); | ||||
| 
 | ||||
|         return entry.toURL(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get a LTI. | ||||
|      * | ||||
|      * @param courseId Course ID. | ||||
|      * @param cmId Course module ID. | ||||
|      * @param options Other options. | ||||
|      * @return Promise resolved when the LTI is retrieved. | ||||
|      */ | ||||
|     async getLti(courseId: number, cmId: number, options: CoreSitesCommonWSOptions = {}): Promise<AddonModLtiLti> { | ||||
|         const params: AddonModLtiGetLtisByCoursesWSParams = { | ||||
|             courseids: [courseId], | ||||
|         }; | ||||
|         const preSets: CoreSiteWSPreSets = { | ||||
|             cacheKey: this.getLtiCacheKey(courseId), | ||||
|             updateFrequency: CoreSite.FREQUENCY_RARELY, | ||||
|             component: AddonModLtiProvider.COMPONENT, | ||||
|             ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets.
 | ||||
|         }; | ||||
| 
 | ||||
|         const site = await CoreSites.getSite(options.siteId); | ||||
| 
 | ||||
|         const response = await site.read<AddonModLtiGetLtisByCoursesWSResponse>('mod_lti_get_ltis_by_courses', params, preSets); | ||||
| 
 | ||||
|         const currentLti = response.ltis.find((lti) => lti.coursemodule == cmId); | ||||
|         if (currentLti) { | ||||
|             return currentLti; | ||||
|         } | ||||
| 
 | ||||
|         throw new CoreError('Activity not found.'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get cache key for LTI data WS calls. | ||||
|      * | ||||
|      * @param courseId Course ID. | ||||
|      * @return Cache key. | ||||
|      */ | ||||
|     protected getLtiCacheKey(courseId: number): string { | ||||
|         return ROOT_CACHE_KEY + 'lti:' + courseId; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get a LTI launch data. | ||||
|      * | ||||
|      * @param id LTI id. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved when the launch data is retrieved. | ||||
|      */ | ||||
|     async getLtiLaunchData(id: number, siteId?: string): Promise<AddonModLtiGetToolLaunchDataWSResponse> { | ||||
|         const params: AddonModLtiGetToolLaunchDataWSParams = { | ||||
|             toolid: id, | ||||
|         }; | ||||
| 
 | ||||
|         // Try to avoid using cache since the "nonce" parameter is set to a timestamp.
 | ||||
|         const preSets: CoreSiteWSPreSets = { | ||||
|             getFromCache: false, | ||||
|             saveToCache: true, | ||||
|             emergencyCache: true, | ||||
|             cacheKey: this.getLtiLaunchDataCacheKey(id), | ||||
|         }; | ||||
| 
 | ||||
|         const site = await CoreSites.getSite(siteId); | ||||
| 
 | ||||
|         return site.read<AddonModLtiGetToolLaunchDataWSResponse>('mod_lti_get_tool_launch_data', params, preSets); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get cache key for LTI launch data WS calls. | ||||
|      * | ||||
|      * @param id LTI id. | ||||
|      * @return Cache key. | ||||
|      */ | ||||
|     protected getLtiLaunchDataCacheKey(id: number): string { | ||||
|         return `${ROOT_CACHE_KEY}launch:${id}`; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Invalidates LTI data. | ||||
|      * | ||||
|      * @param courseId Course ID. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved when the data is invalidated. | ||||
|      */ | ||||
|     async invalidateLti(courseId: number, siteId?: string): Promise<void> { | ||||
|         const site = await CoreSites.getSite(siteId); | ||||
| 
 | ||||
|         await site.invalidateWsCacheForKey(this.getLtiCacheKey(courseId)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Invalidates options. | ||||
|      * | ||||
|      * @param id LTI id. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved when the data is invalidated. | ||||
|      */ | ||||
|     async invalidateLtiLaunchData(id: number, siteId?: string): Promise<void> { | ||||
|         const site = await CoreSites.getSite(siteId); | ||||
| 
 | ||||
|         await site.invalidateWsCacheForKey(this.getLtiLaunchDataCacheKey(id)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if open in InAppBrowser is disabled. | ||||
|      * | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with boolean: whether it's disabled. | ||||
|      */ | ||||
|     async isOpenInAppBrowserDisabled(siteId?: string): Promise<boolean> { | ||||
|         const site = await CoreSites.getSite(siteId); | ||||
| 
 | ||||
|         return this.isOpenInAppBrowserDisabledInSite(site); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if open in InAppBrowser is disabled. | ||||
|      * | ||||
|      * @param site Site. If not defined, current site. | ||||
|      * @return Whether it's disabled. | ||||
|      */ | ||||
|     isOpenInAppBrowserDisabledInSite(site?: CoreSite): boolean { | ||||
|         site = site || CoreSites.getCurrentSite(); | ||||
| 
 | ||||
|         return !!site?.isFeatureDisabled('CoreCourseModuleDelegate_AddonModLti:openInAppBrowser'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Launch LTI. | ||||
|      * | ||||
|      * @param url Launch URL. | ||||
|      * @param params Launch params. | ||||
|      * @return Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     async launch(url: string, params: AddonModLtiParam[]): Promise<void> { | ||||
|         if (!CoreUrlUtils.isHttpURL(url)) { | ||||
|             throw Translate.instant('addon.mod_lti.errorinvalidlaunchurl'); | ||||
|         } | ||||
| 
 | ||||
|         // Generate launcher and open it.
 | ||||
|         const launcherUrl = await this.generateLauncher(url, params); | ||||
| 
 | ||||
|         if (CoreApp.isMobile()) { | ||||
|             CoreUtils.openInApp(launcherUrl); | ||||
|         } else { | ||||
|             // In desktop open in browser, we found some cases where inapp caused JS issues.
 | ||||
|             CoreUtils.openInBrowser(launcherUrl); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Report the LTI as being viewed. | ||||
|      * | ||||
|      * @param id LTI id. | ||||
|      * @param name Name of the lti. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved when the WS call is successful. | ||||
|      */ | ||||
|     logView(id: number, name?: string, siteId?: string): Promise<any> { | ||||
|         const params: AddonModLtiViewLtiWSParams = { | ||||
|             ltiid: id, | ||||
|         }; | ||||
| 
 | ||||
|         return CoreCourseLogHelper.logSingle( | ||||
|             'mod_lti_view_lti', | ||||
|             params, | ||||
|             AddonModLtiProvider.COMPONENT, | ||||
|             id, | ||||
|             name, | ||||
|             'lti', | ||||
|             {}, | ||||
|             siteId, | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export const AddonModLti = makeSingleton(AddonModLtiProvider); | ||||
| 
 | ||||
| /** | ||||
|  * Params of mod_lti_get_ltis_by_courses WS. | ||||
|  */ | ||||
| export type AddonModLtiGetLtisByCoursesWSParams = { | ||||
|     courseids?: number[]; // Array of course ids.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by mod_lti_get_ltis_by_courses WS. | ||||
|  */ | ||||
| export type AddonModLtiGetLtisByCoursesWSResponse = { | ||||
|     ltis: AddonModLtiLti[]; | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * LTI returned by mod_lti_get_ltis_by_courses. | ||||
|  */ | ||||
| export type AddonModLtiLti = { | ||||
|     id: number; // External tool id.
 | ||||
|     coursemodule: number; // Course module id.
 | ||||
|     course: number; // Course id.
 | ||||
|     name: string; // LTI name.
 | ||||
|     intro?: string; // The LTI intro.
 | ||||
|     introformat?: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | ||||
|     introfiles?: CoreWSExternalFile[]; // @since 3.2.
 | ||||
|     timecreated?: number; // Time of creation.
 | ||||
|     timemodified?: number; // Time of last modification.
 | ||||
|     typeid?: number; // Type id.
 | ||||
|     toolurl?: string; // Tool url.
 | ||||
|     securetoolurl?: string; // Secure tool url.
 | ||||
|     instructorchoicesendname?: string; // Instructor choice send name.
 | ||||
|     instructorchoicesendemailaddr?: number; // Instructor choice send mail address.
 | ||||
|     instructorchoiceallowroster?: number; // Instructor choice allow roster.
 | ||||
|     instructorchoiceallowsetting?: number; // Instructor choice allow setting.
 | ||||
|     instructorcustomparameters?: string; // Instructor custom parameters.
 | ||||
|     instructorchoiceacceptgrades?: number; // Instructor choice accept grades.
 | ||||
|     grade?: number; // Enable grades.
 | ||||
|     launchcontainer?: number; // Launch container mode.
 | ||||
|     resourcekey?: string; // Resource key.
 | ||||
|     password?: string; // Shared secret.
 | ||||
|     debuglaunch?: number; // Debug launch.
 | ||||
|     showtitlelaunch?: number; // Show title launch.
 | ||||
|     showdescriptionlaunch?: number; // Show description launch.
 | ||||
|     servicesalt?: string; // Service salt.
 | ||||
|     icon?: string; // Alternative icon URL.
 | ||||
|     secureicon?: string; // Secure icon URL.
 | ||||
|     section?: number; // Course section id.
 | ||||
|     visible?: number; // Visible.
 | ||||
|     groupmode?: number; // Group mode.
 | ||||
|     groupingid?: number; // Group id.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Params of mod_lti_get_tool_launch_data WS. | ||||
|  */ | ||||
| export type AddonModLtiGetToolLaunchDataWSParams = { | ||||
|     toolid: number; // External tool instance id.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data returned by mod_lti_get_tool_launch_data WS. | ||||
|  */ | ||||
| export type AddonModLtiGetToolLaunchDataWSResponse = { | ||||
|     endpoint: string; // Endpoint URL.
 | ||||
|     parameters: AddonModLtiParam[]; | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Param to send to the LTI. | ||||
|  */ | ||||
| export type AddonModLtiParam = { | ||||
|     name: string; // Parameter name.
 | ||||
|     value: string; // Parameter value.
 | ||||
| }; | ||||
| /** | ||||
|  * Params of mod_lti_view_lti WS. | ||||
|  */ | ||||
| export type AddonModLtiViewLtiWSParams = { | ||||
|     ltiid: number; // Lti instance id.
 | ||||
| }; | ||||
| @ -25,6 +25,7 @@ import { AddonModPageModule } from './page/page.module'; | ||||
| import { AddonModQuizModule } from './quiz/quiz.module'; | ||||
| import { AddonModResourceModule } from './resource/resource.module'; | ||||
| import { AddonModUrlModule } from './url/url.module'; | ||||
| import { AddonModLtiModule } from './lti/lti.module'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     declarations: [], | ||||
| @ -40,6 +41,7 @@ import { AddonModUrlModule } from './url/url.module'; | ||||
|         AddonModResourceModule, | ||||
|         AddonModFolderModule, | ||||
|         AddonModImscpModule, | ||||
|         AddonModLtiModule, | ||||
|     ], | ||||
|     providers: [], | ||||
|     exports: [], | ||||
|  | ||||
| @ -26,7 +26,7 @@ | ||||
| <core-loading [hideUntil]="loaded" class="core-loading-center safe-area-page"> | ||||
| 
 | ||||
|     <core-course-module-description *ngIf="displayDescription" [description]="description" [component]="component" | ||||
|         [componentId]="componentId" contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId"> | ||||
|         [componentId]="componentId" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"> | ||||
|     </core-course-module-description> | ||||
| 
 | ||||
|     <ion-card class="core-warning-card" *ngIf="warning"> | ||||
| @ -36,7 +36,7 @@ | ||||
| 
 | ||||
|     <div class="ion-padding"> | ||||
|         <core-format-text [component]="component" [componentId]="componentId" [text]="contents" contextLevel="module" | ||||
|             [contextInstanceId]="module?.id" [courseId]="courseId"> | ||||
|             [contextInstanceId]="module.id" [courseId]="courseId"> | ||||
|         </core-format-text> | ||||
| 
 | ||||
|         <p class="ion-padding-bottom addon-mod_page-timemodified" *ngIf="displayTimemodified && timemodified"> | ||||
|  | ||||
| @ -59,8 +59,8 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp | ||||
|         await this.loadContent(); | ||||
| 
 | ||||
|         try { | ||||
|             await AddonModPage.logView(this.module!.instance!, this.module!.name); | ||||
|             CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata); | ||||
|             await AddonModPage.logView(this.module.instance!, this.module.name); | ||||
|             CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata); | ||||
|         } catch { | ||||
|             // Ignore errors.
 | ||||
|         } | ||||
| @ -72,7 +72,7 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp | ||||
|      * @return Resolved when done. | ||||
|      */ | ||||
|     protected async invalidateContent(): Promise<void> { | ||||
|         await AddonModPage.invalidateContent(this.module!.id, this.courseId!); | ||||
|         await AddonModPage.invalidateContent(this.module.id, this.courseId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -92,9 +92,9 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp | ||||
| 
 | ||||
|             // Get the module to get the latest title and description. Data should've been updated in download.
 | ||||
|             if (this.canGetPage) { | ||||
|                 getPagePromise = AddonModPage.getPageData(this.courseId!, this.module!.id); | ||||
|                 getPagePromise = AddonModPage.getPageData(this.courseId, this.module.id); | ||||
|             } else { | ||||
|                 getPagePromise = CoreCourse.getModule(this.module!.id, this.courseId!); | ||||
|                 getPagePromise = CoreCourse.getModule(this.module.id, this.courseId); | ||||
|             } | ||||
| 
 | ||||
|             promises.push(getPagePromise.then((page) => { | ||||
| @ -133,7 +133,7 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp | ||||
|             })); | ||||
| 
 | ||||
|             // Get the page HTML.
 | ||||
|             promises.push(AddonModPageHelper.getPageHtml(this.module!.contents, this.module!.id).then((content) => { | ||||
|             promises.push(AddonModPageHelper.getPageHtml(this.module.contents, this.module.id).then((content) => { | ||||
| 
 | ||||
|                 this.contents = content; | ||||
|                 this.warning = downloadResult?.failed ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error!) : ''; | ||||
|  | ||||
| @ -13,7 +13,7 @@ | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!activityComponent?.loaded" (ionRefresh)="activityComponent?.doRefresh($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!activityComponent?.loaded" (ionRefresh)="activityComponent?.doRefresh($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
| 
 | ||||
|  | ||||
| @ -28,7 +28,7 @@ | ||||
| <!-- Content. --> | ||||
| <core-loading [hideUntil]="loaded" class="core-loading-center"> | ||||
|     <core-course-module-description [description]="description" [component]="component" [componentId]="componentId" | ||||
|         contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId"> | ||||
|         contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"> | ||||
|     </core-course-module-description> | ||||
| 
 | ||||
|     <!-- Access rules description messages. --> | ||||
| @ -117,7 +117,7 @@ | ||||
|                 <ion-label> | ||||
|                     <h3 class="item-heading">{{ 'addon.mod_quiz.comment' | translate }}</h3> | ||||
|                     <p><core-format-text [component]="component" [componentId]="componentId" [text]="gradebookFeedback" | ||||
|                         contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId"> | ||||
|                         contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"> | ||||
|                     </core-format-text></p> | ||||
|                 </ion-label> | ||||
|             </ion-item> | ||||
| @ -125,7 +125,7 @@ | ||||
|                 <ion-label> | ||||
|                     <h3 class="item-heading">{{ 'addon.mod_quiz.overallfeedback' | translate }}</h3> | ||||
|                     <p><core-format-text [component]="component" [componentId]="componentId" [text]="overallFeedback" | ||||
|                         contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId"> | ||||
|                         contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"> | ||||
|                     </core-format-text></p> | ||||
|                 </ion-label> | ||||
|             </ion-item> | ||||
|  | ||||
| @ -129,7 +129,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp | ||||
|         try { | ||||
|             await AddonModQuiz.logViewQuiz(this.quiz.id, this.quiz.name); | ||||
| 
 | ||||
|             CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata); | ||||
|             CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata); | ||||
|         } catch { | ||||
|             // Ignore errors.
 | ||||
|         } | ||||
| @ -162,7 +162,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp | ||||
|         this.showStatusSpinner = true; | ||||
| 
 | ||||
|         try { | ||||
|             await AddonModQuizPrefetchHandler.prefetch(this.module!, this.courseId, true); | ||||
|             await AddonModQuizPrefetchHandler.prefetch(this.module, this.courseId, true); | ||||
| 
 | ||||
|             // Success downloading, open quiz.
 | ||||
|             this.openQuiz(); | ||||
| @ -190,7 +190,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp | ||||
|     protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> { | ||||
|         try { | ||||
|             // First get the quiz instance.
 | ||||
|             const quiz = await AddonModQuiz.getQuiz(this.courseId!, this.module!.id); | ||||
|             const quiz = await AddonModQuiz.getQuiz(this.courseId, this.module.id); | ||||
| 
 | ||||
|             this.gradeMethodReadable = AddonModQuiz.getQuizGradeMethod(quiz.grademethod); | ||||
|             this.now = Date.now(); | ||||
| @ -231,7 +231,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp | ||||
|             } | ||||
| 
 | ||||
|             // Get quiz access info.
 | ||||
|             this.quizAccessInfo = await AddonModQuiz.getQuizAccessInformation(quiz.id, { cmId: this.module!.id }); | ||||
|             this.quizAccessInfo = await AddonModQuiz.getQuizAccessInformation(quiz.id, { cmId: this.module.id }); | ||||
| 
 | ||||
|             this.showReviewColumn = this.quizAccessInfo.canreviewmyattempts; | ||||
|             this.accessRules = this.quizAccessInfo.accessrules; | ||||
| @ -242,7 +242,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp | ||||
|             } | ||||
| 
 | ||||
|             // Get question types in the quiz.
 | ||||
|             const types = await AddonModQuiz.getQuizRequiredQtypes(quiz.id, { cmId: this.module!.id }); | ||||
|             const types = await AddonModQuiz.getQuizRequiredQtypes(quiz.id, { cmId: this.module.id }); | ||||
| 
 | ||||
|             this.unsupportedQuestions = AddonModQuiz.getUnsupportedQuestions(types); | ||||
|             this.hasSupportedQuestions = !!types.find((type) => type != 'random' && this.unsupportedQuestions.indexOf(type) == -1); | ||||
| @ -265,10 +265,10 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp | ||||
|     protected async getAttempts(quiz: AddonModQuizQuizData): Promise<void> { | ||||
| 
 | ||||
|         // Get access information of last attempt (it also works if no attempts made).
 | ||||
|         this.attemptAccessInfo = await AddonModQuiz.getAttemptAccessInformation(quiz.id, 0, { cmId: this.module!.id }); | ||||
|         this.attemptAccessInfo = await AddonModQuiz.getAttemptAccessInformation(quiz.id, 0, { cmId: this.module.id }); | ||||
| 
 | ||||
|         // Get attempts.
 | ||||
|         const attempts = await AddonModQuiz.getUserAttempts(quiz.id, { cmId: this.module!.id }); | ||||
|         const attempts = await AddonModQuiz.getUserAttempts(quiz.id, { cmId: this.module.id }); | ||||
| 
 | ||||
|         this.attempts = await this.treatAttempts(quiz, attempts); | ||||
| 
 | ||||
| @ -386,7 +386,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp | ||||
|         if (quiz.showFeedbackColumn) { | ||||
|             // Get the quiz overall feedback.
 | ||||
|             const response = await AddonModQuiz.getFeedbackForGrade(quiz.id, this.gradebookData.grade, { | ||||
|                 cmId: this.module!.id, | ||||
|                 cmId: this.module.id, | ||||
|             }); | ||||
| 
 | ||||
|             this.overallFeedback = response.feedbacktext; | ||||
| @ -404,14 +404,14 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp | ||||
|         } | ||||
| 
 | ||||
|         // If we go to auto review it means an attempt was finished. Check completion status.
 | ||||
|         CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata); | ||||
|         CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata); | ||||
| 
 | ||||
|         // Verify that user can see the review.
 | ||||
|         const attemptId = this.autoReview.attemptId; | ||||
| 
 | ||||
|         if (this.quizAccessInfo?.canreviewmyattempts) { | ||||
|             try { | ||||
|                 await AddonModQuiz.getAttemptReview(attemptId, { page: -1, cmId: this.module!.id }); | ||||
|                 await AddonModQuiz.getAttemptReview(attemptId, { page: -1, cmId: this.module.id }); | ||||
| 
 | ||||
|                 await CoreNavigator.navigate(`review/${attemptId}`); | ||||
|             } catch { | ||||
| @ -429,7 +429,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp | ||||
|     protected hasSyncSucceed(result: AddonModQuizSyncResult): boolean { | ||||
|         if (result.attemptFinished) { | ||||
|             // An attempt was finished, check completion status.
 | ||||
|             CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata); | ||||
|             CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata); | ||||
|         } | ||||
| 
 | ||||
|         // If the sync call isn't rejected it means the sync was successful.
 | ||||
| @ -488,7 +488,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp | ||||
|     protected async invalidateContent(): Promise<void> { | ||||
|         const promises: Promise<void>[] = []; | ||||
| 
 | ||||
|         promises.push(AddonModQuiz.invalidateQuizData(this.courseId!)); | ||||
|         promises.push(AddonModQuiz.invalidateQuizData(this.courseId)); | ||||
| 
 | ||||
|         if (this.quiz) { | ||||
|             promises.push(AddonModQuiz.invalidateUserAttemptsForUser(this.quiz.id)); | ||||
| @ -497,7 +497,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp | ||||
|             promises.push(AddonModQuiz.invalidateAttemptAccessInformation(this.quiz.id)); | ||||
|             promises.push(AddonModQuiz.invalidateCombinedReviewOptionsForUser(this.quiz.id)); | ||||
|             promises.push(AddonModQuiz.invalidateUserBestGradeForUser(this.quiz.id)); | ||||
|             promises.push(AddonModQuiz.invalidateGradeFromGradebook(this.courseId!)); | ||||
|             promises.push(AddonModQuiz.invalidateGradeFromGradebook(this.courseId)); | ||||
|         } | ||||
| 
 | ||||
|         await Promise.all(promises); | ||||
| @ -536,7 +536,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp | ||||
| 
 | ||||
|         CoreNavigator.navigate('player', { | ||||
|             params: { | ||||
|                 moduleUrl: this.module?.url, | ||||
|                 moduleUrl: this.module.url, | ||||
|             }, | ||||
|         }); | ||||
|     } | ||||
| @ -594,7 +594,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp | ||||
|         } | ||||
| 
 | ||||
|         // Get combined review options.
 | ||||
|         promises.push(AddonModQuiz.getCombinedReviewOptions(quiz.id, { cmId: this.module!.id }).then((options) => { | ||||
|         promises.push(AddonModQuiz.getCombinedReviewOptions(quiz.id, { cmId: this.module.id }).then((options) => { | ||||
|             this.options = options; | ||||
| 
 | ||||
|             return; | ||||
| @ -633,11 +633,11 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected async getQuizGrade(quiz: AddonModQuizQuizData): Promise<void> { | ||||
|         this.bestGrade = await AddonModQuiz.getUserBestGrade(quiz.id, { cmId: this.module!.id }); | ||||
|         this.bestGrade = await AddonModQuiz.getUserBestGrade(quiz.id, { cmId: this.module.id }); | ||||
| 
 | ||||
|         try { | ||||
|             // Get gradebook grade.
 | ||||
|             const data = await AddonModQuiz.getGradeFromGradebook(this.courseId!, this.module!.id); | ||||
|             const data = await AddonModQuiz.getGradeFromGradebook(this.courseId, this.module.id); | ||||
| 
 | ||||
|             if (data) { | ||||
|                 this.gradebookData = { | ||||
|  | ||||
| @ -14,7 +14,7 @@ | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!activityComponent?.loaded" (ionRefresh)="activityComponent?.doRefresh($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!activityComponent?.loaded" (ionRefresh)="activityComponent?.doRefresh($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
| 
 | ||||
|  | ||||
| @ -22,7 +22,7 @@ | ||||
| 
 | ||||
|     <core-course-module-description *ngIf="mode != 'iframe' && (mode != 'embedded' || displayDescription)" | ||||
|         [description]="description" [component]="component" [componentId]="componentId" contextLevel="module" | ||||
|         [contextInstanceId]="module!.id" [courseId]="courseId"> | ||||
|         [contextInstanceId]="module.id" [courseId]="courseId"> | ||||
|     </core-course-module-description> | ||||
| 
 | ||||
|     <ion-card class="core-warning-card" *ngIf="warning"> | ||||
|  | ||||
| @ -64,8 +64,8 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource | ||||
| 
 | ||||
|         await this.loadContent(); | ||||
|         try { | ||||
|             await AddonModResource.logView(this.module!.instance!, this.module!.name); | ||||
|             CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata); | ||||
|             await AddonModResource.logView(this.module.instance!, this.module.name); | ||||
|             CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata); | ||||
|         } catch { | ||||
|             // Ignore errors.
 | ||||
|         } | ||||
| @ -77,7 +77,7 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource | ||||
|      * @return Resolved when done. | ||||
|      */ | ||||
|     protected async invalidateContent(): Promise<void> { | ||||
|         return AddonModResource.invalidateContent(this.module!.id, this.courseId!); | ||||
|         return AddonModResource.invalidateContent(this.module.id, this.courseId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -88,9 +88,9 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource | ||||
|      */ | ||||
|     protected async fetchContent(refresh?: boolean): Promise<void> { | ||||
|         // Load module contents if needed. Passing refresh is needed to force reloading contents.
 | ||||
|         await CoreCourse.loadModuleContents(this.module!, this.courseId, undefined, false, refresh); | ||||
|         await CoreCourse.loadModuleContents(this.module, this.courseId, undefined, false, refresh); | ||||
| 
 | ||||
|         if (!this.module!.contents || !this.module!.contents.length) { | ||||
|         if (!this.module.contents || !this.module.contents.length) { | ||||
|             throw new CoreError(Translate.instant('core.filenotfound')); | ||||
|         } | ||||
| 
 | ||||
| @ -99,11 +99,11 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource | ||||
| 
 | ||||
|         // Get the resource instance to get the latest name/description and to know if it's embedded.
 | ||||
|         if (this.canGetResource) { | ||||
|             resource = await CoreUtils.ignoreErrors(AddonModResource.getResourceData(this.courseId!, this.module!.id)); | ||||
|             resource = await CoreUtils.ignoreErrors(AddonModResource.getResourceData(this.courseId, this.module.id)); | ||||
|             this.description = resource?.intro || ''; | ||||
|             options = resource?.displayoptions ? CoreTextUtils.unserialize(resource.displayoptions) : {}; | ||||
|         } else { | ||||
|             resource = await CoreUtils.ignoreErrors(CoreCourse.getModule(this.module!.id, this.courseId)); | ||||
|             resource = await CoreUtils.ignoreErrors(CoreCourse.getModule(this.module.id, this.courseId)); | ||||
|             this.description = resource?.description || ''; | ||||
|             options = resource?.customdata ? CoreTextUtils.unserialize(CoreTextUtils.parseJSON(resource.customdata)) : {}; | ||||
|         } | ||||
| @ -114,9 +114,9 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource | ||||
|                 this.dataRetrieved.emit(resource); | ||||
|             } | ||||
| 
 | ||||
|             if (AddonModResourceHelper.isDisplayedInIframe(this.module!)) { | ||||
|             if (AddonModResourceHelper.isDisplayedInIframe(this.module)) { | ||||
|                 const downloadResult = await this.downloadResourceIfNeeded(refresh, true); | ||||
|                 const src = await AddonModResourceHelper.getIframeSrc(this.module!); | ||||
|                 const src = await AddonModResourceHelper.getIframeSrc(this.module); | ||||
|                 this.mode = 'iframe'; | ||||
| 
 | ||||
|                 if (this.src && src.toString() == this.src.toString()) { | ||||
| @ -137,11 +137,11 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             if (resource && 'display' in resource && AddonModResourceHelper.isDisplayedEmbedded(this.module!, resource.display)) { | ||||
|             if (resource && 'display' in resource && AddonModResourceHelper.isDisplayedEmbedded(this.module, resource.display)) { | ||||
|                 this.mode = 'embedded'; | ||||
|                 this.warning = ''; | ||||
| 
 | ||||
|                 this.contentText = await AddonModResourceHelper.getEmbeddedHtml(this.module!, this.courseId!); | ||||
|                 this.contentText = await AddonModResourceHelper.getEmbeddedHtml(this.module, this.courseId); | ||||
|                 this.mode = this.contentText.length > 0 ? 'embedded' : 'external'; | ||||
|             } else { | ||||
|                 this.mode = 'external'; | ||||
| @ -158,20 +158,20 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async open(): Promise<void> { | ||||
|         let downloadable = await CoreCourseModulePrefetchDelegate.isModuleDownloadable(this.module!, this.courseId!); | ||||
|         let downloadable = await CoreCourseModulePrefetchDelegate.isModuleDownloadable(this.module, this.courseId); | ||||
| 
 | ||||
|         if (downloadable) { | ||||
|             // Check if the main file is downloadle.
 | ||||
|             // This isn't done in "isDownloadable" to prevent extra WS calls in the course page.
 | ||||
|             downloadable = await AddonModResourceHelper.isMainFileDownloadable(this.module!); | ||||
|             downloadable = await AddonModResourceHelper.isMainFileDownloadable(this.module); | ||||
| 
 | ||||
|             if (downloadable) { | ||||
|                 return AddonModResourceHelper.openModuleFile(this.module!, this.courseId!); | ||||
|                 return AddonModResourceHelper.openModuleFile(this.module, this.courseId); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // The resource cannot be downloaded, open the activity in browser.
 | ||||
|         await CoreSites.getCurrentSite()?.openInBrowserWithAutoLoginIfSameSite(this.module!.url!); | ||||
|         await CoreSites.getCurrentSite()?.openInBrowserWithAutoLoginIfSameSite(this.module.url!); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -16,7 +16,7 @@ | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" | ||||
|         [disabled]="!activityComponent?.loaded || activityComponent?.mode != 'external'" | ||||
|         (ionRefresh)="activityComponent?.doRefresh($event)"> | ||||
|         (ionRefresh)="activityComponent?.doRefresh($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
| 
 | ||||
|  | ||||
| @ -16,7 +16,7 @@ | ||||
| <core-loading [hideUntil]="loaded" class="core-loading-center"> | ||||
| 
 | ||||
|     <core-course-module-description *ngIf="displayDescription" [description]="description" [component]="component" | ||||
|         [componentId]="componentId" contextLevel="module" [contextInstanceId]="module!.id" [courseId]="courseId"> | ||||
|         [componentId]="componentId" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"> | ||||
|     </core-course-module-description> | ||||
| 
 | ||||
|     <div *ngIf="shouldIframe || (shouldEmbed && isOther)" class="addon-mod_url-embedded-url"> | ||||
|  | ||||
| @ -75,7 +75,7 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo | ||||
|      * @return Resolved when done. | ||||
|      */ | ||||
|     protected async invalidateContent(): Promise<void> { | ||||
|         await AddonModUrl.invalidateContent(this.module!.id, this.courseId!); | ||||
|         await AddonModUrl.invalidateContent(this.module.id, this.courseId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -90,7 +90,7 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo | ||||
|                 throw null; | ||||
|             } | ||||
|             // Fetch the module data.
 | ||||
|             const url = await AddonModUrl.getUrl(this.courseId!, this.module!.id); | ||||
|             const url = await AddonModUrl.getUrl(this.courseId, this.module.id); | ||||
| 
 | ||||
|             this.name = url.name; | ||||
|             this.description = url.intro; | ||||
| @ -102,17 +102,17 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo | ||||
|             } | ||||
| 
 | ||||
|             // Try to load module contents, it's needed to get the URL with parameters.
 | ||||
|             await CoreCourse.loadModuleContents(this.module!, this.courseId, undefined, false, refresh, undefined, 'url'); | ||||
|             await CoreCourse.loadModuleContents(this.module, this.courseId, undefined, false, refresh, undefined, 'url'); | ||||
| 
 | ||||
|             // Always use the URL from the module because it already includes the parameters.
 | ||||
|             this.url = this.module!.contents[0] && this.module!.contents[0].fileurl ? this.module!.contents[0].fileurl : undefined; | ||||
|             this.url = this.module.contents[0] && this.module.contents[0].fileurl ? this.module.contents[0].fileurl : undefined; | ||||
| 
 | ||||
|             await this.calculateDisplayOptions(url); | ||||
| 
 | ||||
|         } catch { | ||||
|             // Fallback in case is not prefetched or not available.
 | ||||
|             const mod = | ||||
|                 await CoreCourse.getModule(this.module!.id, this.courseId, undefined, false, false, undefined, 'url'); | ||||
|                 await CoreCourse.getModule(this.module.id, this.courseId, undefined, false, false, undefined, 'url'); | ||||
| 
 | ||||
|             this.name = mod.name; | ||||
|             this.description = mod.description; | ||||
| @ -167,8 +167,8 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo | ||||
|      */ | ||||
|     protected async logView(): Promise<void> { | ||||
|         try { | ||||
|             await AddonModUrl.logView(this.module!.instance!, this.module!.name); | ||||
|             CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata); | ||||
|             await AddonModUrl.logView(this.module.instance!, this.module.name); | ||||
|             CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata); | ||||
|         } catch { | ||||
|             // Ignore errors.
 | ||||
|         } | ||||
|  | ||||
| @ -13,7 +13,7 @@ | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!activityComponent?.loaded" (ionRefresh)="activityComponent?.doRefresh($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!activityComponent?.loaded" (ionRefresh)="activityComponent?.doRefresh($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
| 
 | ||||
|  | ||||
| @ -7,7 +7,7 @@ | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!notificationsLoaded" (ionRefresh)="refreshNotifications($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!notificationsLoaded" (ionRefresh)="refreshNotifications($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
|     <core-loading [hideUntil]="notificationsLoaded"> | ||||
|  | ||||
| @ -174,13 +174,13 @@ export class AddonNotificationsListPage implements OnInit, OnDestroy { | ||||
|      * @param refresher Refresher. | ||||
|      * @return Promise<any> Promise resolved when done. | ||||
|      */ | ||||
|     async refreshNotifications(refresher?: CustomEvent<IonRefresher>): Promise<void> { | ||||
|     async refreshNotifications(refresher?: IonRefresher): Promise<void> { | ||||
|         await CoreUtils.ignoreErrors(AddonNotifications.invalidateNotificationsList()); | ||||
| 
 | ||||
|         try { | ||||
|             await this.fetchNotifications(true); | ||||
|         } finally { | ||||
|             refresher?.detail.complete(); | ||||
|             refresher?.complete(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -16,7 +16,8 @@ | ||||
|     </core-context-menu> | ||||
| </core-navbar-buttons> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!preferencesLoaded || !notifPrefsEnabled" (ionRefresh)="refreshPreferences($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!preferencesLoaded || !notifPrefsEnabled" | ||||
|         (ionRefresh)="refreshPreferences($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
|     <core-loading [hideUntil]="preferencesLoaded"> | ||||
|  | ||||
| @ -179,13 +179,13 @@ export class AddonNotificationsSettingsPage implements OnInit, OnDestroy { | ||||
|      * | ||||
|      * @param refresher Refresher. | ||||
|      */ | ||||
|     async refreshPreferences(refresher?: CustomEvent<IonRefresher>): Promise<void> { | ||||
|     async refreshPreferences(refresher?: IonRefresher): Promise<void> { | ||||
|         try { | ||||
|             await CoreUtils.ignoreErrors(AddonNotifications.invalidateNotificationPreferences()); | ||||
| 
 | ||||
|             await this.fetchPreferences(); | ||||
|         } finally { | ||||
|             refresher?.detail.complete(); | ||||
|             refresher?.complete(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -8,7 +8,7 @@ | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!filesLoaded || (!showPrivateFiles && !showSiteFiles)" | ||||
|         (ionRefresh)="refreshData($event)"> | ||||
|         (ionRefresh)="refreshData($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
| 
 | ||||
|  | ||||
| @ -119,9 +119,9 @@ export class AddonPrivateFilesIndexPage implements OnInit, OnDestroy { | ||||
|      * | ||||
|      * @param refresher Refresher. | ||||
|      */ | ||||
|     refreshData(event?: CustomEvent<IonRefresher>): void { | ||||
|     refreshData(event?: IonRefresher): void { | ||||
|         this.refreshFiles().finally(() => { | ||||
|             event?.detail.complete(); | ||||
|             event?.complete(); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -71,10 +71,10 @@ export abstract class CoreBlockBaseComponent implements OnInit { | ||||
|      * @param showErrors If show errors to the user of hide them. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async doRefresh(refresher?:  CustomEvent<IonRefresher>, done?: () => void, showErrors: boolean = false): Promise<void> { | ||||
|     async doRefresh(refresher?:  IonRefresher, done?: () => void, showErrors: boolean = false): Promise<void> { | ||||
|         if (this.loaded) { | ||||
|             return this.refreshContent(showErrors).finally(() => { | ||||
|                 refresher?.detail.complete(); | ||||
|                 refresher?.complete(); | ||||
|                 done && done(); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
| @ -140,7 +140,7 @@ export class CoreBlockComponent implements OnInit, OnDestroy, DoCheck { | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async doRefresh( | ||||
|         refresher?: CustomEvent<IonRefresher>, | ||||
|         refresher?: IonRefresher, | ||||
|         done?: () => void, | ||||
|         showErrors: boolean = false, | ||||
|     ): Promise<void> { | ||||
|  | ||||
| @ -26,7 +26,7 @@ | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!commentsLoaded" (ionRefresh)="refreshComments(false, $event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!commentsLoaded" (ionRefresh)="refreshComments(false, $event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
|     <core-loading [hideUntil]="commentsLoaded"> | ||||
|  | ||||
| @ -205,7 +205,7 @@ export class CoreCommentsViewerPage implements OnInit, OnDestroy { | ||||
|      * @param refresher Refresher. | ||||
|      * @return Resolved when done. | ||||
|      */ | ||||
|     async refreshComments(showErrors: boolean, refresher?: CustomEvent<IonRefresher>): Promise<void> { | ||||
|     async refreshComments(showErrors: boolean, refresher?: IonRefresher): Promise<void> { | ||||
|         this.commentsLoaded = false; | ||||
|         this.refreshIcon = CoreConstants.ICON_LOADING; | ||||
|         this.syncIcon = CoreConstants.ICON_LOADING; | ||||
| @ -219,7 +219,7 @@ export class CoreCommentsViewerPage implements OnInit, OnDestroy { | ||||
|             try { | ||||
|                 await this.fetchComments(true, showErrors); | ||||
|             } finally { | ||||
|                 refresher?.detail.complete(); | ||||
|                 refresher?.complete(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -132,7 +132,7 @@ import { ADDON_MOD_FORUM_SERVICES } from '@addons/mod/forum/forum.module'; | ||||
| // @todo import { ADDON_MOD_H5P_ACTIVITY_SERVICES } from '@addons/mod/h5pactivity/h5pactivity.module';
 | ||||
| import { ADDON_MOD_IMSCP_SERVICES } from '@addons/mod/imscp/imscp.module'; | ||||
| import { ADDON_MOD_LESSON_SERVICES } from '@addons/mod/lesson/lesson.module'; | ||||
| // @todo import { ADDON_MOD_LTI_SERVICES } from '@addons/mod/lti/lti.module';
 | ||||
| import { ADDON_MOD_LTI_SERVICES } from '@addons/mod/lti/lti.module'; | ||||
| import { ADDON_MOD_PAGE_SERVICES } from '@addons/mod/page/page.module'; | ||||
| import { ADDON_MOD_QUIZ_SERVICES } from '@addons/mod/quiz/quiz.module'; | ||||
| import { ADDON_MOD_RESOURCE_SERVICES } from '@addons/mod/resource/resource.module'; | ||||
| @ -297,7 +297,7 @@ export class CoreCompileProvider { | ||||
|             // @todo ...ADDON_MOD_H5P_ACTIVITY_SERVICES,
 | ||||
|             ...ADDON_MOD_IMSCP_SERVICES, | ||||
|             ...ADDON_MOD_LESSON_SERVICES, | ||||
|             // @todo ...ADDON_MOD_LTI_SERVICES,
 | ||||
|             ...ADDON_MOD_LTI_SERVICES, | ||||
|             ...ADDON_MOD_PAGE_SERVICES, | ||||
|             ...ADDON_MOD_QUIZ_SERVICES, | ||||
|             ...ADDON_MOD_RESOURCE_SERVICES, | ||||
|  | ||||
| @ -50,8 +50,8 @@ export type CoreCourseResourceDownloadResult = { | ||||
| }) | ||||
| export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, CoreCourseModuleMainComponent { | ||||
| 
 | ||||
|     @Input() module?: CoreCourseModule; // The module of the component.
 | ||||
|     @Input() courseId?: number; // Course ID the component belongs to.
 | ||||
|     @Input() module!: CoreCourseModule; // The module of the component.
 | ||||
|     @Input() courseId!: number; // Course ID the component belongs to.
 | ||||
|     @Output() dataRetrieved = new EventEmitter<unknown>(); // Called to notify changes the index page from the main component.
 | ||||
| 
 | ||||
|     loaded = false; // If the component has been loaded.
 | ||||
| @ -90,10 +90,10 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, | ||||
|      */ | ||||
|     async ngOnInit(): Promise<void> { | ||||
|         this.siteId = CoreSites.getCurrentSiteId(); | ||||
|         this.description = this.module?.description; | ||||
|         this.componentId = this.module?.id; | ||||
|         this.externalUrl = this.module?.url; | ||||
|         this.courseId = this.courseId || this.module?.course; | ||||
|         this.description = this.module.description; | ||||
|         this.componentId = this.module.id; | ||||
|         this.externalUrl = this.module.url; | ||||
|         this.courseId = this.courseId || this.module.course!; | ||||
|         this.blog = await AddonBlog.isPluginEnabled(); | ||||
|     } | ||||
| 
 | ||||
| @ -105,8 +105,9 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, | ||||
|      * @param showErrors If show errors to the user of hide them. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async doRefresh(refresher?: CustomEvent<IonRefresher> | null, done?: () => void, showErrors: boolean = false): Promise<void> { | ||||
|     async doRefresh(refresher?: IonRefresher | null, done?: () => void, showErrors: boolean = false): Promise<void> { | ||||
|         if (!this.loaded || !this.module) { | ||||
|             // Module can be undefined if course format changes from single activity to weekly/topics.
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
| @ -118,7 +119,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, | ||||
| 
 | ||||
|         await CoreUtils.ignoreErrors(this.refreshContent(true, showErrors)); | ||||
| 
 | ||||
|         refresher?.detail.complete(); | ||||
|         refresher?.complete(); | ||||
|         done && done(); | ||||
|     } | ||||
| 
 | ||||
| @ -193,12 +194,8 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, | ||||
|      * Fill the context menu options | ||||
|      */ | ||||
|     protected fillContextMenu(refresh: boolean = false): void { | ||||
|         if (!this.module) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // All data obtained, now fill the context menu.
 | ||||
|         CoreCourseHelper.fillContextMenu(this, this.module, this.courseId!, refresh, this.component); | ||||
|         CoreCourseHelper.fillContextMenu(this, this.module, this.courseId, refresh, this.component); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -215,10 +212,10 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, | ||||
|     expandDescription(): void { | ||||
|         CoreTextUtils.viewText(Translate.instant('core.description'), this.description!, { | ||||
|             component: this.component, | ||||
|             componentId: this.module?.id, | ||||
|             componentId: this.module.id, | ||||
|             filter: true, | ||||
|             contextLevel: 'module', | ||||
|             instanceId: this.module?.id, | ||||
|             instanceId: this.module.id, | ||||
|             courseId: this.courseId, | ||||
|         }); | ||||
|     } | ||||
| @ -227,7 +224,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, | ||||
|      * Go to blog posts. | ||||
|      */ | ||||
|     async gotoBlog(): Promise<void> { | ||||
|         const params: Params = { cmId: this.module?.id }; | ||||
|         const params: Params = { cmId: this.module.id }; | ||||
| 
 | ||||
|         CoreNavigator.navigateToSitePath(AddonBlogMainMenuHandlerService.PAGE_NAME, { params }); | ||||
|     } | ||||
| @ -238,11 +235,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, | ||||
|      * @param done Function to call when done. | ||||
|      */ | ||||
|     prefetch(done?: () => void): void { | ||||
|         if (!this.module) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         CoreCourseHelper.contextMenuPrefetch(this, this.module, this.courseId!, done); | ||||
|         CoreCourseHelper.contextMenuPrefetch(this, this.module, this.courseId, done); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -251,17 +244,13 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, | ||||
|      * @param done Function to call when done. | ||||
|      */ | ||||
|     removeFiles(done?: () => void): void { | ||||
|         if (!this.module) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (this.prefetchStatus == CoreConstants.DOWNLOADING) { | ||||
|             CoreDomUtils.showAlertTranslated(undefined, 'core.course.cannotdeletewhiledownloading'); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         CoreCourseHelper.confirmAndRemoveFiles(this.module, this.courseId!, done); | ||||
|         CoreCourseHelper.confirmAndRemoveFiles(this.module, this.courseId, done); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -309,13 +298,13 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected async setStatusListener(): Promise<void> { | ||||
|         if (typeof this.statusObserver != 'undefined' || !this.module) { | ||||
|         if (typeof this.statusObserver != 'undefined') { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // Listen for changes on this module status.
 | ||||
|         this.statusObserver = CoreEvents.on(CoreEvents.PACKAGE_STATUS_CHANGED, (data) => { | ||||
|             if (!this.module || data.componentId != this.module.id || data.component != this.component) { | ||||
|             if (data.componentId != this.module.id || data.component != this.component) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
| @ -327,7 +316,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, | ||||
|         }, this.siteId); | ||||
| 
 | ||||
|         // Also, get the current status.
 | ||||
|         const status = await CoreCourseModulePrefetchDelegate.getModuleStatus(this.module, this.courseId!); | ||||
|         const status = await CoreCourseModulePrefetchDelegate.getModuleStatus(this.module, this.courseId); | ||||
| 
 | ||||
|         this.currentStatus = status; | ||||
|         this.showStatus(status); | ||||
| @ -350,17 +339,13 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, | ||||
|             failed: false, | ||||
|         }; | ||||
| 
 | ||||
|         if (!this.module) { | ||||
|             return result; | ||||
|         } | ||||
| 
 | ||||
|         // Get module status to determine if it needs to be downloaded.
 | ||||
|         await this.setStatusListener(); | ||||
| 
 | ||||
|         if (this.currentStatus != CoreConstants.DOWNLOADED) { | ||||
|             // Download content. This function also loads module contents if needed.
 | ||||
|             try { | ||||
|                 await CoreCourseModulePrefetchDelegate.downloadModule(this.module, this.courseId!); | ||||
|                 await CoreCourseModulePrefetchDelegate.downloadModule(this.module, this.courseId); | ||||
| 
 | ||||
|                 // If we reach here it means the download process already loaded the contents, no need to do it again.
 | ||||
|                 contentsAlreadyLoaded = true; | ||||
|  | ||||
| @ -497,7 +497,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { | ||||
|      * @param afterCompletionChange Whether the refresh is due to a completion change. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async doRefresh(refresher?: CustomEvent<IonRefresher>, done?: () => void, afterCompletionChange?: boolean): Promise<void> { | ||||
|     async doRefresh(refresher?: IonRefresher, done?: () => void, afterCompletionChange?: boolean): Promise<void> { | ||||
|         const promises = this.dynamicComponents?.map(async (component) => { | ||||
|             await component.callComponentFunction('doRefresh', [refresher, done, afterCompletionChange]); | ||||
|         }) || []; | ||||
| @ -508,7 +508,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { | ||||
| 
 | ||||
|         await Promise.all(promises); | ||||
| 
 | ||||
|         refresher?.detail.complete(); | ||||
|         refresher?.complete(); | ||||
|         done?.(); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -78,7 +78,7 @@ export class CoreCourseFormatSingleActivityComponent implements OnChanges { | ||||
|      * @param afterCompletionChange Whether the refresh is due to a completion change. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async doRefresh(refresher?: CustomEvent<IonRefresher>, done?: () => void, afterCompletionChange?: boolean): Promise<void> { | ||||
|     async doRefresh(refresher?: IonRefresher, done?: () => void, afterCompletionChange?: boolean): Promise<void> { | ||||
|         if (afterCompletionChange) { | ||||
|             // Don't refresh the view after a completion change since completion isn't displayed.
 | ||||
|             return; | ||||
|  | ||||
| @ -16,7 +16,7 @@ | ||||
|     </core-context-menu> | ||||
| </core-navbar-buttons> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!dataLoaded || !displayRefresher" (ionRefresh)="doRefresh($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!dataLoaded || !displayRefresher" (ionRefresh)="doRefresh($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
| 
 | ||||
| @ -26,4 +26,4 @@ | ||||
|             (completionChanged)="onCompletionChange($event)" class="core-course-format-{{course.format}}"> | ||||
|         </core-course-format> | ||||
|     </core-loading> | ||||
| </ion-content> | ||||
| </ion-content> | ||||
|  | ||||
| @ -327,7 +327,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { | ||||
|      * @param refresher Refresher. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async doRefresh(refresher?: CustomEvent<IonRefresher>): Promise<void> { | ||||
|     async doRefresh(refresher?: IonRefresher): Promise<void> { | ||||
|         await CoreUtils.ignoreErrors(this.invalidateData()); | ||||
| 
 | ||||
|         try { | ||||
| @ -339,7 +339,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { | ||||
|                 await CoreUtils.ignoreErrors(this.formatComponent.doRefresh(refresher)); | ||||
|             } | ||||
| 
 | ||||
|             refresher?.detail.complete(); | ||||
|             refresher?.complete(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -7,7 +7,7 @@ | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshData($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshData($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
|     <core-loading [hideUntil]="loaded"> | ||||
| @ -25,4 +25,4 @@ | ||||
|             </ng-container> | ||||
|         </ion-list> | ||||
|     </core-loading> | ||||
| </ion-content> | ||||
| </ion-content> | ||||
|  | ||||
| @ -119,13 +119,13 @@ export class CoreCourseListModTypePage implements OnInit { | ||||
|      * @param refresher Refresher. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async refreshData(refresher: CustomEvent<IonRefresher>): Promise<void> { | ||||
|     async refreshData(refresher: IonRefresher): Promise<void> { | ||||
|         await CoreUtils.ignoreErrors(CoreCourse.invalidateSections(this.courseId || 0)); | ||||
| 
 | ||||
|         try { | ||||
|             await this.fetchData(); | ||||
|         } finally { | ||||
|             refresher.detail.complete(); | ||||
|             refresher.complete(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -9,7 +9,7 @@ | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <ion-refresher slot="fixed" [disabled]="!dataLoaded" (ionRefresh)="refreshData($event)"> | ||||
|     <ion-refresher slot="fixed" [disabled]="!dataLoaded" (ionRefresh)="refreshData($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
|     <core-loading [hideUntil]="dataLoaded"> | ||||
|  | ||||
| @ -379,7 +379,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { | ||||
|      * | ||||
|      * @param refresher The refresher if this was triggered by a Pull To Refresh. | ||||
|      */ | ||||
|     async refreshData(refresher?: CustomEvent<IonRefresher>): Promise<void> { | ||||
|     async refreshData(refresher?: IonRefresher): Promise<void> { | ||||
|         const promises: Promise<void>[] = []; | ||||
| 
 | ||||
|         promises.push(CoreCourses.invalidateUserCourses()); | ||||
| @ -394,7 +394,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { | ||||
|         } | ||||
| 
 | ||||
|         await Promise.all(promises).finally(() => this.getCourse()).finally(() => { | ||||
|             refresher?.detail.complete(); | ||||
|             refresher?.complete(); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -184,7 +184,7 @@ export interface CoreCourseModuleMainComponent { | ||||
|      * @param done Function to call when done. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     doRefresh(refresher?: CustomEvent<IonRefresher>, done?: () => void): Promise<void>; | ||||
|     doRefresh(refresher?: IonRefresher, done?: () => void): Promise<void>; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user