diff --git a/src/addons/calendar/services/calendar-sync.ts b/src/addons/calendar/services/calendar-sync.ts index 21845fba8..cc9cb00b0 100644 --- a/src/addons/calendar/services/calendar-sync.ts +++ b/src/addons/calendar/services/calendar-sync.ts @@ -27,7 +27,7 @@ import { import { AddonCalendarOffline } from './calendar-offline'; import { AddonCalendarHelper } from './calendar-helper'; import { makeSingleton, Translate } from '@singletons'; -import { CoreSync } from '@services/sync'; +import { CoreSync, CoreSyncResult } from '@services/sync'; import { CoreNetworkError } from '@classes/errors/network-error'; import moment from 'moment-timezone'; @@ -301,13 +301,11 @@ export class AddonCalendarSyncProvider extends CoreSyncBaseProvider; // Map offline ID with online ID for created events. deleted: number[]; toinvalidate: AddonCalendarSyncInvalidateEvent[]; - updated: boolean; source?: string; // Added on pages. moment?: moment.Moment; // Added on day page. }; diff --git a/src/addons/messages/pages/discussion/discussion.page.ts b/src/addons/messages/pages/discussion/discussion.page.ts index 9dbb10719..acb9aa30d 100644 --- a/src/addons/messages/pages/discussion/discussion.page.ts +++ b/src/addons/messages/pages/discussion/discussion.page.ts @@ -270,9 +270,10 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView } if (this.userId) { + const userId = this.userId; // Get the member info. Invalidate first to make sure we get the latest status. promises.push(AddonMessages.invalidateMemberInfo(this.userId).then(async () => { - this.otherMember = await AddonMessages.getMemberInfo(this.userId!); + this.otherMember = await AddonMessages.getMemberInfo(userId); if (!exists && this.otherMember) { this.conversationImage = this.otherMember.profileimageurl; @@ -288,6 +289,8 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView } else { if (this.userId) { + const userId = this.userId; + // Fake the user member info. promises.push(CoreUser.getProfile(this.userId).then(async (user) => { this.otherMember = { @@ -305,8 +308,8 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView canmessage: true, requirescontact: false, }; - this.otherMember.isblocked = await AddonMessages.isBlocked(this.userId!); - this.otherMember.iscontact = await AddonMessages.isContact(this.userId!); + this.otherMember.isblocked = await AddonMessages.isBlocked(userId); + this.otherMember.iscontact = await AddonMessages.isContact(userId); this.blockIcon = this.otherMember.isblocked ? 'fas-user-check' : 'fas-user-lock'; return; diff --git a/src/addons/messages/pages/discussions-35/discussions.page.ts b/src/addons/messages/pages/discussions-35/discussions.page.ts index 0706b9b9d..3b597d0c7 100644 --- a/src/addons/messages/pages/discussions-35/discussions.page.ts +++ b/src/addons/messages/pages/discussions-35/discussions.page.ts @@ -80,17 +80,17 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy { AddonMessagesProvider.NEW_MESSAGE_EVENT, (data) => { if (data.userId && this.discussions) { - const discussion = this.discussions.find((disc) => disc.message!.user == data.userId); + const discussion = this.discussions.find((disc) => disc.message?.user === data.userId); if (discussion === undefined) { this.loaded = false; this.refreshData().finally(() => { this.loaded = true; }); - } else { - // An existing discussion has a new message, update the last message. - discussion.message!.message = data.message; - discussion.message!.timecreated = data.timecreated; + } else if (discussion.message) { + // An existing discussion has a new message, update the last message. + discussion.message.message = data.message; + discussion.message.timecreated = data.timecreated; } } }, @@ -102,10 +102,10 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy { AddonMessagesProvider.READ_CHANGED_EVENT, (data) => { if (data.userId && this.discussions) { - const discussion = this.discussions.find((disc) => disc.message!.user == data.userId); + const discussion = this.discussions.find((disc) => disc.message?.user === data.userId); if (discussion !== undefined) { - // A discussion has been read reset counter. + // A discussion has been read reset counter. discussion.unread = false; // Conversations changed, invalidate them and refresh unread counts. @@ -138,7 +138,7 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy { } /** - * Component loaded. + * @inheritdoc */ async ngOnInit(): Promise { this.route.queryParams.subscribe(async (params) => { @@ -150,9 +150,9 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy { await this.fetchData(); - if (!this.discussionUserId && this.discussions.length > 0 && CoreScreen.isTablet) { + if (!this.discussionUserId && this.discussions.length > 0 && CoreScreen.isTablet && this.discussions[0].message) { // Take first and load it. - await this.gotoDiscussion(this.discussions[0].message!.user); + await this.gotoDiscussion(this.discussions[0].message.user); } // Treat deep link now that the conversation route has been loaded if needed. @@ -287,7 +287,7 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy { } /** - * Component destroyed. + * @inheritdoc */ ngOnDestroy(): void { this.newMessagesObserver?.off(); diff --git a/src/addons/mod/assign/components/index/index.ts b/src/addons/mod/assign/components/index/index.ts index 5f4640723..411090e5c 100644 --- a/src/addons/mod/assign/components/index/index.ts +++ b/src/addons/mod/assign/components/index/index.ts @@ -14,6 +14,7 @@ import { Component, Optional, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { Params } from '@angular/router'; +import { CoreError } from '@classes/errors/error'; import { CoreSite } from '@classes/site'; import { CoreCourseModuleMainActivityComponent } from '@features/course/classes/main-activity-component'; import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; @@ -314,11 +315,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo /** * @inheritdoc */ - protected hasSyncSucceed(result?: AddonModAssignSyncResult): boolean { - if (!result) { - return false; - } - + protected hasSyncSucceed(result: AddonModAssignSyncResult): boolean { if (result.updated) { this.submissionComponent?.invalidateAndRefresh(false); } @@ -384,9 +381,9 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo /** * @inheritdoc */ - protected async sync(): Promise { + protected async sync(): Promise { if (!this.assign) { - return; + throw new CoreError('Cannot sync without a assign.'); } return AddonModAssignSync.syncAssign(this.assign.id); diff --git a/src/addons/mod/assign/services/assign-sync.ts b/src/addons/mod/assign/services/assign-sync.ts index 3a04878b1..1c5ced9cf 100644 --- a/src/addons/mod/assign/services/assign-sync.ts +++ b/src/addons/mod/assign/services/assign-sync.ts @@ -32,7 +32,7 @@ import { AddonModAssignSubmissionsDBRecordFormatted, AddonModAssignSubmissionsGradingDBRecordFormatted, } from './assign-offline'; -import { CoreSync } from '@services/sync'; +import { CoreSync, CoreSyncResult } from '@services/sync'; import { CoreCourseLogHelper } from '@features/course/services/log-helper'; import { CoreUtils } from '@services/utils/utils'; import { CoreNetwork } from '@services/network'; @@ -530,9 +530,7 @@ export const AddonModAssignSync = makeSingleton(AddonModAssignSyncProvider); /** * Data returned by a assign sync. */ -export type AddonModAssignSyncResult = { - warnings: string[]; // List of warnings. - updated: boolean; // Whether some data was sent to the server or offline data was updated. +export type AddonModAssignSyncResult = CoreSyncResult & { courseId?: number; // Course the assign belongs to (if known). gradesBlocked: number[]; // Whether some grade couldn't be synced because it was blocked. UserId fields of the blocked grade. }; diff --git a/src/addons/mod/choice/components/index/index.ts b/src/addons/mod/choice/components/index/index.ts index 91b25b58b..d4f7e039c 100644 --- a/src/addons/mod/choice/components/index/index.ts +++ b/src/addons/mod/choice/components/index/index.ts @@ -13,6 +13,7 @@ // limitations under the License. import { Component, Optional, OnInit } from '@angular/core'; +import { CoreError } from '@classes/errors/error'; import { CoreCourseModuleMainActivityComponent } from '@features/course/classes/main-activity-component'; import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; import { IonContent } from '@ionic/angular'; @@ -454,22 +455,14 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo } /** - * Performs the sync of the activity. - * - * @returns Promise resolved when done. + * @inheritdoc */ protected sync(): Promise { - return AddonModChoiceSync.syncChoice(this.choice!.id, this.userId); - } + if (!this.choice) { + throw new CoreError('Cannot sync without a choice.'); + } - /** - * Checks if sync has succeed from result sync data. - * - * @param result Data returned on the sync function. - * @returns Whether it succeed or not. - */ - protected hasSyncSucceed(result: AddonModChoiceSyncResult): boolean { - return result.updated; + return AddonModChoiceSync.syncChoice(this.choice.id, this.userId); } } diff --git a/src/addons/mod/choice/services/choice-sync.ts b/src/addons/mod/choice/services/choice-sync.ts index d0b74a684..c98d270a5 100644 --- a/src/addons/mod/choice/services/choice-sync.ts +++ b/src/addons/mod/choice/services/choice-sync.ts @@ -15,6 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreNetworkError } from '@classes/errors/network-error'; import { CoreCourseActivitySyncBaseProvider } from '@features/course/classes/activity-sync'; +import { CoreSyncResult } from '@services/sync'; import { CoreCourse } from '@features/course/services/course'; import { CoreCourseLogHelper } from '@features/course/services/log-helper'; import { CoreNetwork } from '@services/network'; @@ -217,10 +218,7 @@ export const AddonModChoiceSync = makeSingleton(AddonModChoiceSyncProvider); /** * Data returned by a choice sync. */ -export type AddonModChoiceSyncResult = { - warnings: string[]; // List of warnings. - updated: boolean; // Whether some data was sent to the server or offline data was updated. -}; +export type AddonModChoiceSyncResult = CoreSyncResult; /** * Data passed to AUTO_SYNCED event. diff --git a/src/addons/mod/choice/services/choice.ts b/src/addons/mod/choice/services/choice.ts index 459bbf3b2..57e002fea 100644 --- a/src/addons/mod/choice/services/choice.ts +++ b/src/addons/mod/choice/services/choice.ts @@ -77,15 +77,14 @@ export class AddonModChoiceProvider { choiceId: number, name: string, courseId: number, - responses?: number[], + responses: number[] = [], siteId?: string, ): Promise { siteId = siteId || CoreSites.getCurrentSiteId(); - responses = responses || []; // Convenience function to store a message to be synchronized later. const storeOffline = async (): Promise => { - await AddonModChoiceOffline.saveResponse(choiceId, name, courseId, responses!, true, siteId); + await AddonModChoiceOffline.saveResponse(choiceId, name, courseId, responses, true, siteId); return false; }; diff --git a/src/addons/mod/data/components/index/index.ts b/src/addons/mod/data/components/index/index.ts index 36948b327..2cdac62c5 100644 --- a/src/addons/mod/data/components/index/index.ts +++ b/src/addons/mod/data/components/index/index.ts @@ -519,24 +519,12 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp } /** - * Performs the sync of the activity. - * - * @returns Promise resolved when done. + * @inheritdoc */ protected sync(): Promise { return AddonModDataPrefetchHandler.sync(this.module, this.courseId); } - /** - * Checks if sync has succeed from result sync data. - * - * @param result Data returned on the sync function. - * @returns If suceed or not. - */ - protected hasSyncSucceed(result: AddonModDataSyncResult): boolean { - return result.updated; - } - /** * @inheritdoc */ diff --git a/src/addons/mod/data/services/data-sync.ts b/src/addons/mod/data/services/data-sync.ts index 5e76877bc..cd2e2ccea 100644 --- a/src/addons/mod/data/services/data-sync.ts +++ b/src/addons/mod/data/services/data-sync.ts @@ -24,7 +24,7 @@ import { CoreRatingSync } from '@features/rating/services/rating-sync'; import { CoreNetwork } from '@services/network'; import { CoreFileEntry } from '@services/file-helper'; import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; -import { CoreSync } from '@services/sync'; +import { CoreSync, CoreSyncResult } from '@services/sync'; import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; import { Translate, makeSingleton } from '@singletons'; @@ -477,10 +477,7 @@ export type AddonModDataSyncEntryResult = { /** * Data returned by a database sync. */ -export type AddonModDataSyncResult = { - warnings: string[]; // List of warnings. - updated: boolean; // Whether some data was sent to the server or offline data was updated. -}; +export type AddonModDataSyncResult = CoreSyncResult; export type AddonModDataAutoSyncData = { dataId: number; diff --git a/src/addons/mod/feedback/components/index/index.ts b/src/addons/mod/feedback/components/index/index.ts index c02c0fa53..2f877c17a 100644 --- a/src/addons/mod/feedback/components/index/index.ts +++ b/src/addons/mod/feedback/components/index/index.ts @@ -13,6 +13,7 @@ // limitations under the License. import { Component, Input, Optional, ViewChild, OnInit, OnDestroy } from '@angular/core'; +import { CoreError } from '@classes/errors/error'; import { CoreTabsComponent } from '@components/tabs/tabs'; import { CoreCourseModuleMainActivityComponent } from '@features/course/classes/main-activity-component'; import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; @@ -477,14 +478,11 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity * @inheritdoc */ protected sync(): Promise { - return AddonModFeedbackSync.syncFeedback(this.feedback!.id); - } + if (!this.feedback) { + throw new CoreError('Cannot sync without a feedback.'); + } - /** - * @inheritdoc - */ - protected hasSyncSucceed(result: AddonModFeedbackSyncResult): boolean { - return result.updated; + return AddonModFeedbackSync.syncFeedback(this.feedback.id); } /** diff --git a/src/addons/mod/feedback/services/feedback-sync.ts b/src/addons/mod/feedback/services/feedback-sync.ts index 5f84a7ceb..32f6791ef 100644 --- a/src/addons/mod/feedback/services/feedback-sync.ts +++ b/src/addons/mod/feedback/services/feedback-sync.ts @@ -20,7 +20,7 @@ import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/c import { CoreCourseLogHelper } from '@features/course/services/log-helper'; import { CoreNetwork } from '@services/network'; import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; -import { CoreSync } from '@services/sync'; +import { CoreSync, CoreSyncResult } from '@services/sync'; import { CoreUtils } from '@services/utils/utils'; import { makeSingleton, Translate } from '@singletons'; import { CoreEvents } from '@singletons/events'; @@ -292,10 +292,7 @@ export const AddonModFeedbackSync = makeSingleton(AddonModFeedbackSyncProvider); /** * Data returned by a feedback sync. */ -export type AddonModFeedbackSyncResult = { - warnings: string[]; // List of warnings. - updated: boolean; // Whether some data was sent to the server or offline data was updated. -}; +export type AddonModFeedbackSyncResult = CoreSyncResult; /** * Data passed to AUTO_SYNCED event. diff --git a/src/addons/mod/forum/components/index/index.ts b/src/addons/mod/forum/components/index/index.ts index 3dd733ad9..bb604e3f2 100644 --- a/src/addons/mod/forum/components/index/index.ts +++ b/src/addons/mod/forum/components/index/index.ts @@ -500,24 +500,12 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom } /** - * Performs the sync of the activity. - * - * @returns Promise resolved when done. + * @inheritdoc */ protected sync(): Promise { return AddonModForumPrefetchHandler.sync(this.module, this.courseId); } - /** - * Checks if sync has succeed from result sync data. - * - * @param result Data returned on the sync function. - * @returns Whether it succeed or not. - */ - protected hasSyncSucceed(result: AddonModForumSyncResult): boolean { - return result.updated; - } - /** * Compares sync event data with current data to check if refresh content is needed. * diff --git a/src/addons/mod/forum/services/forum-helper.ts b/src/addons/mod/forum/services/forum-helper.ts index 16f9f1cc8..68c86e7aa 100644 --- a/src/addons/mod/forum/services/forum-helper.ts +++ b/src/addons/mod/forum/services/forum-helper.ts @@ -62,7 +62,7 @@ export class AddonModForumHelperProvider { message: string, attachments?: CoreFileEntry[], options?: AddonModForumDiscussionOptions, - groupIds?: number[], + groupIds: number[] = [], timeCreated?: number, siteId?: string, ): Promise { @@ -76,7 +76,7 @@ export class AddonModForumHelperProvider { // Convenience function to store a message to be synchronized later. const storeOffline = async (): Promise => { // Multiple groups, the discussion is being posted to all groups. - const groupId = groupIds!.length > 1 ? AddonModForumProvider.ALL_GROUPS : groupIds![0]; + const groupId = groupIds.length > 1 ? AddonModForumProvider.ALL_GROUPS : groupIds[0]; if (offlineAttachments && options) { options.attachmentsid = offlineAttachments; @@ -182,7 +182,7 @@ export class AddonModForumHelperProvider { * @param siteId Site ID. If not defined, current site. * @returns Promise resolved with the object converted to Online. */ - convertOfflineReplyToOnline(offlineReply: AddonModForumOfflineReply, siteId?: string): Promise { + async convertOfflineReplyToOnline(offlineReply: AddonModForumOfflineReply, siteId?: string): Promise { const reply: AddonModForumPost = { id: -offlineReply.timecreated, discussionid: offlineReply.discussionid, @@ -236,11 +236,11 @@ export class AddonModForumHelperProvider { ), ); - return Promise.all(promises).then(() => { - reply.attachment = reply.attachments!.length > 0 ? 1 : 0; + await Promise.all(promises); - return reply; - }); + reply.attachment = reply.attachments?.length ? 1 : 0; + + return reply; } /** diff --git a/src/addons/mod/forum/services/forum-sync.ts b/src/addons/mod/forum/services/forum-sync.ts index 37e88fc29..b2324c12d 100644 --- a/src/addons/mod/forum/services/forum-sync.ts +++ b/src/addons/mod/forum/services/forum-sync.ts @@ -21,7 +21,7 @@ import { CoreRatingSync } from '@features/rating/services/rating-sync'; import { CoreNetwork } from '@services/network'; import { CoreGroups } from '@services/groups'; import { CoreSites } from '@services/sites'; -import { CoreSync } from '@services/sync'; +import { CoreSync, CoreSyncResult } from '@services/sync'; import { CoreUtils } from '@services/utils/utils'; import { makeSingleton, Translate } from '@singletons'; import { CoreEvents } from '@singletons/events'; @@ -221,7 +221,7 @@ export class AddonModForumSyncProvider extends CoreCourseActivitySyncBaseProvide }; // Sync offline logs. - const syncDiscussions = async (): Promise<{ warnings: string[]; updated: boolean }> => { + const syncDiscussions = async (): Promise => { await CoreUtils.ignoreErrors( CoreCourseLogHelper.syncActivity(AddonModForumProvider.COMPONENT, forumId, siteId), ); @@ -643,10 +643,7 @@ export const AddonModForumSync = makeSingleton(AddonModForumSyncProvider); /** * Result of forum sync. */ -export type AddonModForumSyncResult = { - updated: boolean; - warnings: string[]; -}; +export type AddonModForumSyncResult = CoreSyncResult; /** * Data passed to AUTO_SYNCED event. diff --git a/src/addons/mod/forum/services/handlers/prefetch.ts b/src/addons/mod/forum/services/handlers/prefetch.ts index b03910690..29124b40e 100644 --- a/src/addons/mod/forum/services/handlers/prefetch.ts +++ b/src/addons/mod/forum/services/handlers/prefetch.ts @@ -22,7 +22,7 @@ import { CoreCourse, CoreCourseAnyModuleData, CoreCourseCommonModWSOptions } fro import { CoreUser } from '@features/user/services/user'; import { CoreGroups, CoreGroupsProvider } from '@services/groups'; import { CoreUtils } from '@services/utils/utils'; -import { AddonModForumSync } from '../forum-sync'; +import { AddonModForumSync, AddonModForumSyncResult } from '../forum-sync'; import { makeSingleton } from '@singletons'; import { CoreCourses } from '@features/courses/services/courses'; @@ -341,11 +341,3 @@ export class AddonModForumPrefetchHandlerService extends CoreCourseActivityPrefe } export const AddonModForumPrefetchHandler = makeSingleton(AddonModForumPrefetchHandlerService); - -/** - * Data returned by a forum sync. - */ -export type AddonModForumSyncResult = { - warnings: string[]; // List of warnings. - updated: boolean; // Whether some data was sent to the server or offline data was updated. -}; diff --git a/src/addons/mod/glossary/components/index/index.ts b/src/addons/mod/glossary/components/index/index.ts index 5b22a4cd4..6b0acca1c 100644 --- a/src/addons/mod/glossary/components/index/index.ts +++ b/src/addons/mod/glossary/components/index/index.ts @@ -215,24 +215,12 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity } /** - * Performs the sync of the activity. - * - * @returns Promise resolved when done. + * @inheritdoc */ protected sync(): Promise { return AddonModGlossaryPrefetchHandler.sync(this.module, this.courseId); } - /** - * Checks if sync has succeed from result sync data. - * - * @param result Data returned on the sync function. - * @returns Whether it succeed or not. - */ - protected hasSyncSucceed(result: AddonModGlossarySyncResult): boolean { - return result.updated; - } - /** * Compares sync event data with current data to check if refresh content is needed. * diff --git a/src/addons/mod/glossary/services/glossary-sync.ts b/src/addons/mod/glossary/services/glossary-sync.ts index 8db74bcc1..0fc65ad13 100644 --- a/src/addons/mod/glossary/services/glossary-sync.ts +++ b/src/addons/mod/glossary/services/glossary-sync.ts @@ -21,7 +21,7 @@ import { CoreCourseLogHelper } from '@features/course/services/log-helper'; import { CoreRatingSync } from '@features/rating/services/rating-sync'; import { CoreNetwork } from '@services/network'; import { CoreSites } from '@services/sites'; -import { CoreSync } from '@services/sync'; +import { CoreSync, CoreSyncResult } from '@services/sync'; import { CoreUtils } from '@services/utils/utils'; import { makeSingleton, Translate } from '@singletons'; import { CoreEvents } from '@singletons/events'; @@ -344,10 +344,7 @@ export const AddonModGlossarySync = makeSingleton(AddonModGlossarySyncProvider); /** * Data returned by a glossary sync. */ -export type AddonModGlossarySyncResult = { - warnings: string[]; // List of warnings. - updated: boolean; // Whether some data was sent to the server or offline data was updated. -}; +export type AddonModGlossarySyncResult = CoreSyncResult; /** * Data passed to AUTO_SYNCED event. diff --git a/src/addons/mod/h5pactivity/services/h5pactivity-sync.ts b/src/addons/mod/h5pactivity/services/h5pactivity-sync.ts index 5eb33e384..7d9c3fb19 100644 --- a/src/addons/mod/h5pactivity/services/h5pactivity-sync.ts +++ b/src/addons/mod/h5pactivity/services/h5pactivity-sync.ts @@ -16,6 +16,7 @@ import { Injectable } from '@angular/core'; import { CoreNetworkError } from '@classes/errors/network-error'; import { CoreCourseActivitySyncBaseProvider } from '@features/course/classes/activity-sync'; +import { CoreSyncResult } from '@services/sync'; import { CoreCourseLogHelper } from '@features/course/services/log-helper'; import { CoreXAPIOffline } from '@features/xapi/services/offline'; import { CoreXAPI } from '@features/xapi/services/xapi'; @@ -197,10 +198,7 @@ export const AddonModH5PActivitySync = makeSingleton(AddonModH5PActivitySyncProv /** * Sync result. */ -export type AddonModH5PActivitySyncResult = { - updated: boolean; - warnings: string[]; -}; +export type AddonModH5PActivitySyncResult = CoreSyncResult; /** * Data passed to AUTO_SYNC event. diff --git a/src/addons/mod/lesson/components/index/index.ts b/src/addons/mod/lesson/components/index/index.ts index 5495057e2..bd66d2b49 100644 --- a/src/addons/mod/lesson/components/index/index.ts +++ b/src/addons/mod/lesson/components/index/index.ts @@ -47,6 +47,7 @@ import { } from '../../services/lesson-sync'; import { AddonModLessonModuleHandlerService } from '../../services/handlers/module'; import { CoreTime } from '@singletons/time'; +import { CoreError } from '@classes/errors/error'; /** * Component that displays a lesson entry page. @@ -270,10 +271,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo } /** - * Checks if sync has succeed from result sync data. - * - * @param result Data returned on the sync function. - * @returns If suceed or not. + * @inheritdoc */ protected hasSyncSucceed(result: AddonModLessonSyncResult): boolean { if (result.updated || this.dataSent) { @@ -637,12 +635,14 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo } /** - * Performs the sync of the activity. - * - * @returns Promise resolved when done. + * @inheritdoc */ protected async sync(): Promise { - const result = await AddonModLessonSync.syncLesson(this.lesson!.id, true); + if (!this.lesson) { + throw new CoreError('Cannot sync without a lesson.'); + } + + const result = await AddonModLessonSync.syncLesson(this.lesson.id, true); if (!result.updated && this.dataSent && this.isPrefetched()) { // The user sent data to server, but not in the sync process. Check if we need to fetch data. diff --git a/src/addons/mod/lesson/services/lesson-sync.ts b/src/addons/mod/lesson/services/lesson-sync.ts index 1a4241178..adf4eb126 100644 --- a/src/addons/mod/lesson/services/lesson-sync.ts +++ b/src/addons/mod/lesson/services/lesson-sync.ts @@ -21,7 +21,7 @@ import { CoreCourse } from '@features/course/services/course'; import { CoreCourseLogHelper } from '@features/course/services/log-helper'; import { CoreNetwork } from '@services/network'; import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; -import { CoreSync } from '@services/sync'; +import { CoreSync, CoreSyncResult } from '@services/sync'; import { CoreTimeUtils } from '@services/utils/time'; import { CoreUrlUtils } from '@services/utils/url'; import { CoreUtils } from '@services/utils/utils'; @@ -495,9 +495,7 @@ export const AddonModLessonSync = makeSingleton(AddonModLessonSyncProvider); /** * Data returned by a lesson sync. */ -export type AddonModLessonSyncResult = { - warnings: string[]; // List of warnings. - updated: boolean; // Whether some data was sent to the server or offline data was updated. +export type AddonModLessonSyncResult = CoreSyncResult & { courseId?: number; // Course the lesson belongs to (if known). }; diff --git a/src/addons/mod/quiz/components/index/index.ts b/src/addons/mod/quiz/components/index/index.ts index c4adff103..98b84d041 100644 --- a/src/addons/mod/quiz/components/index/index.ts +++ b/src/addons/mod/quiz/components/index/index.ts @@ -418,10 +418,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp } /** - * Checks if sync has succeed from result sync data. - * - * @param result Data returned on the sync function. - * @returns If suceed or not. + * @inheritdoc */ protected hasSyncSucceed(result: AddonModQuizSyncResult): boolean { if (result.attemptFinished) { @@ -553,9 +550,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp } /** - * Performs the sync of the activity. - * - * @returns Promise resolved when done. + * @inheritdoc */ protected async sync(): Promise { if (!this.candidateQuiz) { diff --git a/src/addons/mod/quiz/services/quiz-sync.ts b/src/addons/mod/quiz/services/quiz-sync.ts index 4b6a573ae..74871a22e 100644 --- a/src/addons/mod/quiz/services/quiz-sync.ts +++ b/src/addons/mod/quiz/services/quiz-sync.ts @@ -23,7 +23,7 @@ import { CoreQuestion, CoreQuestionQuestionParsed } from '@features/question/ser import { CoreQuestionDelegate } from '@features/question/services/question-delegate'; import { CoreNetwork } from '@services/network'; import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; -import { CoreSync } from '@services/sync'; +import { CoreSync, CoreSyncResult } from '@services/sync'; import { CoreUtils } from '@services/utils/utils'; import { makeSingleton, Translate } from '@singletons'; import { CoreEvents } from '@singletons/events'; @@ -482,10 +482,8 @@ export const AddonModQuizSync = makeSingleton(AddonModQuizSyncProvider); /** * Data returned by a quiz sync. */ -export type AddonModQuizSyncResult = { - warnings: string[]; // List of warnings. +export type AddonModQuizSyncResult = CoreSyncResult & { attemptFinished: boolean; // Whether an attempt was finished in the site due to the sync. - updated: boolean; }; /** diff --git a/src/addons/mod/quiz/services/quiz.ts b/src/addons/mod/quiz/services/quiz.ts index 4ee1a7711..c9693a201 100644 --- a/src/addons/mod/quiz/services/quiz.ts +++ b/src/addons/mod/quiz/services/quiz.ts @@ -99,7 +99,7 @@ export class AddonModQuizProvider { * @returns Grade to display. */ formatGrade(grade?: number | null, decimals?: number): string { - if (grade === undefined || grade == -1 || grade === null || isNaN(grade)) { + if (grade === undefined || grade === -1 || grade === null || isNaN(grade)) { return Translate.instant('addon.mod_quiz.notyetgraded'); } @@ -1800,7 +1800,7 @@ export class AddonModQuizProvider { ): string | undefined { let grade: number | undefined; - const rawGradeNum = typeof rawGrade == 'string' ? parseFloat(rawGrade) : rawGrade; + const rawGradeNum = typeof rawGrade === 'string' ? parseFloat(rawGrade) : rawGrade; if (rawGradeNum !== undefined && rawGradeNum !== null && !isNaN(rawGradeNum)) { if (quiz.sumgrades && quiz.sumgrades >= 0.000005) { grade = rawGradeNum * (quiz.grade ?? 0) / quiz.sumgrades; diff --git a/src/addons/mod/resource/components/index/index.ts b/src/addons/mod/resource/components/index/index.ts index 1fb0ecfeb..3871a2798 100644 --- a/src/addons/mod/resource/components/index/index.ts +++ b/src/addons/mod/resource/components/index/index.ts @@ -143,7 +143,7 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource this.displayDescription = false; this.warning = downloadResult.failed - ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error!) + ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error ?? '') : ''; return; diff --git a/src/addons/mod/resource/services/handlers/prefetch.ts b/src/addons/mod/resource/services/handlers/prefetch.ts index 3342cc474..93ab036a9 100644 --- a/src/addons/mod/resource/services/handlers/prefetch.ts +++ b/src/addons/mod/resource/services/handlers/prefetch.ts @@ -61,8 +61,8 @@ export class AddonModResourcePrefetchHandlerService extends CoreCourseResourcePr async downloadOrPrefetch(module: CoreCourseModuleData, courseId: number, prefetch?: boolean): Promise { let dirPath: string | undefined; - if (AddonModResourceHelper.isDisplayedInIframe(module)) { - dirPath = await CoreFilepool.getPackageDirPathByUrl(CoreSites.getCurrentSiteId(), module.url!); + if (AddonModResourceHelper.isDisplayedInIframe(module) && module.url !== undefined) { + dirPath = await CoreFilepool.getPackageDirPathByUrl(CoreSites.getCurrentSiteId(), module.url); } const promises: Promise[] = []; diff --git a/src/addons/mod/resource/services/resource-helper.ts b/src/addons/mod/resource/services/resource-helper.ts index f1ee994ef..11e75b041 100644 --- a/src/addons/mod/resource/services/resource-helper.ts +++ b/src/addons/mod/resource/services/resource-helper.ts @@ -62,7 +62,7 @@ export class AddonModResourceHelperProvider { * @returns Promise resolved with the iframe src. */ async getIframeSrc(module: CoreCourseModuleData): Promise { - if (!module.contents?.length) { + if (!module.contents?.length || module.url === undefined) { throw new CoreError('No contents available in module'); } @@ -74,7 +74,7 @@ export class AddonModResourceHelperProvider { } try { - const dirPath = await CoreFilepool.getPackageDirUrlByUrl(CoreSites.getCurrentSiteId(), module.url!); + const dirPath = await CoreFilepool.getPackageDirUrlByUrl(CoreSites.getCurrentSiteId(), module.url); // This URL is going to be injected in an iframe, we need trustAsResourceUrl to make it work in a browser. return CorePath.concatenatePaths(dirPath, mainFilePath); diff --git a/src/addons/mod/scorm/components/index/index.ts b/src/addons/mod/scorm/components/index/index.ts index 66ac6508c..90e36e1ed 100644 --- a/src/addons/mod/scorm/components/index/index.ts +++ b/src/addons/mod/scorm/components/index/index.ts @@ -14,6 +14,7 @@ import { CoreConstants } from '@/core/constants'; import { Component, Input, OnInit, Optional } from '@angular/core'; +import { CoreError } from '@classes/errors/error'; import { CoreCourseModuleMainActivityComponent } from '@features/course/classes/main-activity-component'; import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; import { CoreCourse } from '@features/course/services/course'; @@ -59,7 +60,10 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom moduleName = 'scorm'; scorm?: AddonModScormScorm; // The SCORM object. - currentOrganization: Partial = {}; // Selected organization. + currentOrganization: Partial & { identifier: string} = { + identifier: '', + }; // Selected organization. + startNewAttempt = false; errorMessage?: string; // Error message. syncTime?: string; // Last sync time. @@ -70,7 +74,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom percentage?: string; // Download/unzip percentage. showPercentage = false; // Whether to show the percentage. progressMessage?: string; // Message about download/unzip. - organizations?: AddonModScormOrganization[]; // List of organizations. + organizations: AddonModScormOrganization[] = []; // List of organizations. loadingToc = false; // Whether the TOC is being loaded. toc?: AddonModScormTOCScoWithIcon[]; // Table of contents (structure). accessInfo?: AddonModScormGetScormAccessInformationWSResponse; // Access information. @@ -128,7 +132,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom try { await AddonModScormPrefetchHandler.download(this.module, this.courseId, undefined, (data) => { - if (!data) { + if (!data || !this.scorm) { return; } @@ -137,8 +141,8 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom if (data.downloading) { // Downloading package. - if (this.scorm!.packagesize && data.progress) { - const percentageNumber = Number(data.progress.loaded / this.scorm!.packagesize) * 100; + if (this.scorm.packagesize && data.progress) { + const percentageNumber = Number(data.progress.loaded / this.scorm.packagesize) * 100; this.percentage = percentageNumber.toFixed(1); this.showPercentage = percentageNumber >= 0 && percentageNumber <= 100; } @@ -198,7 +202,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom ( this.accessInfo.canskipview && !this.accessInfo.canviewreport && (this.scorm.skipview ?? 0) >= AddonModScormProvider.SKIPVIEW_FIRST && - (this.scorm.skipview == AddonModScormProvider.SKIPVIEW_ALWAYS || this.lastAttempt == 0) + (this.scorm.skipview === AddonModScormProvider.SKIPVIEW_ALWAYS || this.lastAttempt === 0) ) ); } @@ -221,7 +225,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom this.lastAttempt = attempt.num; this.lastIsOffline = attempt.offline; - if (this.lastAttempt != this.attempts.lastAttempt.num) { + if (this.lastAttempt !== this.attempts.lastAttempt.num) { this.attemptToContinue = this.lastAttempt; } else { this.attemptToContinue = undefined; @@ -237,7 +241,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom this.gradeMethodReadable = AddonModScorm.getScormGradeMethod(scorm); this.attemptsLeft = AddonModScorm.countAttemptsLeft(scorm, this.attempts.lastAttempt.num); - if (scorm.forcenewattempt == AddonModScormProvider.SCORM_FORCEATTEMPT_ALWAYS || + if (scorm.forcenewattempt === AddonModScormProvider.SCORM_FORCEATTEMPT_ALWAYS || (scorm.forcenewattempt && !this.incomplete)) { this.startNewAttempt = true; } @@ -272,13 +276,9 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom protected async fetchStructure(scorm: AddonModScormScorm): Promise { this.organizations = await AddonModScorm.getOrganizations(scorm.id, { cmId: this.module.id }); - if (!this.currentOrganization.identifier) { + if (this.currentOrganization.identifier === '' && this.organizations[0]?.identifier) { // Load first organization (if any). - if (this.organizations.length) { - this.currentOrganization.identifier = this.organizations[0].identifier; - } else { - this.currentOrganization.identifier = ''; - } + this.currentOrganization.identifier = this.organizations[0].identifier; } return this.loadOrganizationToc(scorm, this.currentOrganization.identifier); @@ -297,7 +297,11 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom offline: boolean, attempts: Record, ): Promise { - const grade = await AddonModScorm.getAttemptGrade(this.scorm!, attempt, offline); + if (!this.scorm) { + return; + } + + const grade = await AddonModScorm.getAttemptGrade(this.scorm, attempt, offline); attempts[attempt] = { num: attempt, @@ -361,10 +365,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom } /** - * Checks if sync has succeed from result sync data. - * - * @param result Data returned on the sync function. - * @returns If suceed or not. + * @inheritdoc */ protected hasSyncSucceed(result: AddonModScormSyncResult): boolean { if (result.updated || this.dataSent) { @@ -374,7 +375,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom this.dataSent = false; - return true; + return result.updated; } /** @@ -438,8 +439,12 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom * Load a organization's TOC. */ async loadOrganization(): Promise { + if (!this.scorm) { + return; + } + try { - await this.loadOrganizationToc(this.scorm!, this.currentOrganization.identifier!); + await this.loadOrganizationToc(this.scorm, this.currentOrganization.identifier); } catch (error) { CoreDomUtils.showErrorModalDefault(error, this.fetchContentDefaultError, true); } @@ -453,7 +458,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom * @returns Promise resolved when done. */ protected async loadOrganizationToc(scorm: AddonModScormScorm, organizationId: string): Promise { - if (!scorm.displaycoursestructure) { + if (!scorm.displaycoursestructure || this.lastAttempt === undefined) { // TOC is not displayed, no need to load it. return; } @@ -461,18 +466,17 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom this.loadingToc = true; try { - this.toc = await AddonModScormHelper.getToc(scorm.id, this.lastAttempt!, this.incomplete, { + this.toc = await AddonModScormHelper.getToc(scorm.id, this.lastAttempt, this.incomplete, { organization: organizationId, offline: this.lastIsOffline, cmId: this.module.id, }); // Search organization title. - this.organizations!.forEach((org) => { - if (org.identifier == organizationId) { - this.currentOrganization.title = org.title; - } - }); + const organization = this.organizations.find((org) => org.identifier === organizationId); + if (organization) { + this.currentOrganization.title = organization.title; + } } finally { this.loadingToc = false; } @@ -486,20 +490,18 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom * @param scoId SCO that needs to be loaded when the SCORM is opened. If not defined, load first SCO. */ async open(event?: Event, preview: boolean = false, scoId?: number): Promise { - if (event) { - event.preventDefault(); - event.stopPropagation(); - } + event?.preventDefault(); + event?.stopPropagation(); - if (this.downloading) { + if (this.downloading || !this.scorm) { // Scope is being downloaded, abort. return; } - const isOutdated = this.currentStatus == CoreConstants.OUTDATED; - const scorm = this.scorm!; + const isOutdated = this.currentStatus === CoreConstants.OUTDATED; + const scorm = this.scorm; - if (!isOutdated && this.currentStatus != CoreConstants.NOT_DOWNLOADED) { + if (!isOutdated && this.currentStatus !== CoreConstants.NOT_DOWNLOADED) { // Already downloaded, open it. this.openScorm(scoId, preview); @@ -552,7 +554,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom this.dataSentObserver?.off(); this.dataSentObserver = CoreEvents.on(AddonModScormProvider.DATA_SENT_EVENT, (data) => { - if (data.scormId === this.scorm!.id) { + if (data.scormId === this.scorm?.id) { this.dataSent = true; if (this.module.completiondata && CoreCourse.isIncompleteAutomaticCompletion(this.module.completiondata)) { @@ -581,14 +583,14 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom */ protected async showStatus(status: string): Promise { - if (status == CoreConstants.OUTDATED && this.scorm) { + if (status === CoreConstants.OUTDATED && this.scorm) { // Only show the outdated message if the file should be downloaded. const download = await AddonModScorm.shouldDownloadMainFile(this.scorm, true); this.statusMessage = download ? 'addon.mod_scorm.scormstatusoutdated' : ''; - } else if (status == CoreConstants.NOT_DOWNLOADED) { + } else if (status === CoreConstants.NOT_DOWNLOADED) { this.statusMessage = 'addon.mod_scorm.scormstatusnotdownloaded'; - } else if (status == CoreConstants.DOWNLOADING) { + } else if (status === CoreConstants.DOWNLOADING) { if (!this.downloading) { // It's being downloaded right now but the view isn't tracking it. "Restore" the download. this.downloadScormPackage(); @@ -605,14 +607,18 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom * @returns Promise resolved when done. */ protected async sync(retries = 0): Promise { - if (CoreSync.isBlocked(AddonModScormProvider.COMPONENT, this.scorm!.id) && retries < 5) { + if (!this.scorm) { + throw new CoreError('Cannot sync without a scorm.'); + } + + if (CoreSync.isBlocked(AddonModScormProvider.COMPONENT, this.scorm.id) && retries < 5) { // Sync is currently blocked, this can happen when SCORM player is left. Retry in a bit. await CoreUtils.wait(400); return this.sync(retries + 1); } - const result = await AddonModScormSync.syncScorm(this.scorm!); + const result = await AddonModScormSync.syncScorm(this.scorm); if (!result.updated && this.dataSent) { // The user sent data to server, but not in the sync process. Check if we need to fetch data. diff --git a/src/addons/mod/scorm/services/scorm-sync.ts b/src/addons/mod/scorm/services/scorm-sync.ts index cfae16dbb..b55470e84 100644 --- a/src/addons/mod/scorm/services/scorm-sync.ts +++ b/src/addons/mod/scorm/services/scorm-sync.ts @@ -18,7 +18,7 @@ import { CoreCourseActivitySyncBaseProvider } from '@features/course/classes/act import { CoreCourse } from '@features/course/services/course'; import { CoreCourseLogHelper } from '@features/course/services/log-helper'; import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; -import { CoreSync } from '@services/sync'; +import { CoreSync, CoreSyncResult } from '@services/sync'; import { CoreUtils } from '@services/utils/utils'; import { makeSingleton, Translate } from '@singletons'; import { CoreEvents } from '@singletons/events'; @@ -841,18 +841,14 @@ export const AddonModScormSync = makeSingleton(AddonModScormSyncProvider); /** * Data returned by a SCORM sync. */ -export type AddonModScormSyncResult = { - warnings: string[]; // List of warnings. +export type AddonModScormSyncResult = CoreSyncResult & { attemptFinished: boolean; // Whether an attempt was finished in the site due to the sync, - updated: boolean; // Whether some data was sent to the site. }; /** * Auto sync event data. */ -export type AddonModScormAutoSyncEventData = { +export type AddonModScormAutoSyncEventData = CoreSyncResult & { scormId: number; attemptFinished: boolean; - warnings: string[]; - updated: boolean; }; diff --git a/src/addons/mod/survey/components/index/index.ts b/src/addons/mod/survey/components/index/index.ts index 7fa09627b..8fa968dda 100644 --- a/src/addons/mod/survey/components/index/index.ts +++ b/src/addons/mod/survey/components/index/index.ts @@ -13,6 +13,7 @@ // limitations under the License. import { Component, OnInit, Optional } from '@angular/core'; +import { CoreError } from '@classes/errors/error'; import { CoreIonLoadingElement } from '@classes/ion-loading'; import { CoreCourseModuleMainActivityComponent } from '@features/course/classes/main-activity-component'; import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; @@ -117,8 +118,8 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo if (sync) { // Try to synchronize the survey. - const answersSent = await this.syncActivity(showErrors); - if (answersSent) { + const updated = await this.syncActivity(showErrors); + if (updated) { // Answers were sent, update the survey. this.survey = await AddonModSurvey.getSurvey(this.courseId, this.module.id); } @@ -130,17 +131,18 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo : await AddonModSurveyOffline.hasAnswers(this.survey.id); if (!this.survey.surveydone && !this.hasOffline) { - await this.fetchQuestions(); + await this.fetchQuestions(this.survey.id); } } /** * Convenience function to get survey questions. * + * @param surveyId Survey Id. * @returns Promise resolved when done. */ - protected async fetchQuestions(): Promise { - const questions = await AddonModSurvey.getQuestions(this.survey!.id, { cmId: this.module.id }); + protected async fetchQuestions(surveyId: number): Promise { + const questions = await AddonModSurvey.getQuestions(surveyId, { cmId: this.module.id }); this.questions = AddonModSurveyHelper.formatQuestions(questions); @@ -183,6 +185,10 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo * Save options selected. */ async submit(): Promise { + if (!this.survey) { + return; + } + let modal: CoreIonLoadingElement | undefined; try { @@ -198,7 +204,7 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo }); } - const online = await AddonModSurvey.submitAnswers(this.survey!.id, this.survey!.name, this.courseId, answers); + const online = await AddonModSurvey.submitAnswers(this.survey.id, this.survey.name, this.courseId, answers); CoreEvents.trigger(CoreEvents.ACTIVITY_DATA_SENT, { module: this.moduleName }); @@ -231,22 +237,14 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo } /** - * Performs the sync of the activity. - * - * @returns Promise resolved when done. + * @inheritdoc */ - protected sync(): Promise { - return AddonModSurveySync.syncSurvey(this.survey!.id, this.currentUserId); - } + protected async sync(): Promise { + if (!this.survey) { + throw new CoreError('Cannot sync without a survey.'); + } - /** - * Checks if sync has succeed from result sync data. - * - * @param result Data returned on the sync function. - * @returns If suceed or not. - */ - protected hasSyncSucceed(result: AddonModSurveySyncResult): boolean { - return result.answersSent; + return AddonModSurveySync.syncSurvey(this.survey.id, this.currentUserId); } } diff --git a/src/addons/mod/survey/services/survey-sync.ts b/src/addons/mod/survey/services/survey-sync.ts index 455039f7b..6089e342a 100644 --- a/src/addons/mod/survey/services/survey-sync.ts +++ b/src/addons/mod/survey/services/survey-sync.ts @@ -15,6 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreNetworkError } from '@classes/errors/network-error'; import { CoreCourseActivitySyncBaseProvider } from '@features/course/classes/activity-sync'; +import { CoreSyncResult } from '@services/sync'; import { CoreCourse } from '@features/course/services/course'; import { CoreCourseLogHelper } from '@features/course/services/log-helper'; import { CoreNetwork } from '@services/network'; @@ -80,7 +81,7 @@ export class AddonModSurveySyncProvider extends CoreCourseActivitySyncBaseProvid ? this.syncSurvey(entry.surveyid, entry.userid, siteId) : this.syncSurveyIfNeeded(entry.surveyid, entry.userid, siteId)); - if (result && result.answersSent) { + if (result && result.updated) { // Sync successful, send event. CoreEvents.trigger(AddonModSurveySyncProvider.AUTO_SYNCED, { surveyId: entry.surveyid, @@ -150,7 +151,7 @@ export class AddonModSurveySyncProvider extends CoreCourseActivitySyncBaseProvid protected async performSyncSurvey(surveyId: number, userId: number, siteId: string): Promise { const result: AddonModSurveySyncResult = { warnings: [], - answersSent: false, + updated: false, }; // Sync offline logs. @@ -179,7 +180,7 @@ export class AddonModSurveySyncProvider extends CoreCourseActivitySyncBaseProvid try { await AddonModSurvey.submitAnswersOnline(surveyId, data.answers, siteId); - result.answersSent = true; + result.updated = true; // Answers sent, delete them. await AddonModSurveyOffline.deleteSurveyAnswers(surveyId, siteId, userId); @@ -190,7 +191,7 @@ export class AddonModSurveySyncProvider extends CoreCourseActivitySyncBaseProvid } // The WebService has thrown an error, this means that answers cannot be submitted. Delete them. - result.answersSent = true; + result.updated = true; await AddonModSurveyOffline.deleteSurveyAnswers(surveyId, siteId, userId); @@ -236,9 +237,7 @@ declare module '@singletons/events' { /** * Data returned by a assign sync. */ -export type AddonModSurveySyncResult = { - warnings: string[]; // List of warnings. - answersSent: boolean; // Whether some data was sent to the server or offline data was updated. +export type AddonModSurveySyncResult = CoreSyncResult & { courseId?: number; // Course the survey belongs to (if known). }; diff --git a/src/addons/mod/url/components/index/index.ts b/src/addons/mod/url/components/index/index.ts index 0d69f7f19..0739552df 100644 --- a/src/addons/mod/url/components/index/index.ts +++ b/src/addons/mod/url/components/index/index.ts @@ -189,7 +189,11 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo */ go(): void { this.logView(); - AddonModUrlHelper.open(this.url!); + if (!this.url) { + return; + } + + AddonModUrlHelper.open(this.url); } } diff --git a/src/addons/mod/wiki/components/index/index.ts b/src/addons/mod/wiki/components/index/index.ts index 333b921d6..3b724896a 100644 --- a/src/addons/mod/wiki/components/index/index.ts +++ b/src/addons/mod/wiki/components/index/index.ts @@ -707,12 +707,9 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp } /** - * Checks if sync has succeed from result sync data. - * - * @param result Data returned on the sync function. - * @returns If suceed or not. + * @inheritdoc */ - protected hasSyncSucceed(result: AddonModWikiSyncWikiResult | undefined): boolean { + protected hasSyncSucceed(result: AddonModWikiSyncWikiResult): boolean { if (!result) { return false; } @@ -843,13 +840,11 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp } /** - * Performs the sync of the activity. - * - * @returns Promise resolved when done. + * @inheritdoc */ - protected async sync(): Promise { + protected async sync(): Promise { if (!this.wiki) { - return; + throw new CoreError('Cannot sync without a wiki.'); } return AddonModWikiSync.syncWiki(this.wiki.id, this.courseId, this.wiki.coursemodule); diff --git a/src/addons/mod/wiki/pages/edit/edit.ts b/src/addons/mod/wiki/pages/edit/edit.ts index 7e9500256..6c2c1b96a 100644 --- a/src/addons/mod/wiki/pages/edit/edit.ts +++ b/src/addons/mod/wiki/pages/edit/edit.ts @@ -45,8 +45,8 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { cmId?: number; // Course module ID. courseId?: number; // Course the wiki belongs to. title?: string; // Title to display. - pageForm?: FormGroup; // The form group. - contentControl?: FormControl; // The FormControl for the page content. + pageForm: FormGroup; // The form group. + contentControl: FormControl; // The FormControl for the page content. canEditTitle = false; // Whether title can be edited. loaded = false; // Whether the data has been loaded. component = AddonModWikiProvider.COMPONENT; // Component to link the files to. @@ -71,7 +71,10 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { constructor( protected formBuilder: FormBuilder, - ) { } + ) { + this.contentControl = this.formBuilder.control(''); + this.pageForm = this.formBuilder.group({}); + } /** * @inheritdoc @@ -96,10 +99,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { this.blockId = AddonModWikiSync.getSubwikiBlockId(this.subwikiId, this.wikiId, this.userId, this.groupId); // Create the form group and its controls. - this.contentControl = this.formBuilder.control(''); - this.pageForm = this.formBuilder.group({ - title: pageTitle, - }); + this.pageForm.addControl('title', this.formBuilder.control(pageTitle)); this.pageForm.addControl('text', this.contentControl); // Block the wiki so it cannot be synced. @@ -121,7 +121,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { if (success && !this.isDestroyed) { // Block the subwiki now that we have blockId for sure. const newBlockId = AddonModWikiSync.getSubwikiBlockId(this.subwikiId, this.wikiId, this.userId, this.groupId); - if (newBlockId != this.blockId) { + if (newBlockId !== this.blockId) { CoreSync.unblockOperation(this.component, this.blockId); this.blockId = newBlockId; CoreSync.blockOperation(this.component, this.blockId); @@ -143,7 +143,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { try { // Wait for sync to be over (if any). - const syncResult = await AddonModWikiSync.waitForSync(this.blockId!); + const syncResult = this.blockId ? await AddonModWikiSync.waitForSync(this.blockId) : undefined; if (this.pageId) { // Editing a page that already exists. @@ -154,7 +154,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { // Get page contents to obtain title and editing permission const pageContents = await AddonModWiki.getPageContents(this.pageId, { cmId: this.cmId }); - this.pageForm!.controls.title.setValue(pageContents.title); // Set the title in the form group. + this.pageForm.controls.title.setValue(pageContents.title); // Set the title in the form group. this.wikiId = pageContents.wikiid; this.subwikiId = pageContents.subwikiid; this.title = Translate.instant('addon.mod_wiki.editingpage', { $a: pageContents.title }); @@ -177,7 +177,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { // Get the original page contents, treating file URLs if needed. const content = CoreTextUtils.replacePluginfileUrls(editContents.content || '', this.subwikiFiles); - this.contentControl!.setValue(content); + this.contentControl.setValue(content); this.originalContent = content; this.version = editContents.version; @@ -188,7 +188,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { }, AddonModWikiProvider.RENEW_LOCK_TIME); } } else { - const pageTitle = this.pageForm!.controls.title.value; + const pageTitle = this.pageForm.controls.title.value; this.editing = false; canEdit = !!this.blockId; // If no blockId, the user cannot edit the page. @@ -222,7 +222,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { if (page) { // Load offline content. - this.contentControl!.setValue(page.cachedcontent); + this.contentControl.setValue(page.cachedcontent); this.originalContent = page.cachedcontent; this.editOffline = true; } else { @@ -286,13 +286,17 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { * @param title Page title. */ protected goToPage(title: string): void { + if (!this.wikiId) { + return; + } + // Not the firstpage. AddonModWiki.setEditedPageData({ cmId: this.cmId, courseId: this.courseId, pageId: this.pageId, pageTitle: title, - wikiId: this.wikiId!, + wikiId: this.wikiId, subwikiId: this.subwikiId, userId: this.userId, groupId: this.groupId, @@ -307,7 +311,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { * @returns Whether data has changed. */ protected hasDataChanged(): boolean { - const values = this.pageForm!.value; + const values = this.pageForm.value; return !(this.originalContent == values.text || (!this.editing && !values.text && !values.title)); } @@ -348,7 +352,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { * @returns Promise resolved when done. */ async save(): Promise { - const values = this.pageForm!.value; + const values = this.pageForm.value; const title = values.title; let text = values.text; @@ -358,14 +362,14 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { text = CoreTextUtils.formatHtmlLines(text); try { - if (this.editing) { + if (this.editing && this.pageId) { // Edit existing page. - await AddonModWiki.editPage(this.pageId!, text, this.section); + await AddonModWiki.editPage(this.pageId, text, this.section); CoreForms.triggerFormSubmittedEvent(this.formElement, true, CoreSites.getCurrentSiteId()); // Invalidate page since it changed. - await AddonModWiki.invalidatePage(this.pageId!); + await AddonModWiki.invalidatePage(this.pageId); return this.goToPage(title); } @@ -451,7 +455,11 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { * Renew lock and control versions. */ protected async renewLock(): Promise { - const response = await AddonModWiki.getPageForEditing(this.pageId!, this.section, true); + if (!this.pageId) { + return; + } + + const response = await AddonModWiki.getPageForEditing(this.pageId, this.section, true); if (response.version && this.version != response.version) { this.wrongVersionLock = true; diff --git a/src/addons/mod/wiki/services/handlers/create-link.ts b/src/addons/mod/wiki/services/handlers/create-link.ts index 544808831..c25c37723 100644 --- a/src/addons/mod/wiki/services/handlers/create-link.ts +++ b/src/addons/mod/wiki/services/handlers/create-link.ts @@ -103,6 +103,10 @@ export class AddonModWikiCreateLinkHandlerService extends CoreContentLinksHandle try { const route = CoreNavigator.getCurrentRoute({ pageComponent: AddonModWikiIndexPage }); + if (!route) { + // Current view isn't wiki index. + return; + } const subwikiId = parseInt(params.swid, 10); const wikiId = parseInt(params.wid, 10); let path = AddonModWikiModuleHandlerService.PAGE_NAME; @@ -112,7 +116,7 @@ export class AddonModWikiCreateLinkHandlerService extends CoreContentLinksHandle if (isSameWiki) { // User is seeing the wiki, we can get the module from the wiki params. - path = path + `/${route!.snapshot.params.courseId}/${route!.snapshot.params.cmId}/edit`; + path = path + `/${route.snapshot.params.courseId}/${route.snapshot.params.cmId}/edit`; } else if (wikiId) { // The URL specifies which wiki it belongs to. Get the module. const module = await CoreCourse.getModuleBasicInfoByInstance( diff --git a/src/addons/mod/wiki/services/wiki-sync.ts b/src/addons/mod/wiki/services/wiki-sync.ts index 906c62365..0ba10b7f2 100644 --- a/src/addons/mod/wiki/services/wiki-sync.ts +++ b/src/addons/mod/wiki/services/wiki-sync.ts @@ -19,7 +19,7 @@ import { CoreCourseLogHelper } from '@features/course/services/log-helper'; import { CoreNetwork } from '@services/network'; import { CoreGroups } from '@services/groups'; import { CoreSites } from '@services/sites'; -import { CoreSync } from '@services/sync'; +import { CoreSync, CoreSyncResult } from '@services/sync'; import { CoreUtils } from '@services/utils/utils'; import { makeSingleton, Translate } from '@singletons'; import { CoreEvents } from '@singletons/events'; @@ -345,9 +345,7 @@ export const AddonModWikiSync = makeSingleton(AddonModWikiSyncProvider); /** * Data returned by a subwiki sync. */ -export type AddonModWikiSyncSubwikiResult = { - warnings: string[]; // List of warnings. - updated: boolean; // Whether data was updated in the site. +export type AddonModWikiSyncSubwikiResult = CoreSyncResult & { created: AddonModWikiCreatedPage[]; // List of created pages. discarded: AddonModWikiDiscardedPage[]; // List of discarded pages. }; @@ -355,9 +353,7 @@ export type AddonModWikiSyncSubwikiResult = { /** * Data returned by a wiki sync. */ -export type AddonModWikiSyncWikiResult = { - warnings: string[]; // List of warnings. - updated: boolean; // Whether data was updated in the site. +export type AddonModWikiSyncWikiResult = CoreSyncResult & { subwikis: { [subwikiId: number]: { // List of subwikis. created: AddonModWikiCreatedPage[]; diff --git a/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts b/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts index e749b2da6..e2ea393c4 100644 --- a/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts +++ b/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts @@ -162,6 +162,10 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe cmId: this.workshop.coursemodule, }); + if (!this.data.assessment.form) { + return; + } + if (this.edit) { try { const offlineAssessment = await AddonModWorkshopOffline.getAssessment(this.workshop.id, this.assessmentId); @@ -176,7 +180,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe } // Override assessment plugins values. - this.data.assessment.form!.current = AddonModWorkshop.parseFields( + this.data.assessment.form.current = AddonModWorkshop.parseFields( CoreUtils.objectToArrayOfObjects(offlineData, 'name', 'value'), ); @@ -221,7 +225,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe try { this.data.selectedValues = await AddonWorkshopAssessmentStrategyDelegate.getOriginalValues( this.strategy, - this.data.assessment.form!, + this.data.assessment.form, this.workshop.id, ); } finally { @@ -245,7 +249,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe * @returns True if data has changed. */ hasDataChanged(): boolean { - if (!this.assessmentStrategyLoaded) { + if (!this.assessmentStrategyLoaded || !this.workshop.strategy) { return false; } @@ -269,7 +273,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe } return AddonWorkshopAssessmentStrategyDelegate.hasDataChanged( - this.workshop.strategy!, + this.workshop.strategy, this.originalData.selectedValues, this.data.selectedValues, ); @@ -281,6 +285,10 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe * @returns Promise resolved when done, rejected if assessment could not be saved. */ async saveAssessment(): Promise { + if (!this.data.assessment?.form) { + return; + } + const files = CoreFileSession.getFiles( AddonModWorkshopProvider.COMPONENT, this.workshop.id + '_' + this.assessmentId, @@ -328,7 +336,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe this.workshop, this.data.selectedValues, text, - this.data.assessment!.form!, + this.data.assessment.form, attachmentsId, ); } catch (errors) { diff --git a/src/addons/mod/workshop/components/index/index.ts b/src/addons/mod/workshop/components/index/index.ts index 035c4ebfe..cfed3a329 100644 --- a/src/addons/mod/workshop/components/index/index.ts +++ b/src/addons/mod/workshop/components/index/index.ts @@ -14,6 +14,7 @@ import { Component, Input, OnDestroy, OnInit, Optional } from '@angular/core'; import { Params } from '@angular/router'; +import { CoreError } from '@classes/errors/error'; import { CoreCourseModuleMainActivityComponent } from '@features/course/classes/main-activity-component'; import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; import { IonContent } from '@ionic/angular'; @@ -264,7 +265,12 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity * @returns Resolved when done. */ async gotoSubmissionsPage(page: number): Promise { - const report = await AddonModWorkshop.getGradesReport(this.workshop!.id, { + if (!this.workshop) { + return; + } + const workshop = this.workshop; + + const report = await AddonModWorkshop.getGradesReport(workshop.id, { groupId: this.group, page, cmId: this.module.id, @@ -284,7 +290,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity await Promise.all(grades.map(async (grade) => { const submission: AddonModWorkshopSubmissionDataWithOfflineData = { id: grade.submissionid, - workshopid: this.workshop!.id, + workshopid: workshop.id, example: false, authorid: grade.userid, timecreated: grade.submissionmodified, @@ -303,7 +309,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity reviewerof: this.parseReviewer(grade.reviewerof), }; - if (this.workshop!.phase == AddonModWorkshopPhase.PHASE_ASSESSMENT) { + if (workshop.phase == AddonModWorkshopPhase.PHASE_ASSESSMENT) { submission.reviewedbydone = grade.reviewedby?.reduce((a, b) => a + (b.grade ? 1 : 0), 0) || 0; submission.reviewerofdone = grade.reviewerof?.reduce((a, b) => a + (b.grade ? 1 : 0), 0) || 0; submission.reviewedbycount = grade.reviewedby?.length || 0; @@ -313,7 +319,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity const offlineData = await AddonModWorkshopHelper.applyOfflineData(submission, this.offlineSubmissions); if (offlineData !== undefined) { - this.grades!.push(offlineData); + this.grades.push(offlineData); } })); } @@ -358,8 +364,12 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity * Go to submit page. */ gotoSubmit(): void { - if (this.canSubmit && ((this.access!.creatingsubmissionallowed && !this.submission) || - (this.access!.modifyingsubmissionallowed && this.submission))) { + if (!this.canSubmit || !this.access) { + return; + } + + if ((this.access.creatingsubmissionallowed && !this.submission) || + (this.access.modifyingsubmissionallowed && this.submission)) { const params: Params = { module: this.module, access: this.access, @@ -378,20 +388,22 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity * View Phase info. */ async viewPhaseInfo(): Promise { - if (this.phases) { - const modalData = await CoreDomUtils.openModal({ - component: AddonModWorkshopPhaseInfoComponent, - componentProps: { - phases: CoreUtils.objectToArray(this.phases), - workshopPhase: this.workshop!.phase, - externalUrl: this.module.url, - showSubmit: this.showSubmit, - }, - }); + if (!this.phases || !this.workshop) { + return; + } - if (modalData === true) { - this.gotoSubmit(); - } + const modalData = await CoreDomUtils.openModal({ + component: AddonModWorkshopPhaseInfoComponent, + componentProps: { + phases: CoreUtils.objectToArray(this.phases), + workshopPhase: this.workshop.phase, + externalUrl: this.module.url, + showSubmit: this.showSubmit, + }, + }); + + if (modalData === true) { + this.gotoSubmit(); } } @@ -413,26 +425,32 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity * @returns Promise resolved when done. */ protected async setPhaseInfo(): Promise { + if (!this.phases || !this.workshop || !this.access) { + return; + } + this.submission = undefined; this.canAssess = false; this.assessments = []; this.userGrades = undefined; this.publishedSubmissions = []; + const workshop = this.workshop; + this.canSubmit = AddonModWorkshopHelper.canSubmit( - this.workshop!, - this.access!, - this.phases![AddonModWorkshopPhase.PHASE_SUBMISSION].tasks, + this.workshop, + this.access, + this.phases[AddonModWorkshopPhase.PHASE_SUBMISSION].tasks, ); this.showSubmit = this.canSubmit && - ((this.access!.creatingsubmissionallowed && !this.submission) || - (this.access!.modifyingsubmissionallowed && !!this.submission)); + ((this.access.creatingsubmissionallowed && !this.submission) || + (this.access.modifyingsubmissionallowed && !!this.submission)); const promises: Promise[] = []; if (this.canSubmit) { - promises.push(AddonModWorkshopHelper.getUserSubmission(this.workshop!.id, { cmId: this.module.id }) + promises.push(AddonModWorkshopHelper.getUserSubmission(this.workshop.id, { cmId: this.module.id }) .then(async (submission) => { this.submission = await AddonModWorkshopHelper.applyOfflineData(submission, this.offlineSubmissions); @@ -440,27 +458,27 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity })); } - if (this.access!.canviewallsubmissions && this.workshop!.phase >= AddonModWorkshopPhase.PHASE_SUBMISSION) { + if (this.access.canviewallsubmissions && this.workshop.phase >= AddonModWorkshopPhase.PHASE_SUBMISSION) { promises.push(this.gotoSubmissionsPage(this.page)); } let assessPromise = Promise.resolve(); - if (this.workshop!.phase >= AddonModWorkshopPhase.PHASE_ASSESSMENT) { - this.canAssess = AddonModWorkshopHelper.canAssess(this.workshop!, this.access!); + if (this.workshop.phase >= AddonModWorkshopPhase.PHASE_ASSESSMENT) { + this.canAssess = AddonModWorkshopHelper.canAssess(this.workshop, this.access); if (this.canAssess) { - assessPromise = AddonModWorkshopHelper.getReviewerAssessments(this.workshop!.id, { + assessPromise = AddonModWorkshopHelper.getReviewerAssessments(this.workshop.id, { cmId: this.module.id, }).then(async (assessments) => { await Promise.all(assessments.map(async (assessment) => { - assessment.strategy = this.workshop!.strategy; + assessment.strategy = workshop.strategy; if (!this.hasOffline) { return; } try { - const offlineAssessment = await AddonModWorkshopOffline.getAssessment(this.workshop!.id, assessment.id); + const offlineAssessment = await AddonModWorkshopOffline.getAssessment(workshop.id, assessment.id); assessment.offline = true; assessment.timemodified = Math.floor(offlineAssessment.timemodified / 1000); @@ -477,27 +495,23 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity } } - if (this.workshop!.phase == AddonModWorkshopPhase.PHASE_CLOSED) { - promises.push(AddonModWorkshop.getGrades(this.workshop!.id, { cmId: this.module.id }).then((grades) => { + if (this.workshop.phase === AddonModWorkshopPhase.PHASE_CLOSED) { + promises.push(AddonModWorkshop.getGrades(this.workshop.id, { cmId: this.module.id }).then((grades) => { this.userGrades = grades.submissionlongstrgrade || grades.assessmentlongstrgrade ? grades : undefined; return; })); - if (this.access!.canviewpublishedsubmissions) { + if (this.access.canviewpublishedsubmissions) { promises.push(assessPromise.then(async () => { const submissions: AddonModWorkshopSubmissionDataWithOfflineData[] = - await AddonModWorkshop.getSubmissions(this.workshop!.id, { cmId: this.module.id }); + await AddonModWorkshop.getSubmissions(workshop.id, { cmId: this.module.id }); this.publishedSubmissions = submissions.filter((submission) => { if (submission.published) { - submission.reviewedby = []; - - this.assessments.forEach((assessment) => { - if (assessment.submissionid == submission.id) { - submission.reviewedby!.push(AddonModWorkshopHelper.realGradeValue(this.workshop!, assessment)); - } - }); + submission.reviewedby = + this.assessments.filter((assessment) => assessment.submissionid === submission.id) + .map((assessment => AddonModWorkshopHelper.realGradeValue(workshop, assessment))); return true; } @@ -514,22 +528,14 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity } /** - * Performs the sync of the activity. - * - * @returns Promise resolved when done. + * @inheritdoc */ protected sync(): Promise { - return AddonModWorkshopSync.syncWorkshop(this.workshop!.id); - } + if (!this.workshop) { + throw new CoreError('Cannot sync without a workshop.'); + } - /** - * Checks if sync has succeed from result sync data. - * - * @param result Data returned on the sync function. - * @returns If suceed or not. - */ - protected hasSyncSucceed(result: AddonModWorkshopSyncResult): boolean { - return result.updated; + return AddonModWorkshopSync.syncWorkshop(this.workshop.id); } /** diff --git a/src/addons/mod/workshop/pages/edit-submission/edit-submission.ts b/src/addons/mod/workshop/pages/edit-submission/edit-submission.ts index 0878cdbce..d83fd9d4c 100644 --- a/src/addons/mod/workshop/pages/edit-submission/edit-submission.ts +++ b/src/addons/mod/workshop/pages/edit-submission/edit-submission.ts @@ -385,11 +385,14 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy, Ca ); newSubmissionId = false; } else { + if (!submissionId) { + throw new CoreError('Submission cannot be updated without a submissionId'); + } // Try to send it to server. // Don't allow offline if there are attachments since they were uploaded fine. newSubmissionId = await AddonModWorkshop.updateSubmission( this.workshopId, - submissionId!, + submissionId, this.courseId, inputData.title, inputData.content, diff --git a/src/addons/mod/workshop/pages/submission/submission.ts b/src/addons/mod/workshop/pages/submission/submission.ts index 6534c0cbd..711c20fb5 100644 --- a/src/addons/mod/workshop/pages/submission/submission.ts +++ b/src/addons/mod/workshop/pages/submission/submission.ts @@ -254,7 +254,7 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea return; })); - } else if (this.currentUserId == this.userId && this.assessmentId) { + } else if (this.currentUserId === this.userId && this.assessmentId) { // Get new data, different that came from stateParams. promises.push(AddonModWorkshop.getAssessment(this.workshopId, this.assessmentId, { cmId: this.module.id, @@ -268,7 +268,7 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea return; })); - } else if (this.workshop.phase == AddonModWorkshopPhase.PHASE_CLOSED && this.userId == this.currentUserId) { + } else if (this.workshop.phase === AddonModWorkshopPhase.PHASE_CLOSED && this.userId === this.currentUserId) { const assessments = await AddonModWorkshop.getSubmissionAssessments(this.workshopId, this.submissionId, { cmId: this.module.id, }); @@ -276,7 +276,7 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea this.submissionInfo.reviewedby = assessments.map((assessment) => this.parseAssessment(assessment)); } - if (this.canAddFeedback || this.workshop.phase == AddonModWorkshopPhase.PHASE_CLOSED) { + if (this.canAddFeedback || this.workshop.phase === AddonModWorkshopPhase.PHASE_CLOSED) { this.evaluate = { published: this.submission.published, text: this.submission.feedbackauthor || '', @@ -284,54 +284,13 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea } if (this.canAddFeedback) { - if (!this.isDestroyed) { // Block the workshop. CoreSync.blockOperation(this.component, this.workshopId); } - const defaultGrade = Translate.instant('addon.mod_workshop.notoverridden'); - - promises.push(CoreGradesHelper.makeGradesMenu(this.workshop.grade || 0, undefined, defaultGrade, -1) - .then(async (grades) => { - this.evaluationGrades = grades; - - this.evaluate!.grade = { - label: CoreGradesHelper.getGradeLabelFromValue(grades, this.submissionInfo.gradeover) || - defaultGrade, - value: this.submissionInfo.gradeover || -1, - }; - - try { - const offlineSubmission = - await AddonModWorkshopOffline.getEvaluateSubmission(this.workshopId, this.submissionId); - - this.hasOffline = true; - this.evaluate!.published = offlineSubmission.published; - this.evaluate!.text = offlineSubmission.feedbacktext; - this.evaluate!.grade = { - label: CoreGradesHelper.getGradeLabelFromValue( - grades, - parseInt(offlineSubmission.gradeover, 10), - ) || defaultGrade, - value: offlineSubmission.gradeover || -1, - }; - } catch { - // Ignore errors. - this.hasOffline = false; - } finally { - this.originalEvaluation.published = this.evaluate!.published; - this.originalEvaluation.text = this.evaluate!.text; - this.originalEvaluation.grade = this.evaluate!.grade.value; - - this.feedbackForm.controls['published'].setValue(this.evaluate!.published); - this.feedbackForm.controls['grade'].setValue(this.evaluate!.grade.value); - this.feedbackForm.controls['text'].setValue(this.evaluate!.text); - } - - return; - })); - } else if (this.workshop.phase == AddonModWorkshopPhase.PHASE_CLOSED && this.submission.gradeoverby && + promises.push(this.fillEvaluationsGrades()); + } else if (this.workshop.phase === AddonModWorkshopPhase.PHASE_CLOSED && this.submission.gradeoverby && this.evaluate && this.evaluate.text) { promises.push(CoreUser.getProfile(this.submission.gradeoverby, this.courseId, true).then((profile) => { this.evaluateByProfile = profile; @@ -362,6 +321,49 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea } } + /** + * Fill evaluation grade info. + */ + protected async fillEvaluationsGrades(): Promise { + const defaultGrade = Translate.instant('addon.mod_workshop.notoverridden'); + + this.evaluationGrades = await CoreGradesHelper.makeGradesMenu(this.workshop.grade || 0, undefined, defaultGrade, -1); + + if (!this.evaluate) { + // Should not happen. + return; + } + + this.evaluate.grade = { + label: CoreGradesHelper.getGradeLabelFromValue(this.evaluationGrades, this.submissionInfo.gradeover) || defaultGrade, + value: this.submissionInfo.gradeover || -1, + }; + + try { + const offlineSubmission = await AddonModWorkshopOffline.getEvaluateSubmission(this.workshopId, this.submissionId); + + this.hasOffline = true; + this.evaluate.published = offlineSubmission.published; + this.evaluate.text = offlineSubmission.feedbacktext; + this.evaluate.grade = { + label: CoreGradesHelper.getGradeLabelFromValue(this.evaluationGrades, parseInt(offlineSubmission.gradeover, 10)) || + defaultGrade, + value: offlineSubmission.gradeover || -1, + }; + } catch { + // Ignore errors. + this.hasOffline = false; + } finally { + this.originalEvaluation.published = this.evaluate.published; + this.originalEvaluation.text = this.evaluate.text; + this.originalEvaluation.grade = this.evaluate.grade.value; + + this.feedbackForm.controls['published'].setValue(this.evaluate.published); + this.feedbackForm.controls['grade'].setValue(this.evaluate.grade.value); + this.feedbackForm.controls['text'].setValue(this.evaluate.text); + } + } + /** * Parse assessment to be shown. * diff --git a/src/addons/mod/workshop/services/handlers/prefetch.ts b/src/addons/mod/workshop/services/handlers/prefetch.ts index 1b62c5f55..2da580e75 100644 --- a/src/addons/mod/workshop/services/handlers/prefetch.ts +++ b/src/addons/mod/workshop/services/handlers/prefetch.ts @@ -71,7 +71,7 @@ export class AddonModWorkshopPrefetchHandlerService extends CoreCourseActivityPr ): Promise<{ workshop?: AddonModWorkshopData; groups: CoreGroup[]; files: CoreWSFile[]}> { let groups: CoreGroup[] = []; let files: CoreWSFile[] = []; - let workshop: AddonModWorkshopData | undefined; + let workshop: AddonModWorkshopData; let access: AddonModWorkshopGetWorkshopAccessInformationWSResponse | undefined; const modOptions = { @@ -79,11 +79,25 @@ export class AddonModWorkshopPrefetchHandlerService extends CoreCourseActivityPr ...options, // Include all options. }; - try { - const site = await CoreSites.getSite(options.siteId); - const userId = site.getUserId(); - const workshop = await AddonModWorkshop.getWorkshop(courseId, module.id, options); + const site = await CoreSites.getSite(options.siteId); + options.siteId = options.siteId ?? site.getId(); + const userId = site.getUserId(); + try { + workshop = await AddonModWorkshop.getWorkshop(courseId, module.id, options); + } catch (error) { + if (options.omitFail) { + // Any error, return the info we have. + return { + groups: [], + files: [], + }; + } + + throw error; + } + + try { files = this.getIntroFilesFromInstance(module, workshop); files = files.concat(workshop.instructauthorsfiles || []).concat(workshop.instructreviewersfiles || []); @@ -124,7 +138,7 @@ export class AddonModWorkshopPrefetchHandlerService extends CoreCourseActivityPr await Promise.all(submissions.map(async (submission) => { files = files.concat(submission.contentfiles || []).concat(submission.attachmentfiles || []); - const assessments = await AddonModWorkshop.getSubmissionAssessments(workshop!.id, submission.id, { + const assessments = await AddonModWorkshop.getSubmissionAssessments(workshop.id, submission.id, { cmId: module.id, }); @@ -133,9 +147,9 @@ export class AddonModWorkshopPrefetchHandlerService extends CoreCourseActivityPr .concat(assessment.feedbackcontentfiles); }); - if (workshop!.phase >= AddonModWorkshopPhase.PHASE_ASSESSMENT && canAssess) { + if (workshop.phase >= AddonModWorkshopPhase.PHASE_ASSESSMENT && canAssess) { await Promise.all(assessments.map((assessment) => - AddonModWorkshopHelper.getReviewerAssessmentById(workshop!.id, assessment.id))); + AddonModWorkshopHelper.getReviewerAssessmentById(workshop.id, assessment.id))); } })); @@ -267,7 +281,12 @@ export class AddonModWorkshopPrefetchHandlerService extends CoreCourseActivityPr // Prefetch the workshop data. const info = await this.getWorkshopInfoHelper(module, courseId, commonOptions); - const workshop = info.workshop!; + if (!info.workshop) { + // It would throw an exception so it would not happen. + return; + } + + const workshop = info.workshop; const promises: Promise[] = []; const assessmentIds: number[] = []; diff --git a/src/addons/mod/workshop/services/workshop-helper.ts b/src/addons/mod/workshop/services/workshop-helper.ts index aefb60346..f73414544 100644 --- a/src/addons/mod/workshop/services/workshop-helper.ts +++ b/src/addons/mod/workshop/services/workshop-helper.ts @@ -181,7 +181,7 @@ export class AddonModWorkshopHelperProvider { assessment = await AddonModWorkshop.getAssessment(workshopId, assessmentId, options); } catch (error) { const assessments = await AddonModWorkshop.getReviewerAssessments(workshopId, options); - assessment = assessments.find((assessment_1) => assessment_1.id == assessmentId); + assessment = assessments.find((ass) => ass.id === assessmentId); if (!assessment) { throw error; @@ -266,9 +266,7 @@ export class AddonModWorkshopHelperProvider { * Upload or store some files for a submission, depending if the user is offline or not. * * @param workshopId Workshop ID. - * @param submissionId If not editing, it will refer to timecreated. * @param files List of files. - * @param editing If the submission is being edited or added otherwise. * @param offline True if files sould be stored for offline, false to upload them. * @param siteId Site ID. If not defined, current site. * @returns Promise resolved if success. @@ -451,30 +449,26 @@ export class AddonModWorkshopHelperProvider { * @returns Promise resolved with the files. */ async applyOfflineData( - submission?: AddonModWorkshopSubmissionDataWithOfflineData, + submission: AddonModWorkshopSubmissionDataWithOfflineData = { + id: 0, + workshopid: 0, + title: '', + content: '', + timemodified: 0, + example: false, + authorid: 0, + timecreated: 0, + contenttrust: 0, + attachment: 0, + published: false, + late: 0, + }, actions: AddonModWorkshopOfflineSubmission[] = [], ): Promise { - if (actions.length == 0) { + if (actions.length === 0) { return submission; } - if (submission === undefined) { - submission = { - id: 0, - workshopid: 0, - title: '', - content: '', - timemodified: 0, - example: false, - authorid: 0, - timecreated: 0, - contenttrust: 0, - attachment: 0, - published: false, - late: 0, - }; - } - let attachmentsId: CoreFileUploaderStoreFilesResult | undefined; const workshopId = actions[0].workshopid; @@ -482,17 +476,17 @@ export class AddonModWorkshopHelperProvider { switch (action.action) { case AddonModWorkshopAction.ADD: case AddonModWorkshopAction.UPDATE: - submission!.title = action.title; - submission!.content = action.content; - submission!.title = action.title; - submission!.courseid = action.courseid; - submission!.submissionmodified = action.timemodified / 1000; - submission!.offline = true; + submission.title = action.title; + submission.content = action.content; + submission.title = action.title; + submission.courseid = action.courseid; + submission.submissionmodified = action.timemodified / 1000; + submission.offline = true; attachmentsId = action.attachmentsid as CoreFileUploaderStoreFilesResult; break; case AddonModWorkshopAction.DELETE: - submission!.deleted = true; - submission!.submissionmodified = action.timemodified / 1000; + submission.deleted = true; + submission.submissionmodified = action.timemodified / 1000; break; default: } @@ -534,7 +528,8 @@ export class AddonModWorkshopHelperProvider { } const data = - (await AddonWorkshopAssessmentStrategyDelegate.prepareAssessmentData(workshop.strategy!, selectedValues, form)) || {}; + (await AddonWorkshopAssessmentStrategyDelegate.prepareAssessmentData(workshop.strategy ?? '', selectedValues, form)) || + {}; data.feedbackauthor = feedbackText; data.feedbackauthorattachmentsid = attachmentsId; data.nodims = form.dimenssionscount; @@ -551,16 +546,16 @@ export class AddonModWorkshopHelperProvider { * @returns Real grade formatted. */ protected realGradeValueHelper(value?: number | string, max = 0, decimals = 0): string | undefined { - if (typeof value == 'string') { + if (typeof value === 'string') { // Already treated. return value; } - if (value == null || value === undefined) { + if (value === null || value === undefined) { return undefined; } - if (max == 0) { + if (max === 0) { return '0'; } diff --git a/src/addons/mod/workshop/services/workshop-sync.ts b/src/addons/mod/workshop/services/workshop-sync.ts index 695afa417..3e345020b 100644 --- a/src/addons/mod/workshop/services/workshop-sync.ts +++ b/src/addons/mod/workshop/services/workshop-sync.ts @@ -20,7 +20,7 @@ import { CoreFileUploaderStoreFilesResult } from '@features/fileuploader/service import { CoreNetwork } from '@services/network'; import { CoreFileEntry } from '@services/file-helper'; import { CoreSites } from '@services/sites'; -import { CoreSync } from '@services/sync'; +import { CoreSync, CoreSyncResult } from '@services/sync'; import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; import { Translate, makeSingleton } from '@singletons'; @@ -639,7 +639,4 @@ export type AddonModWorkshopAutoSyncData = { warnings: string[]; }; -export type AddonModWorkshopSyncResult = { - warnings: string[]; - updated: boolean; -}; +export type AddonModWorkshopSyncResult = CoreSyncResult; diff --git a/src/addons/mod/workshop/services/workshop.ts b/src/addons/mod/workshop/services/workshop.ts index 1db0888c5..26c14809d 100644 --- a/src/addons/mod/workshop/services/workshop.ts +++ b/src/addons/mod/workshop/services/workshop.ts @@ -610,13 +610,16 @@ export class AddonModWorkshopProvider { grades: AddonModWorkshopGradesData[], options: AddonModWorkshopGetGradesReportOptions = {}, ): Promise { + options.page = options.page ?? 0; + options.perPage = options.perPage ?? AddonModWorkshopProvider.PER_PAGE; + const report = await this.getGradesReport(workshopId, options); Array.prototype.push.apply(grades, report.grades); - const canLoadMore = ((options.page! + 1) * options.perPage!) < report.totalcount; + const canLoadMore = ((options.page + 1) * options.perPage) < report.totalcount; if (canLoadMore) { - options.page!++; + options.page++; return this.fetchGradeReportsRecursive(workshopId, grades, options); } @@ -778,7 +781,11 @@ export class AddonModWorkshopProvider { // Other errors ocurring. CoreWS.throwOnFailedStatus(response, 'Add submission failed'); - return response.submissionid!; + if (!response.submissionid) { + throw new CoreError('Add submission failed, no submission id was returned'); + } + + return response.submissionid; } /** diff --git a/src/addons/qtype/calculated/component/addon-qtype-calculated.html b/src/addons/qtype/calculated/component/addon-qtype-calculated.html index ee44ea280..2e2a5ee1d 100644 --- a/src/addons/qtype/calculated/component/addon-qtype-calculated.html +++ b/src/addons/qtype/calculated/component/addon-qtype-calculated.html @@ -1,57 +1,57 @@ - + - - + - + {{ 'addon.mod_quiz.answercolon' | translate }}
- + - + - +
- +
- +
-