diff --git a/src/addons/badges/pages/issued-badge/issued-badge.html b/src/addons/badges/pages/issued-badge/issued-badge.html index 545ca1fc2..406235739 100644 --- a/src/addons/badges/pages/issued-badge/issued-badge.html +++ b/src/addons/badges/pages/issued-badge/issued-badge.html @@ -8,7 +8,7 @@ - + diff --git a/src/addons/badges/pages/issued-badge/issued-badge.page.ts b/src/addons/badges/pages/issued-badge/issued-badge.page.ts index 0fb6b4ddf..ea1412a1a 100644 --- a/src/addons/badges/pages/issued-badge/issued-badge.page.ts +++ b/src/addons/badges/pages/issued-badge/issued-badge.page.ts @@ -101,7 +101,7 @@ export class AddonBadgesIssuedBadgePage implements OnInit { * * @param refresher Refresher. */ - async refreshBadges(refresher?: CustomEvent): Promise { + async refreshBadges(refresher?: IonRefresher): Promise { 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(); } } diff --git a/src/addons/blog/pages/entries/entries.html b/src/addons/blog/pages/entries/entries.html index 5d6c68e37..bd0afb530 100644 --- a/src/addons/blog/pages/entries/entries.html +++ b/src/addons/blog/pages/entries/entries.html @@ -8,7 +8,7 @@ - + diff --git a/src/addons/blog/pages/entries/entries.ts b/src/addons/blog/pages/entries/entries.ts index 39a63ae49..1e92dcc30 100644 --- a/src/addons/blog/pages/entries/entries.ts +++ b/src/addons/blog/pages/entries/entries.ts @@ -250,7 +250,7 @@ export class AddonBlogEntriesPage implements OnInit { * * @param refresher Refresher instance. */ - refresh(refresher?: CustomEvent): 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(); } }); }); diff --git a/src/addons/calendar/pages/day/day.html b/src/addons/calendar/pages/day/day.html index 33ea1792f..f42707045 100644 --- a/src/addons/calendar/pages/day/day.html +++ b/src/addons/calendar/pages/day/day.html @@ -21,7 +21,7 @@ - + diff --git a/src/addons/calendar/pages/day/day.page.ts b/src/addons/calendar/pages/day/day.page.ts index 9e40f97a1..8768f77e7 100644 --- a/src/addons/calendar/pages/day/day.page.ts +++ b/src/addons/calendar/pages/day/day.page.ts @@ -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, done?: () => void): Promise { + async doRefresh(refresher?: IonRefresher, done?: () => void): Promise { if (!this.loaded) { return; } await this.refreshData(true).finally(() => { - refresher?.detail.complete(); + refresher?.complete(); done && done(); }); } diff --git a/src/addons/calendar/pages/edit-event/edit-event.html b/src/addons/calendar/pages/edit-event/edit-event.html index 193a65b58..855e6468a 100644 --- a/src/addons/calendar/pages/edit-event/edit-event.html +++ b/src/addons/calendar/pages/edit-event/edit-event.html @@ -7,7 +7,7 @@ - + diff --git a/src/addons/calendar/pages/edit-event/edit-event.page.ts b/src/addons/calendar/pages/edit-event/edit-event.page.ts index 2fc986fcf..41aad7974 100644 --- a/src/addons/calendar/pages/edit-event/edit-event.page.ts +++ b/src/addons/calendar/pages/edit-event/edit-event.page.ts @@ -363,7 +363,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave { * * @param refresher Refresher. */ - refreshData(refresher?: CustomEvent): 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(); }); }); } diff --git a/src/addons/calendar/pages/event/event.html b/src/addons/calendar/pages/event/event.html index 06771880f..8a208d412 100644 --- a/src/addons/calendar/pages/event/event.html +++ b/src/addons/calendar/pages/event/event.html @@ -31,7 +31,7 @@ - + diff --git a/src/addons/calendar/pages/event/event.page.ts b/src/addons/calendar/pages/event/event.page.ts index 3355c3c3f..b14e27981 100644 --- a/src/addons/calendar/pages/event/event.page.ts +++ b/src/addons/calendar/pages/event/event.page.ts @@ -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, done?: () => void, showErrors= false): Promise { + async doRefresh(refresher?: IonRefresher, done?: () => void, showErrors= false): Promise { if (!this.eventLoaded) { return; } await this.refreshEvent(true, showErrors).finally(() => { - refresher?.detail.complete(); + refresher?.complete(); done && done(); }); } diff --git a/src/addons/calendar/pages/index/index.html b/src/addons/calendar/pages/index/index.html index a81f512ee..8bf094003 100644 --- a/src/addons/calendar/pages/index/index.html +++ b/src/addons/calendar/pages/index/index.html @@ -26,7 +26,7 @@ - + diff --git a/src/addons/calendar/pages/index/index.page.ts b/src/addons/calendar/pages/index/index.page.ts index b93e09a8b..edc213f18 100644 --- a/src/addons/calendar/pages/index/index.page.ts +++ b/src/addons/calendar/pages/index/index.page.ts @@ -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, done?: () => void, showErrors?: boolean): Promise { + async doRefresh(refresher?: IonRefresher, done?: () => void, showErrors?: boolean): Promise { if (!this.loaded) { return; } await this.refreshData(true, showErrors).finally(() => { - refresher?.detail.complete(); + refresher?.complete(); done && done(); }); } diff --git a/src/addons/calendar/pages/list/list.html b/src/addons/calendar/pages/list/list.html index 65fbc523a..037d09307 100644 --- a/src/addons/calendar/pages/list/list.html +++ b/src/addons/calendar/pages/list/list.html @@ -21,7 +21,7 @@ - + diff --git a/src/addons/calendar/pages/list/list.page.ts b/src/addons/calendar/pages/list/list.page.ts index 79cf722a5..857f79293 100644 --- a/src/addons/calendar/pages/list/list.page.ts +++ b/src/addons/calendar/pages/list/list.page.ts @@ -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, done?: () => void, showErrors?: boolean): Promise { + async doRefresh(refresher?: IonRefresher, done?: () => void, showErrors?: boolean): Promise { if (!this.eventsLoaded) { return; } await this.refreshEvents(true, showErrors).finally(() => { - refresher?.detail.complete(); + refresher?.complete(); done && done(); }); } diff --git a/src/addons/messageoutput/airnotifier/pages/devices/devices.html b/src/addons/messageoutput/airnotifier/pages/devices/devices.html index 134300b25..63e0ba868 100644 --- a/src/addons/messageoutput/airnotifier/pages/devices/devices.html +++ b/src/addons/messageoutput/airnotifier/pages/devices/devices.html @@ -7,7 +7,7 @@ - + diff --git a/src/addons/messageoutput/airnotifier/pages/devices/devices.ts b/src/addons/messageoutput/airnotifier/pages/devices/devices.ts index 6f64746a4..513539169 100644 --- a/src/addons/messageoutput/airnotifier/pages/devices/devices.ts +++ b/src/addons/messageoutput/airnotifier/pages/devices/devices.ts @@ -106,13 +106,13 @@ export class AddonMessageOutputAirnotifierDevicesPage implements OnInit, OnDestr * * @param refresher Refresher. */ - async refreshDevices(refresher: CustomEvent): Promise { + async refreshDevices(refresher: IonRefresher): Promise { try { await CoreUtils.ignoreErrors(AddonMessageOutputAirnotifier.invalidateUserDevices()); await this.fetchDevices(); } finally { - refresher?.detail.complete(); + refresher?.complete(); } } diff --git a/src/addons/messages/components/conversation-info/conversation-info.html b/src/addons/messages/components/conversation-info/conversation-info.html index 76a789e43..38add1ddc 100644 --- a/src/addons/messages/components/conversation-info/conversation-info.html +++ b/src/addons/messages/components/conversation-info/conversation-info.html @@ -9,7 +9,7 @@ - + diff --git a/src/addons/messages/components/conversation-info/conversation-info.ts b/src/addons/messages/components/conversation-info/conversation-info.ts index 3736e59f8..7b877e732 100644 --- a/src/addons/messages/components/conversation-info/conversation-info.ts +++ b/src/addons/messages/components/conversation-info/conversation-info.ts @@ -122,7 +122,7 @@ export class AddonMessagesConversationInfoComponent implements OnInit { * @param refresher Refresher. * @return Promise resolved when done. */ - async refreshData(refresher?: CustomEvent): Promise { + async refreshData(refresher?: IonRefresher): Promise { const promises: Promise[] = []; 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(); }); } diff --git a/src/addons/messages/pages/contacts-35/contacts.html b/src/addons/messages/pages/contacts-35/contacts.html index a91b3af6b..7123855b3 100644 --- a/src/addons/messages/pages/contacts-35/contacts.html +++ b/src/addons/messages/pages/contacts-35/contacts.html @@ -13,7 +13,7 @@ - + diff --git a/src/addons/messages/pages/contacts-35/contacts.page.ts b/src/addons/messages/pages/contacts-35/contacts.page.ts index bd0f98e2b..5d66baa9c 100644 --- a/src/addons/messages/pages/contacts-35/contacts.page.ts +++ b/src/addons/messages/pages/contacts-35/contacts.page.ts @@ -128,7 +128,7 @@ export class AddonMessagesContacts35Page implements OnInit, OnDestroy { * @param refresher Refresher. * @return Promise resolved when done. */ - async refreshData(refresher?: CustomEvent): Promise { + async refreshData(refresher?: IonRefresher): Promise { 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(); } } diff --git a/src/addons/messages/pages/contacts/contacts.html b/src/addons/messages/pages/contacts/contacts.html index 9fd4b990f..b8257e7eb 100644 --- a/src/addons/messages/pages/contacts/contacts.html +++ b/src/addons/messages/pages/contacts/contacts.html @@ -21,7 +21,7 @@ - + @@ -57,7 +57,7 @@ - + diff --git a/src/addons/messages/pages/contacts/contacts.page.ts b/src/addons/messages/pages/contacts/contacts.page.ts index 9843c5f51..473820dd8 100644 --- a/src/addons/messages/pages/contacts/contacts.page.ts +++ b/src/addons/messages/pages/contacts/contacts.page.ts @@ -185,7 +185,7 @@ export class AddonMessagesContactsPage implements OnInit, OnDestroy { * @param refresher Refresher. * @return Promise resolved when done. */ - async refreshData(refresher?: CustomEvent): Promise { + async refreshData(refresher?: IonRefresher): Promise { 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(); } } diff --git a/src/addons/messages/pages/discussions-35/discussions.html b/src/addons/messages/pages/discussions-35/discussions.html index e5bce97cd..2fcb1b7df 100644 --- a/src/addons/messages/pages/discussions-35/discussions.html +++ b/src/addons/messages/pages/discussions-35/discussions.html @@ -13,7 +13,7 @@ - + diff --git a/src/addons/messages/pages/discussions-35/discussions.page.ts b/src/addons/messages/pages/discussions-35/discussions.page.ts index 34a935772..da4b4a5b7 100644 --- a/src/addons/messages/pages/discussions-35/discussions.page.ts +++ b/src/addons/messages/pages/discussions-35/discussions.page.ts @@ -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, refreshUnreadCounts: boolean = true): Promise { + async refreshData(refresher?: IonRefresher, refreshUnreadCounts: boolean = true): Promise { const promises: Promise[] = []; 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(); } })); } diff --git a/src/addons/messages/pages/group-conversations/group-conversations.html b/src/addons/messages/pages/group-conversations/group-conversations.html index 4bd68d1bd..7bcb4da68 100644 --- a/src/addons/messages/pages/group-conversations/group-conversations.html +++ b/src/addons/messages/pages/group-conversations/group-conversations.html @@ -19,7 +19,7 @@ - + diff --git a/src/addons/messages/pages/group-conversations/group-conversations.page.ts b/src/addons/messages/pages/group-conversations/group-conversations.page.ts index 2757fc7a2..6dbc9d9ca 100644 --- a/src/addons/messages/pages/group-conversations/group-conversations.page.ts +++ b/src/addons/messages/pages/group-conversations/group-conversations.page.ts @@ -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, refreshUnreadCounts: boolean = true): Promise { + async refreshData(refresher?: IonRefresher, refreshUnreadCounts: boolean = true): Promise { // 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(); } } } diff --git a/src/addons/messages/pages/settings/settings.html b/src/addons/messages/pages/settings/settings.html index f1fa655a1..2eaf16de4 100644 --- a/src/addons/messages/pages/settings/settings.html +++ b/src/addons/messages/pages/settings/settings.html @@ -7,7 +7,7 @@ - + diff --git a/src/addons/messages/pages/settings/settings.page.ts b/src/addons/messages/pages/settings/settings.page.ts index 4a0cf0f89..b986e55b3 100644 --- a/src/addons/messages/pages/settings/settings.page.ts +++ b/src/addons/messages/pages/settings/settings.page.ts @@ -251,10 +251,10 @@ export class AddonMessagesSettingsPage implements OnInit, OnDestroy { * * @param refresher Refresher. */ - refreshPreferences(refresher?: CustomEvent): void { + refreshPreferences(refresher?: IonRefresher): void { AddonMessages.invalidateMessagePreferences().finally(() => { this.fetchPreferences().finally(() => { - refresher?.detail.complete(); + refresher?.complete(); }); }); } diff --git a/src/addons/mod/assign/components/index/addon-mod-assign-index.html b/src/addons/mod/assign/components/index/addon-mod-assign-index.html index 12d416cdc..04e5dd3e5 100644 --- a/src/addons/mod/assign/components/index/addon-mod-assign-index.html +++ b/src/addons/mod/assign/components/index/addon-mod-assign-index.html @@ -35,7 +35,7 @@ + contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId" (click)="expandDescription($event)"> @@ -136,7 +136,7 @@ + [moduleId]="module.id"> diff --git a/src/addons/mod/assign/components/index/index.ts b/src/addons/mod/assign/components/index/index.ts index ab7786eb0..bb05633bc 100644 --- a/src/addons/mod/assign/components/index/index.ts +++ b/src/addons/mod/assign/components/index/index.ts @@ -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 { const promises: Promise[] = []; - promises.push(AddonModAssign.invalidateAssignmentData(this.courseId!)); + promises.push(AddonModAssign.invalidateAssignmentData(this.courseId)); if (this.assign) { promises.push(AddonModAssign.invalidateAllSubmissionData(this.assign.id)); diff --git a/src/addons/mod/assign/pages/index/index.html b/src/addons/mod/assign/pages/index/index.html index 457b6f8d0..c886cd072 100644 --- a/src/addons/mod/assign/pages/index/index.html +++ b/src/addons/mod/assign/pages/index/index.html @@ -14,7 +14,7 @@ - + diff --git a/src/addons/mod/assign/pages/submission-list/submission-list.html b/src/addons/mod/assign/pages/submission-list/submission-list.html index e88b89eac..a47fa1e33 100644 --- a/src/addons/mod/assign/pages/submission-list/submission-list.html +++ b/src/addons/mod/assign/pages/submission-list/submission-list.html @@ -14,7 +14,7 @@ - + diff --git a/src/addons/mod/assign/pages/submission-list/submission-list.page.ts b/src/addons/mod/assign/pages/submission-list/submission-list.page.ts index fe21ced82..26f9f8d22 100644 --- a/src/addons/mod/assign/pages/submission-list/submission-list.page.ts +++ b/src/addons/mod/assign/pages/submission-list/submission-list.page.ts @@ -328,9 +328,9 @@ export class AddonModAssignSubmissionListPage implements AfterViewInit, OnDestro * * @param refresher Refresher. */ - refreshList(refresher?: CustomEvent): void { + refreshList(refresher?: IonRefresher): void { this.refreshAllData(true).finally(() => { - refresher?.detail.complete(); + refresher?.complete(); }); } diff --git a/src/addons/mod/assign/pages/submission-review/submission-review.html b/src/addons/mod/assign/pages/submission-review/submission-review.html index 6b010c098..024b0bdc9 100644 --- a/src/addons/mod/assign/pages/submission-review/submission-review.html +++ b/src/addons/mod/assign/pages/submission-review/submission-review.html @@ -19,7 +19,7 @@ - + diff --git a/src/addons/mod/assign/pages/submission-review/submission-review.ts b/src/addons/mod/assign/pages/submission-review/submission-review.ts index 2c5e846d7..f6f5d5dd6 100644 --- a/src/addons/mod/assign/pages/submission-review/submission-review.ts +++ b/src/addons/mod/assign/pages/submission-review/submission-review.ts @@ -155,9 +155,9 @@ export class AddonModAssignSubmissionReviewPage implements OnInit, CanLeave { * * @param refresher Refresher. */ - refreshSubmission(refresher?: CustomEvent): void { + refreshSubmission(refresher?: IonRefresher): void { this.refreshAllData().finally(() => { - refresher?.detail.complete(); + refresher?.complete(); }); } diff --git a/src/addons/mod/book/components/index/addon-mod-book-index.html b/src/addons/mod/book/components/index/addon-mod-book-index.html index 0aaef2eb6..87a5723dc 100644 --- a/src/addons/mod/book/components/index/addon-mod-book-index.html +++ b/src/addons/mod/book/components/index/addon-mod-book-index.html @@ -24,7 +24,7 @@ + contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"> @@ -38,7 +38,7 @@ + [contextInstanceId]="module.id" [courseId]="courseId">
{{ 'core.tag.tags' | translate }}: diff --git a/src/addons/mod/book/components/index/index.ts b/src/addons/mod/book/components/index/index.ts index 19d1f0b2e..929fda9a6 100644 --- a/src/addons/mod/book/components/index/index.ts +++ b/src/addons/mod/book/components/index/index.ts @@ -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 { - 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); diff --git a/src/addons/mod/book/pages/index/index.html b/src/addons/mod/book/pages/index/index.html index fd3fa778d..c8abcd019 100644 --- a/src/addons/mod/book/pages/index/index.html +++ b/src/addons/mod/book/pages/index/index.html @@ -13,7 +13,7 @@ - + diff --git a/src/addons/mod/folder/components/index/addon-mod-folder-index.html b/src/addons/mod/folder/components/index/addon-mod-folder-index.html index 992d5370f..9262fe900 100644 --- a/src/addons/mod/folder/components/index/addon-mod-folder-index.html +++ b/src/addons/mod/folder/components/index/addon-mod-folder-index.html @@ -26,7 +26,7 @@ + contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"> diff --git a/src/addons/mod/folder/components/index/index.ts b/src/addons/mod/folder/components/index/index.ts index 6bb72670a..a3b4b2fae 100644 --- a/src/addons/mod/folder/components/index/index.ts +++ b/src/addons/mod/folder/components/index/index.ts @@ -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 { - 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 { 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); } diff --git a/src/addons/mod/folder/pages/index/index.html b/src/addons/mod/folder/pages/index/index.html index 2bdcf774b..b9094f24a 100644 --- a/src/addons/mod/folder/pages/index/index.html +++ b/src/addons/mod/folder/pages/index/index.html @@ -15,7 +15,7 @@ + (ionRefresh)="activityComponent?.doRefresh($event.target)"> diff --git a/src/addons/mod/forum/components/index/index.html b/src/addons/mod/forum/components/index/index.html index 599c5e192..7563b457d 100644 --- a/src/addons/mod/forum/components/index/index.html +++ b/src/addons/mod/forum/components/index/index.html @@ -37,7 +37,7 @@ - + diff --git a/src/addons/mod/forum/components/index/index.ts b/src/addons/mod/forum/components/index/index.ts index 322df8f67..4c0a44724 100644 --- a/src/addons/mod/forum/components/index/index.ts +++ b/src/addons/mod/forum/components/index/index.ts @@ -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 { const promises: Promise[] = []; - 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 { - 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 - + diff --git a/src/addons/mod/forum/pages/discussion/discussion.page.ts b/src/addons/mod/forum/pages/discussion/discussion.page.ts index 9cfa80c3a..0b154e89c 100644 --- a/src/addons/mod/forum/pages/discussion/discussion.page.ts +++ b/src/addons/mod/forum/pages/discussion/discussion.page.ts @@ -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 { + async doRefresh(refresher?: IonRefresher | null, done?: () => void, showErrors: boolean = false): Promise { 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 { + changeSort(type: SortType): Promise { this.discussionLoaded = false; this.sort = type; CoreSites.getCurrentSite()!.setLocalSiteConfig('AddonModForumDiscussionSort', this.sort); diff --git a/src/addons/mod/imscp/components/index/addon-mod-imscp-index.html b/src/addons/mod/imscp/components/index/addon-mod-imscp-index.html index e74c82b66..ffefed01e 100644 --- a/src/addons/mod/imscp/components/index/addon-mod-imscp-index.html +++ b/src/addons/mod/imscp/components/index/addon-mod-imscp-index.html @@ -36,7 +36,7 @@
+ [contextInstanceId]="module.id" [courseId]="courseId">
diff --git a/src/addons/mod/imscp/components/index/index.ts b/src/addons/mod/imscp/components/index/index.ts index 5f08a8735..e71c5be51 100644 --- a/src/addons/mod/imscp/components/index/index.ts +++ b/src/addons/mod/imscp/components/index/index.ts @@ -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 { - 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[] = []; - 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 { - 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) : ''; diff --git a/src/addons/mod/lesson/components/index/addon-mod-lesson-index.html b/src/addons/mod/lesson/components/index/addon-mod-lesson-index.html index 676ad9624..b6d7fc159 100644 --- a/src/addons/mod/lesson/components/index/addon-mod-lesson-index.html +++ b/src/addons/mod/lesson/components/index/addon-mod-lesson-index.html @@ -32,7 +32,7 @@ + contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"> diff --git a/src/addons/mod/lesson/components/index/index.ts b/src/addons/mod/lesson/components/index/index.ts index 41074093a..125d6f009 100644 --- a/src/addons/mod/lesson/components/index/index.ts +++ b/src/addons/mod/lesson/components/index/index.ts @@ -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[] = []; - 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 { const promises: Promise[] = []; - 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 { 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) { diff --git a/src/addons/mod/lesson/pages/index/index.html b/src/addons/mod/lesson/pages/index/index.html index a245910fa..6871daf92 100644 --- a/src/addons/mod/lesson/pages/index/index.html +++ b/src/addons/mod/lesson/pages/index/index.html @@ -13,7 +13,7 @@ - + diff --git a/src/addons/mod/lesson/pages/user-retake/user-retake.html b/src/addons/mod/lesson/pages/user-retake/user-retake.html index 7554d3ae1..feef68f22 100644 --- a/src/addons/mod/lesson/pages/user-retake/user-retake.html +++ b/src/addons/mod/lesson/pages/user-retake/user-retake.html @@ -7,7 +7,7 @@ - + diff --git a/src/addons/mod/lesson/pages/user-retake/user-retake.page.ts b/src/addons/mod/lesson/pages/user-retake/user-retake.page.ts index b7473c4b1..58c826758 100644 --- a/src/addons/mod/lesson/pages/user-retake/user-retake.page.ts +++ b/src/addons/mod/lesson/pages/user-retake/user-retake.page.ts @@ -98,9 +98,9 @@ export class AddonModLessonUserRetakePage implements OnInit { * * @param refresher Refresher. */ - doRefresh(refresher: CustomEvent): void { + doRefresh(refresher: IonRefresher): void { this.refreshData().finally(() => { - refresher?.detail.complete(); + refresher?.complete(); }); } diff --git a/src/addons/mod/lti/components/components.module.ts b/src/addons/mod/lti/components/components.module.ts new file mode 100644 index 000000000..27ae3a4f2 --- /dev/null +++ b/src/addons/mod/lti/components/components.module.ts @@ -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 {} diff --git a/src/addons/mod/lti/components/index/addon-mod-lti-index.html b/src/addons/mod/lti/components/index/addon-mod-lti-index.html new file mode 100644 index 000000000..3302fe4ef --- /dev/null +++ b/src/addons/mod/lti/components/index/addon-mod-lti-index.html @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + +
+ + + {{ 'addon.mod_lti.launchactivity' | translate }} + +
+
diff --git a/src/addons/mod/lti/components/index/index.ts b/src/addons/mod/lti/components/index/index.ts new file mode 100644 index 000000000..f18244c01 --- /dev/null +++ b/src/addons/mod/lti/components/index/index.ts @@ -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 { + super.ngOnInit(); + + this.loadContent(); + } + + /** + * @inheritdoc + */ + protected async fetchContent(refresh: boolean = false): Promise { + 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 { + const promises: Promise[] = []; + + 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); + } + +} diff --git a/src/addons/mod/lti/lang.json b/src/addons/mod/lti/lang.json new file mode 100644 index 000000000..7a70ea4e7 --- /dev/null +++ b/src/addons/mod/lti/lang.json @@ -0,0 +1,6 @@ +{ + "errorgetlti": "Error getting module data.", + "errorinvalidlaunchurl": "The launch URL is not valid.", + "launchactivity": "Launch the activity", + "modulenameplural": "External tools" +} \ No newline at end of file diff --git a/src/addons/mod/lti/lti-lazy.module.ts b/src/addons/mod/lti/lti-lazy.module.ts new file mode 100644 index 000000000..163e70a80 --- /dev/null +++ b/src/addons/mod/lti/lti-lazy.module.ts @@ -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 {} diff --git a/src/addons/mod/lti/lti.module.ts b/src/addons/mod/lti/lti.module.ts new file mode 100644 index 000000000..6c6b08390 --- /dev/null +++ b/src/addons/mod/lti/lti.module.ts @@ -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[] = [ + 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 {} diff --git a/src/addons/mod/lti/pages/index/index.html b/src/addons/mod/lti/pages/index/index.html new file mode 100644 index 000000000..2ae22748b --- /dev/null +++ b/src/addons/mod/lti/pages/index/index.html @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/addons/mod/lti/pages/index/index.page.ts b/src/addons/mod/lti/pages/index/index.page.ts new file mode 100644 index 000000000..a19c7515b --- /dev/null +++ b/src/addons/mod/lti/pages/index/index.page.ts @@ -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 { + + @ViewChild(AddonModLtiIndexComponent) activityComponent?: AddonModLtiIndexComponent; + +} diff --git a/src/addons/mod/lti/services/handlers/index-link.ts b/src/addons/mod/lti/services/handlers/index-link.ts new file mode 100644 index 000000000..3b2318049 --- /dev/null +++ b/src/addons/mod/lti/services/handlers/index-link.ts @@ -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); diff --git a/src/addons/mod/lti/services/handlers/list-link.ts b/src/addons/mod/lti/services/handlers/list-link.ts new file mode 100644 index 000000000..17d7bb815 --- /dev/null +++ b/src/addons/mod/lti/services/handlers/list-link.ts @@ -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); diff --git a/src/addons/mod/lti/services/handlers/module.ts b/src/addons/mod/lti/services/handlers/module.ts new file mode 100644 index 000000000..c09f5e5f0 --- /dev/null +++ b/src/addons/mod/lti/services/handlers/module.ts @@ -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 { + 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 { + 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 | undefined> { + return AddonModLtiIndexComponent; + } + +} + +export const AddonModLtiModuleHandler = makeSingleton(AddonModLtiModuleHandlerService); diff --git a/src/addons/mod/lti/services/handlers/prefetch.ts b/src/addons/mod/lti/services/handlers/prefetch.ts new file mode 100644 index 000000000..b8682bb6a --- /dev/null +++ b/src/addons/mod/lti/services/handlers/prefetch.ts @@ -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 { + return; + } + + /** + * @inheritdoc + */ + invalidateModule(module: CoreCourseAnyModuleData, courseId: number): Promise { + return AddonModLti.invalidateLti(courseId); + } + + /** + * @inheritdoc + */ + async isDownloadable(): Promise { + return false; // LTIs aren't downloadable. + } + + /** + * @inheritdoc + */ + async prefetch(): Promise { + return; + } + +} + +export const AddonModLtiPrefetchHandler = makeSingleton(AddonModLtiPrefetchHandlerService); diff --git a/src/addons/mod/lti/services/lti-helper.ts b/src/addons/mod/lti/services/lti-helper.ts new file mode 100644 index 000000000..3f11b180f --- /dev/null +++ b/src/addons/mod/lti/services/lti-helper.ts @@ -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 { + 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 { + try { + await AddonModLti.logView(ltiId, name, siteId); + + CoreCourse.checkModuleCompletion(courseId, module.completiondata); + } catch (error) { + // Ignore errors. + } + } + +} + +export const AddonModLtiHelper = makeSingleton(AddonModLtiHelperProvider); diff --git a/src/addons/mod/lti/services/lti.ts b/src/addons/mod/lti/services/lti.ts new file mode 100644 index 000000000..de9409d71 --- /dev/null +++ b/src/addons/mod/lti/services/lti.ts @@ -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 { + 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 { + if (!CoreFile.isAvailable()) { + return url; + } + + // Generate a form with the params. + let text = `
\n`; + params.forEach((p) => { + if (p.name == 'ext_submit') { + text += ' \n'; + }); + text += '
\n'; + + // Add an in-line script to automatically submit the form. + text += ' \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 { + 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('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 { + 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('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 { + 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 { + 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 { + 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 { + 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 { + 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. +}; diff --git a/src/addons/mod/mod.module.ts b/src/addons/mod/mod.module.ts index 5688284c5..bf3163e78 100644 --- a/src/addons/mod/mod.module.ts +++ b/src/addons/mod/mod.module.ts @@ -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: [], diff --git a/src/addons/mod/page/components/index/addon-mod-page-index.html b/src/addons/mod/page/components/index/addon-mod-page-index.html index 3384cd5ac..3a99d22bb 100644 --- a/src/addons/mod/page/components/index/addon-mod-page-index.html +++ b/src/addons/mod/page/components/index/addon-mod-page-index.html @@ -26,7 +26,7 @@ + [componentId]="componentId" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"> @@ -36,7 +36,7 @@
+ [contextInstanceId]="module.id" [courseId]="courseId">

diff --git a/src/addons/mod/page/components/index/index.ts b/src/addons/mod/page/components/index/index.ts index 7cf5feb5a..d8d3cdb61 100644 --- a/src/addons/mod/page/components/index/index.ts +++ b/src/addons/mod/page/components/index/index.ts @@ -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 { - 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!) : ''; diff --git a/src/addons/mod/page/pages/index/index.html b/src/addons/mod/page/pages/index/index.html index 1547156df..43792de0c 100644 --- a/src/addons/mod/page/pages/index/index.html +++ b/src/addons/mod/page/pages/index/index.html @@ -13,7 +13,7 @@ - + diff --git a/src/addons/mod/quiz/components/index/addon-mod-quiz-index.html b/src/addons/mod/quiz/components/index/addon-mod-quiz-index.html index 87dd74a24..18d2667bc 100644 --- a/src/addons/mod/quiz/components/index/addon-mod-quiz-index.html +++ b/src/addons/mod/quiz/components/index/addon-mod-quiz-index.html @@ -28,7 +28,7 @@ + contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"> @@ -117,7 +117,7 @@

{{ 'addon.mod_quiz.comment' | translate }}

+ contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">

@@ -125,7 +125,7 @@

{{ 'addon.mod_quiz.overallfeedback' | translate }}

+ contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">

diff --git a/src/addons/mod/quiz/components/index/index.ts b/src/addons/mod/quiz/components/index/index.ts index 46b9c15e7..8f10dcc82 100644 --- a/src/addons/mod/quiz/components/index/index.ts +++ b/src/addons/mod/quiz/components/index/index.ts @@ -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 { 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 { // 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 { const promises: Promise[] = []; - 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 { - 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 = { diff --git a/src/addons/mod/quiz/pages/index/index.html b/src/addons/mod/quiz/pages/index/index.html index 3b85aa282..06758cb51 100644 --- a/src/addons/mod/quiz/pages/index/index.html +++ b/src/addons/mod/quiz/pages/index/index.html @@ -14,7 +14,7 @@ - + diff --git a/src/addons/mod/resource/components/index/addon-mod-resource-index.html b/src/addons/mod/resource/components/index/addon-mod-resource-index.html index 56757e8d1..9c5f81c23 100644 --- a/src/addons/mod/resource/components/index/addon-mod-resource-index.html +++ b/src/addons/mod/resource/components/index/addon-mod-resource-index.html @@ -22,7 +22,7 @@ + [contextInstanceId]="module.id" [courseId]="courseId"> diff --git a/src/addons/mod/resource/components/index/index.ts b/src/addons/mod/resource/components/index/index.ts index 4b1c9b8cf..c6afd4d61 100644 --- a/src/addons/mod/resource/components/index/index.ts +++ b/src/addons/mod/resource/components/index/index.ts @@ -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 { - 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 { // 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 { - 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!); } } diff --git a/src/addons/mod/resource/pages/index/index.html b/src/addons/mod/resource/pages/index/index.html index d8f0497a8..16007eb9d 100644 --- a/src/addons/mod/resource/pages/index/index.html +++ b/src/addons/mod/resource/pages/index/index.html @@ -16,7 +16,7 @@ + (ionRefresh)="activityComponent?.doRefresh($event.target)"> diff --git a/src/addons/mod/url/components/index/addon-mod-url-index.html b/src/addons/mod/url/components/index/addon-mod-url-index.html index 11392ce43..10bc4fbc2 100644 --- a/src/addons/mod/url/components/index/addon-mod-url-index.html +++ b/src/addons/mod/url/components/index/addon-mod-url-index.html @@ -16,7 +16,7 @@ + [componentId]="componentId" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
diff --git a/src/addons/mod/url/components/index/index.ts b/src/addons/mod/url/components/index/index.ts index 75b80f768..0409c7e4c 100644 --- a/src/addons/mod/url/components/index/index.ts +++ b/src/addons/mod/url/components/index/index.ts @@ -75,7 +75,7 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo * @return Resolved when done. */ protected async invalidateContent(): Promise { - 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 { 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. } diff --git a/src/addons/mod/url/pages/index/index.html b/src/addons/mod/url/pages/index/index.html index 6a1cda2f0..2ee90cced 100644 --- a/src/addons/mod/url/pages/index/index.html +++ b/src/addons/mod/url/pages/index/index.html @@ -13,7 +13,7 @@ - + diff --git a/src/addons/notifications/pages/list/list.html b/src/addons/notifications/pages/list/list.html index b454f0f24..8a7fcca16 100644 --- a/src/addons/notifications/pages/list/list.html +++ b/src/addons/notifications/pages/list/list.html @@ -7,7 +7,7 @@ - + diff --git a/src/addons/notifications/pages/list/list.ts b/src/addons/notifications/pages/list/list.ts index f65a05e1e..fc37ccd61 100644 --- a/src/addons/notifications/pages/list/list.ts +++ b/src/addons/notifications/pages/list/list.ts @@ -174,13 +174,13 @@ export class AddonNotificationsListPage implements OnInit, OnDestroy { * @param refresher Refresher. * @return Promise Promise resolved when done. */ - async refreshNotifications(refresher?: CustomEvent): Promise { + async refreshNotifications(refresher?: IonRefresher): Promise { await CoreUtils.ignoreErrors(AddonNotifications.invalidateNotificationsList()); try { await this.fetchNotifications(true); } finally { - refresher?.detail.complete(); + refresher?.complete(); } } diff --git a/src/addons/notifications/pages/settings/settings.html b/src/addons/notifications/pages/settings/settings.html index 6283ec6e6..b51239d38 100644 --- a/src/addons/notifications/pages/settings/settings.html +++ b/src/addons/notifications/pages/settings/settings.html @@ -16,7 +16,8 @@ - + diff --git a/src/addons/notifications/pages/settings/settings.ts b/src/addons/notifications/pages/settings/settings.ts index 5524486a4..0748db3b5 100644 --- a/src/addons/notifications/pages/settings/settings.ts +++ b/src/addons/notifications/pages/settings/settings.ts @@ -179,13 +179,13 @@ export class AddonNotificationsSettingsPage implements OnInit, OnDestroy { * * @param refresher Refresher. */ - async refreshPreferences(refresher?: CustomEvent): Promise { + async refreshPreferences(refresher?: IonRefresher): Promise { try { await CoreUtils.ignoreErrors(AddonNotifications.invalidateNotificationPreferences()); await this.fetchPreferences(); } finally { - refresher?.detail.complete(); + refresher?.complete(); } } diff --git a/src/addons/privatefiles/pages/index/index.html b/src/addons/privatefiles/pages/index/index.html index 9ac22822a..497740c02 100644 --- a/src/addons/privatefiles/pages/index/index.html +++ b/src/addons/privatefiles/pages/index/index.html @@ -8,7 +8,7 @@ + (ionRefresh)="refreshData($event.target)"> diff --git a/src/addons/privatefiles/pages/index/index.ts b/src/addons/privatefiles/pages/index/index.ts index c9498f8d6..8c9933dd0 100644 --- a/src/addons/privatefiles/pages/index/index.ts +++ b/src/addons/privatefiles/pages/index/index.ts @@ -119,9 +119,9 @@ export class AddonPrivateFilesIndexPage implements OnInit, OnDestroy { * * @param refresher Refresher. */ - refreshData(event?: CustomEvent): void { + refreshData(event?: IonRefresher): void { this.refreshFiles().finally(() => { - event?.detail.complete(); + event?.complete(); }); } diff --git a/src/core/features/block/classes/base-block-component.ts b/src/core/features/block/classes/base-block-component.ts index bc3bc2d66..e0bf19b45 100644 --- a/src/core/features/block/classes/base-block-component.ts +++ b/src/core/features/block/classes/base-block-component.ts @@ -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, done?: () => void, showErrors: boolean = false): Promise { + async doRefresh(refresher?: IonRefresher, done?: () => void, showErrors: boolean = false): Promise { if (this.loaded) { return this.refreshContent(showErrors).finally(() => { - refresher?.detail.complete(); + refresher?.complete(); done && done(); }); } diff --git a/src/core/features/block/components/block/block.ts b/src/core/features/block/components/block/block.ts index bb534d946..05ee91cc0 100644 --- a/src/core/features/block/components/block/block.ts +++ b/src/core/features/block/components/block/block.ts @@ -140,7 +140,7 @@ export class CoreBlockComponent implements OnInit, OnDestroy, DoCheck { * @return Promise resolved when done. */ async doRefresh( - refresher?: CustomEvent, + refresher?: IonRefresher, done?: () => void, showErrors: boolean = false, ): Promise { diff --git a/src/core/features/comments/pages/viewer/viewer.html b/src/core/features/comments/pages/viewer/viewer.html index 0ab6da568..2cc9db450 100644 --- a/src/core/features/comments/pages/viewer/viewer.html +++ b/src/core/features/comments/pages/viewer/viewer.html @@ -26,7 +26,7 @@ - + diff --git a/src/core/features/comments/pages/viewer/viewer.page.ts b/src/core/features/comments/pages/viewer/viewer.page.ts index 4c8bf2b2b..98f33c31a 100644 --- a/src/core/features/comments/pages/viewer/viewer.page.ts +++ b/src/core/features/comments/pages/viewer/viewer.page.ts @@ -205,7 +205,7 @@ export class CoreCommentsViewerPage implements OnInit, OnDestroy { * @param refresher Refresher. * @return Resolved when done. */ - async refreshComments(showErrors: boolean, refresher?: CustomEvent): Promise { + async refreshComments(showErrors: boolean, refresher?: IonRefresher): Promise { 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(); } } } diff --git a/src/core/features/compile/services/compile.ts b/src/core/features/compile/services/compile.ts index 2eeeff320..44b1ac0c2 100644 --- a/src/core/features/compile/services/compile.ts +++ b/src/core/features/compile/services/compile.ts @@ -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, diff --git a/src/core/features/course/classes/main-resource-component.ts b/src/core/features/course/classes/main-resource-component.ts index 0b849707c..239ae758b 100644 --- a/src/core/features/course/classes/main-resource-component.ts +++ b/src/core/features/course/classes/main-resource-component.ts @@ -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(); // 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 { 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 | null, done?: () => void, showErrors: boolean = false): Promise { + async doRefresh(refresher?: IonRefresher | null, done?: () => void, showErrors: boolean = false): Promise { 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 { - 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 { - 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; diff --git a/src/core/features/course/components/format/format.ts b/src/core/features/course/components/format/format.ts index afbefe842..44d60f329 100644 --- a/src/core/features/course/components/format/format.ts +++ b/src/core/features/course/components/format/format.ts @@ -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, done?: () => void, afterCompletionChange?: boolean): Promise { + async doRefresh(refresher?: IonRefresher, done?: () => void, afterCompletionChange?: boolean): Promise { 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?.(); } diff --git a/src/core/features/course/format/singleactivity/components/singleactivity.ts b/src/core/features/course/format/singleactivity/components/singleactivity.ts index eae6ca273..ade822ac2 100644 --- a/src/core/features/course/format/singleactivity/components/singleactivity.ts +++ b/src/core/features/course/format/singleactivity/components/singleactivity.ts @@ -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, done?: () => void, afterCompletionChange?: boolean): Promise { + async doRefresh(refresher?: IonRefresher, done?: () => void, afterCompletionChange?: boolean): Promise { if (afterCompletionChange) { // Don't refresh the view after a completion change since completion isn't displayed. return; diff --git a/src/core/features/course/pages/contents/contents.html b/src/core/features/course/pages/contents/contents.html index a779fc7fd..288737a8b 100644 --- a/src/core/features/course/pages/contents/contents.html +++ b/src/core/features/course/pages/contents/contents.html @@ -16,7 +16,7 @@ - + @@ -26,4 +26,4 @@ (completionChanged)="onCompletionChange($event)" class="core-course-format-{{course.format}}"> - \ No newline at end of file + diff --git a/src/core/features/course/pages/contents/contents.ts b/src/core/features/course/pages/contents/contents.ts index bbc5ac7b0..e09f8b038 100644 --- a/src/core/features/course/pages/contents/contents.ts +++ b/src/core/features/course/pages/contents/contents.ts @@ -327,7 +327,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { * @param refresher Refresher. * @return Promise resolved when done. */ - async doRefresh(refresher?: CustomEvent): Promise { + async doRefresh(refresher?: IonRefresher): Promise { 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(); } } diff --git a/src/core/features/course/pages/list-mod-type/list-mod-type.html b/src/core/features/course/pages/list-mod-type/list-mod-type.html index 7c2ebffed..76d88b6fb 100644 --- a/src/core/features/course/pages/list-mod-type/list-mod-type.html +++ b/src/core/features/course/pages/list-mod-type/list-mod-type.html @@ -7,7 +7,7 @@ - + @@ -25,4 +25,4 @@ - \ No newline at end of file + diff --git a/src/core/features/course/pages/list-mod-type/list-mod-type.page.ts b/src/core/features/course/pages/list-mod-type/list-mod-type.page.ts index f4dd7492a..f24a70c92 100644 --- a/src/core/features/course/pages/list-mod-type/list-mod-type.page.ts +++ b/src/core/features/course/pages/list-mod-type/list-mod-type.page.ts @@ -119,13 +119,13 @@ export class CoreCourseListModTypePage implements OnInit { * @param refresher Refresher. * @return Promise resolved when done. */ - async refreshData(refresher: CustomEvent): Promise { + async refreshData(refresher: IonRefresher): Promise { await CoreUtils.ignoreErrors(CoreCourse.invalidateSections(this.courseId || 0)); try { await this.fetchData(); } finally { - refresher.detail.complete(); + refresher.complete(); } } diff --git a/src/core/features/course/pages/preview/preview.html b/src/core/features/course/pages/preview/preview.html index d035d22a2..9954d3aff 100644 --- a/src/core/features/course/pages/preview/preview.html +++ b/src/core/features/course/pages/preview/preview.html @@ -9,7 +9,7 @@ - + diff --git a/src/core/features/course/pages/preview/preview.page.ts b/src/core/features/course/pages/preview/preview.page.ts index ef7569fd2..286ca4b25 100644 --- a/src/core/features/course/pages/preview/preview.page.ts +++ b/src/core/features/course/pages/preview/preview.page.ts @@ -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): Promise { + async refreshData(refresher?: IonRefresher): Promise { const promises: Promise[] = []; 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(); }); } diff --git a/src/core/features/course/services/module-delegate.ts b/src/core/features/course/services/module-delegate.ts index 5e2718822..547ff1c1a 100644 --- a/src/core/features/course/services/module-delegate.ts +++ b/src/core/features/course/services/module-delegate.ts @@ -184,7 +184,7 @@ export interface CoreCourseModuleMainComponent { * @param done Function to call when done. * @return Promise resolved when done. */ - doRefresh(refresher?: CustomEvent, done?: () => void): Promise; + doRefresh(refresher?: IonRefresher, done?: () => void): Promise; } /** diff --git a/src/core/features/courses/pages/available-courses/available-courses.html b/src/core/features/courses/pages/available-courses/available-courses.html index eafff0704..9448450d4 100644 --- a/src/core/features/courses/pages/available-courses/available-courses.html +++ b/src/core/features/courses/pages/available-courses/available-courses.html @@ -7,7 +7,7 @@ - + diff --git a/src/core/features/courses/pages/available-courses/available-courses.ts b/src/core/features/courses/pages/available-courses/available-courses.ts index 43238450d..c7a5b70ef 100644 --- a/src/core/features/courses/pages/available-courses/available-courses.ts +++ b/src/core/features/courses/pages/available-courses/available-courses.ts @@ -62,7 +62,7 @@ export class CoreCoursesAvailableCoursesPage implements OnInit { * * @param refresher Refresher. */ - refreshCourses(refresher: CustomEvent): void { + refreshCourses(refresher: IonRefresher): void { const promises: Promise[] = []; promises.push(CoreCourses.invalidateUserCourses()); @@ -70,7 +70,7 @@ export class CoreCoursesAvailableCoursesPage implements OnInit { Promise.all(promises).finally(() => { this.loadCourses().finally(() => { - refresher?.detail.complete(); + refresher?.complete(); }); }); } diff --git a/src/core/features/courses/pages/categories/categories.html b/src/core/features/courses/pages/categories/categories.html index 8015b9cd0..5ac29a6ed 100644 --- a/src/core/features/courses/pages/categories/categories.html +++ b/src/core/features/courses/pages/categories/categories.html @@ -10,7 +10,7 @@ - + diff --git a/src/core/features/courses/pages/categories/categories.ts b/src/core/features/courses/pages/categories/categories.ts index 1dce61f1a..f38243bbe 100644 --- a/src/core/features/courses/pages/categories/categories.ts +++ b/src/core/features/courses/pages/categories/categories.ts @@ -102,7 +102,7 @@ export class CoreCoursesCategoriesPage implements OnInit { * * @param refresher Refresher. */ - refreshCategories(refresher?: CustomEvent): void { + refreshCategories(refresher?: IonRefresher): void { const promises: Promise[] = []; promises.push(CoreCourses.invalidateUserCourses()); @@ -112,7 +112,7 @@ export class CoreCoursesCategoriesPage implements OnInit { Promise.all(promises).finally(() => { this.fetchCategories().finally(() => { - refresher?.detail.complete(); + refresher?.complete(); }); }); } diff --git a/src/core/features/courses/pages/dashboard/dashboard.html b/src/core/features/courses/pages/dashboard/dashboard.html index 5e0488613..b7daa49e9 100644 --- a/src/core/features/courses/pages/dashboard/dashboard.html +++ b/src/core/features/courses/pages/dashboard/dashboard.html @@ -12,7 +12,7 @@ - + diff --git a/src/core/features/courses/pages/dashboard/dashboard.ts b/src/core/features/courses/pages/dashboard/dashboard.ts index 0cd29483c..7aa5bd0a2 100644 --- a/src/core/features/courses/pages/dashboard/dashboard.ts +++ b/src/core/features/courses/pages/dashboard/dashboard.ts @@ -119,7 +119,7 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy { * * @param refresher Refresher. */ - refreshDashboard(refresher: CustomEvent): void { + refreshDashboard(refresher: IonRefresher): void { const promises: Promise[] = []; promises.push(CoreCoursesDashboard.invalidateDashboardBlocks()); @@ -133,7 +133,7 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy { Promise.all(promises).finally(() => { this.loadContent().finally(() => { - refresher?.detail.complete(); + refresher?.complete(); }); }); } diff --git a/src/core/features/courses/pages/my-courses/my-courses.html b/src/core/features/courses/pages/my-courses/my-courses.html index 6cbd9c972..54e5043b1 100644 --- a/src/core/features/courses/pages/my-courses/my-courses.html +++ b/src/core/features/courses/pages/my-courses/my-courses.html @@ -25,7 +25,7 @@ - + diff --git a/src/core/features/courses/pages/my-courses/my-courses.ts b/src/core/features/courses/pages/my-courses/my-courses.ts index 252ba4357..e23121503 100644 --- a/src/core/features/courses/pages/my-courses/my-courses.ts +++ b/src/core/features/courses/pages/my-courses/my-courses.ts @@ -122,7 +122,7 @@ export class CoreCoursesMyCoursesPage implements OnInit, OnDestroy { * * @param refresher Refresher. */ - refreshCourses(refresher: CustomEvent): void { + refreshCourses(refresher: IonRefresher): void { const promises: Promise[] = []; promises.push(CoreCourses.invalidateUserCourses()); @@ -133,7 +133,7 @@ export class CoreCoursesMyCoursesPage implements OnInit, OnDestroy { Promise.all(promises).finally(() => { this.fetchCourses().finally(() => { - refresher?.detail.complete(); + refresher?.complete(); }); }); } diff --git a/src/core/features/features.module.ts b/src/core/features/features.module.ts index 653f24f03..9be217cb1 100644 --- a/src/core/features/features.module.ts +++ b/src/core/features/features.module.ts @@ -32,6 +32,7 @@ import { CoreViewerModule } from './viewer/viewer.module'; import { CoreSearchModule } from './search/search.module'; import { CoreCommentsModule } from './comments/comments.module'; import { CoreSitePluginsModule } from './siteplugins/siteplugins.module'; +import { CoreRatingModule } from './rating/rating.module'; @NgModule({ imports: [ @@ -53,6 +54,7 @@ import { CoreSitePluginsModule } from './siteplugins/siteplugins.module'; CoreViewerModule, CoreCommentsModule, CoreSitePluginsModule, + CoreRatingModule, ], }) export class CoreFeaturesModule {} diff --git a/src/core/features/login/pages/email-signup/email-signup.html b/src/core/features/login/pages/email-signup/email-signup.html index a39d39675..3bab0e0db 100644 --- a/src/core/features/login/pages/email-signup/email-signup.html +++ b/src/core/features/login/pages/email-signup/email-signup.html @@ -16,7 +16,7 @@ - + diff --git a/src/core/features/login/pages/email-signup/email-signup.ts b/src/core/features/login/pages/email-signup/email-signup.ts index 648e51acd..b662e0550 100644 --- a/src/core/features/login/pages/email-signup/email-signup.ts +++ b/src/core/features/login/pages/email-signup/email-signup.ts @@ -257,9 +257,9 @@ export class CoreLoginEmailSignupPage implements OnInit { * * @param event Event. */ - refreshSettings(event?: CustomEvent): void { + refreshSettings(event?: IonRefresher): void { this.fetchData().finally(() => { - event?.detail.complete(); + event?.complete(); }); } diff --git a/src/core/features/settings/pages/site/site.html b/src/core/features/settings/pages/site/site.html index 6ee4b674d..15242fa22 100644 --- a/src/core/features/settings/pages/site/site.html +++ b/src/core/features/settings/pages/site/site.html @@ -10,7 +10,7 @@ - + diff --git a/src/core/features/settings/pages/site/site.ts b/src/core/features/settings/pages/site/site.ts index 3f62f3207..6fabd9e42 100644 --- a/src/core/features/settings/pages/site/site.ts +++ b/src/core/features/settings/pages/site/site.ts @@ -152,9 +152,9 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy { * * @param refresher Refresher. */ - refreshData(refresher?: CustomEvent): void { + refreshData(refresher?: IonRefresher): void { this.fetchData().finally(() => { - refresher?.detail.complete(); + refresher?.complete(); }); } diff --git a/src/core/features/settings/pages/space-usage/space-usage.html b/src/core/features/settings/pages/space-usage/space-usage.html index 582fa1773..a6e822ea2 100644 --- a/src/core/features/settings/pages/space-usage/space-usage.html +++ b/src/core/features/settings/pages/space-usage/space-usage.html @@ -14,7 +14,7 @@ - + diff --git a/src/core/features/settings/pages/space-usage/space-usage.ts b/src/core/features/settings/pages/space-usage/space-usage.ts index af9d51202..7be8548d7 100644 --- a/src/core/features/settings/pages/space-usage/space-usage.ts +++ b/src/core/features/settings/pages/space-usage/space-usage.ts @@ -104,9 +104,9 @@ export class CoreSettingsSpaceUsagePage implements OnInit, OnDestroy { * * @param event Refresher event. */ - refreshData(event?: CustomEvent): void { + refreshData(refresher?: IonRefresher): void { this.loadSiteData().finally(() => { - event?.detail.complete(); + refresher?.complete(); }); } diff --git a/src/core/features/sitehome/pages/index/index.html b/src/core/features/sitehome/pages/index/index.html index bbee0488a..333569a0f 100644 --- a/src/core/features/sitehome/pages/index/index.html +++ b/src/core/features/sitehome/pages/index/index.html @@ -12,8 +12,7 @@ - + diff --git a/src/core/features/sitehome/pages/index/index.ts b/src/core/features/sitehome/pages/index/index.ts index 34b227618..19534eb1f 100644 --- a/src/core/features/sitehome/pages/index/index.ts +++ b/src/core/features/sitehome/pages/index/index.ts @@ -154,7 +154,7 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { * * @param refresher Refresher. */ - doRefresh(refresher?: CustomEvent): void { + doRefresh(refresher?: IonRefresher): void { const promises: Promise[] = []; promises.push(CoreCourse.invalidateSections(this.siteHomeId!)); @@ -184,7 +184,7 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { } await Promise.all(p2).finally(() => { - refresher?.detail.complete(); + refresher?.complete(); }); }); } diff --git a/src/core/features/siteplugins/components/course-format/course-format.ts b/src/core/features/siteplugins/components/course-format/course-format.ts index 8c8bb7251..88cd9d586 100644 --- a/src/core/features/siteplugins/components/course-format/course-format.ts +++ b/src/core/features/siteplugins/components/course-format/course-format.ts @@ -97,7 +97,7 @@ export class CoreSitePluginsCourseFormatComponent implements OnChanges { * @param afterCompletionChange Whether the refresh is due to a completion change. * @return Promise resolved when done. */ - async doRefresh(refresher?: CustomEvent, done?: () => void, afterCompletionChange?: boolean): Promise { + async doRefresh(refresher?: IonRefresher, done?: () => void, afterCompletionChange?: boolean): Promise { await this.content?.refreshContent(afterCompletionChange); } diff --git a/src/core/features/siteplugins/components/module-index/module-index.ts b/src/core/features/siteplugins/components/module-index/module-index.ts index 9f54212df..c3d5c322d 100644 --- a/src/core/features/siteplugins/components/module-index/module-index.ts +++ b/src/core/features/siteplugins/components/module-index/module-index.ts @@ -124,7 +124,7 @@ export class CoreSitePluginsModuleIndexComponent implements OnInit, OnDestroy, C * @param done Function to call when done. * @return Promise resolved when done. */ - async doRefresh(refresher?: CustomEvent | null, done?: () => void): Promise { + async doRefresh(refresher?: IonRefresher | null, done?: () => void): Promise { if (this.content) { this.refreshIcon = CoreConstants.ICON_LOADING; } @@ -132,7 +132,7 @@ export class CoreSitePluginsModuleIndexComponent implements OnInit, OnDestroy, C try { await this.content?.refreshContent(false); } finally { - refresher?.detail.complete(); + refresher?.complete(); done && done(); } } diff --git a/src/core/features/tag/pages/index-area/index-area.html b/src/core/features/tag/pages/index-area/index-area.html index ca219d19d..1a46e593c 100644 --- a/src/core/features/tag/pages/index-area/index-area.html +++ b/src/core/features/tag/pages/index-area/index-area.html @@ -9,7 +9,7 @@ - + diff --git a/src/core/features/tag/pages/index-area/index-area.page.ts b/src/core/features/tag/pages/index-area/index-area.page.ts index d8a1693ee..41c3d10bc 100644 --- a/src/core/features/tag/pages/index-area/index-area.page.ts +++ b/src/core/features/tag/pages/index-area/index-area.page.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Component, OnInit, Type } from '@angular/core'; -import { IonInfiniteScroll, IonRefresher } from '@ionic/angular'; +import { IonRefresher } from '@ionic/angular'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreTag } from '@features/tag/services/tag'; import { ActivatedRoute } from '@angular/router'; @@ -140,11 +140,11 @@ export class CoreTagIndexAreaPage implements OnInit { * @param infiniteComplete Infinite scroll complete function. * @return Resolved when done. */ - async loadMore(infiniteComplete?: CustomEvent): Promise { + async loadMore(infiniteComplete?: () => void): Promise { try { await this.fetchData(); } finally { - infiniteComplete?.detail.complete(); + infiniteComplete?.(); } } @@ -153,7 +153,7 @@ export class CoreTagIndexAreaPage implements OnInit { * * @param refresher Refresher. */ - async refreshData(refresher?: CustomEvent): Promise { + async refreshData(refresher?: IonRefresher): Promise { try { await CoreTag.invalidateTagIndexPerArea( this.tagId, @@ -168,7 +168,7 @@ export class CoreTagIndexAreaPage implements OnInit { try { await this.fetchData(true); } finally { - refresher?.detail.complete(); + refresher?.complete(); } } } diff --git a/src/core/features/tag/pages/index/index.html b/src/core/features/tag/pages/index/index.html index 8172d9862..86459aa45 100644 --- a/src/core/features/tag/pages/index/index.html +++ b/src/core/features/tag/pages/index/index.html @@ -9,7 +9,7 @@ - + diff --git a/src/core/features/tag/pages/index/index.page.ts b/src/core/features/tag/pages/index/index.page.ts index 15f36f5a0..86722010b 100644 --- a/src/core/features/tag/pages/index/index.page.ts +++ b/src/core/features/tag/pages/index/index.page.ts @@ -121,7 +121,7 @@ export class CoreTagIndexPage implements OnInit { * * @param refresher Refresher. */ - refreshData(refresher?: CustomEvent): void { + refreshData(refresher?: IonRefresher): void { CoreTag.invalidateTagIndexPerArea( this.tagId, this.tagName, @@ -132,7 +132,7 @@ export class CoreTagIndexPage implements OnInit { this.recursive, ).finally(() => { this.fetchData().finally(() => { - refresher?.detail.complete(); + refresher?.complete(); }); }); } diff --git a/src/core/features/tag/pages/search/search.html b/src/core/features/tag/pages/search/search.html index 2ecb2320e..043d92f0e 100644 --- a/src/core/features/tag/pages/search/search.html +++ b/src/core/features/tag/pages/search/search.html @@ -7,7 +7,7 @@ - + diff --git a/src/core/features/tag/pages/search/search.page.ts b/src/core/features/tag/pages/search/search.page.ts index fcd51cd88..c6f2dbd82 100644 --- a/src/core/features/tag/pages/search/search.page.ts +++ b/src/core/features/tag/pages/search/search.page.ts @@ -103,12 +103,12 @@ export class CoreTagSearchPage implements OnInit { * * @param refresher Refresher event. */ - refreshData(refresher?: CustomEvent): void { + refreshData(refresher?: IonRefresher): void { CoreUtils.allPromises([ CoreTag.invalidateTagCollections(), CoreTag.invalidateTagCloud(this.collectionId, undefined, undefined, this.query), ]).finally(() => this.fetchData().finally(() => { - refresher?.detail.complete(); + refresher?.complete(); })); } diff --git a/src/core/features/user/pages/about/about.html b/src/core/features/user/pages/about/about.html index 5f44d6504..0ca238725 100644 --- a/src/core/features/user/pages/about/about.html +++ b/src/core/features/user/pages/about/about.html @@ -7,7 +7,7 @@ - + diff --git a/src/core/features/user/pages/about/about.page.ts b/src/core/features/user/pages/about/about.page.ts index 495c2bcdf..1999862e4 100644 --- a/src/core/features/user/pages/about/about.page.ts +++ b/src/core/features/user/pages/about/about.page.ts @@ -94,12 +94,12 @@ export class CoreUserAboutPage implements OnInit { * @param event Event. * @return Promise resolved when done. */ - async refreshUser(event?: CustomEvent): Promise { + async refreshUser(event?: IonRefresher): Promise { await CoreUtils.ignoreErrors(CoreUser.invalidateUserCache(this.userId)); await this.fetchUser(); - event?.detail.complete(); + event?.complete(); if (this.user) { CoreEvents.trigger(CoreUserProvider.PROFILE_REFRESHED, { diff --git a/src/core/features/user/pages/participants/participants.html b/src/core/features/user/pages/participants/participants.html index 1eda7f069..be2d7c93e 100644 --- a/src/core/features/user/pages/participants/participants.html +++ b/src/core/features/user/pages/participants/participants.html @@ -6,7 +6,8 @@ - + @@ -17,16 +18,18 @@ - + - + + class="ion-text-wrap" [class.core-selected-item]="participants.isSelected(participant)" + [title]="participant.fullname" (click)="participants.select(participant)"> @@ -34,20 +37,28 @@

{{ participant.fullname }}

-

{{ 'core.lastaccess' | translate }}: {{ participant.lastcourseaccess | coreTimeAgo }}

-

{{ 'core.lastaccess' | translate }}: {{ participant.lastaccess | coreTimeAgo }}

+

+ {{ 'core.lastaccess' | translate }}: + {{ participant.lastcourseaccess | coreTimeAgo }} +

+

+ {{ 'core.lastaccess' | translate }}: + {{ participant.lastaccess | coreTimeAgo }} +

- + +

- +
diff --git a/src/core/features/user/pages/profile/profile.html b/src/core/features/user/pages/profile/profile.html index acfd950c7..cd7ae01b9 100644 --- a/src/core/features/user/pages/profile/profile.html +++ b/src/core/features/user/pages/profile/profile.html @@ -7,7 +7,7 @@ - + diff --git a/src/core/features/user/pages/profile/profile.page.ts b/src/core/features/user/pages/profile/profile.page.ts index 5dc175524..6b5722678 100644 --- a/src/core/features/user/pages/profile/profile.page.ts +++ b/src/core/features/user/pages/profile/profile.page.ts @@ -235,7 +235,7 @@ export class CoreUserProfilePage implements OnInit, OnDestroy { * @param event Event. * @return Promise resolved when done. */ - async refreshUser(event?: CustomEvent): Promise { + async refreshUser(event?: IonRefresher): Promise { await CoreUtils.ignoreErrors(Promise.all([ CoreUser.invalidateUserCache(this.userId), CoreCourses.invalidateUserNavigationOptions(), @@ -244,7 +244,7 @@ export class CoreUserProfilePage implements OnInit, OnDestroy { await this.fetchUser(); - event?.detail.complete(); + event?.complete(); if (this.user) { CoreEvents.trigger(CoreUserProvider.PROFILE_REFRESHED, {