From ba89f9cb307a3f4dd99d77f1c7599687993c0591 Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Thu, 20 Dec 2018 16:46:34 +0000 Subject: [PATCH] MOBILE-2800 course: Wait for format before opening course --- src/core/course/providers/helper.ts | 43 ++++++++++++++++++- .../course-list-item/course-list-item.ts | 6 +-- .../course-progress/course-progress.ts | 6 +-- src/core/courses/lang/en.json | 1 + src/core/siteplugins/providers/helper.ts | 10 +++-- src/core/siteplugins/providers/siteplugins.ts | 39 ++++++++++++++++- 6 files changed, 94 insertions(+), 11 deletions(-) diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index 09435d2a7..3c1dcdb48 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -36,6 +36,8 @@ import { CoreLoginHelperProvider } from '@core/login/providers/helper'; import { CoreConstants } from '@core/constants'; import { CoreSite } from '@classes/site'; import * as moment from 'moment'; +import { CoreSitePluginsProvider } from '@core/siteplugins/providers/siteplugins'; +import { CoreCourseFormatDelegate } from '@core/course/providers/format-delegate'; /** * Prefetch info of a module. @@ -123,7 +125,8 @@ export class CoreCourseHelperProvider { private courseOptionsDelegate: CoreCourseOptionsDelegate, private siteHomeProvider: CoreSiteHomeProvider, private eventsProvider: CoreEventsProvider, private fileHelper: CoreFileHelperProvider, private appProvider: CoreAppProvider, private fileProvider: CoreFileProvider, private injector: Injector, - private coursesProvider: CoreCoursesProvider, private courseOffline: CoreCourseOfflineProvider) { } + private coursesProvider: CoreCoursesProvider, private courseOffline: CoreCourseOfflineProvider, + private courseFormatDelegate: CoreCourseFormatDelegate, private sitePluginsProvider: CoreSitePluginsProvider) { } /** * This function treats every module on the sections provided to load the handler data, treat completion @@ -1309,4 +1312,42 @@ export class CoreCourseHelperProvider { return (typeof section.availabilityinfo != 'undefined' && section.availabilityinfo != '') || section.summary != '' || (section.modules && section.modules.length > 0); } + + /** + * Wait for any course format plugin to load, and open the course page. + * + * If the plugin's promise is resolved, the course page will be opened. If it is rejected, they will see an error. + * If the promise for the plugin is still in progress when the user tries to open the course, a loader + * will be displayed until it is complete, before the course page is opened. If the promise is already complete, + * they will see the result immediately. + * + * @param {NavController} navCtrl The nav controller to use. + * @param {any} course Course to open + */ + openCourse(navCtrl: NavController, course: any): void { + if (this.sitePluginsProvider.sitePluginPromiseExists('format_' + course.format)) { + // This course uses a custom format plugin, wait for the format plugin to finish loading. + const loading = this.domUtils.showModalLoading(); + this.sitePluginsProvider.sitePluginLoaded('format_' + course.format).then(() => { + // The format loaded successfully, but the handlers wont be registered until all site plugins have loaded. + if (this.sitePluginsProvider.sitePluginsFinishedLoading) { + loading.dismiss(); + this.courseFormatDelegate.openCourse(navCtrl, course); + } else { + const observer = this.eventsProvider.on(CoreEventsProvider.SITE_PLUGINS_LOADED, () => { + loading.dismiss(); + this.courseFormatDelegate.openCourse(navCtrl, course); + observer && observer.off(); + }); + } + }).catch(() => { + // The site plugin failed to load. The user needs to restart the app to try loading it again. + loading.dismiss(); + this.domUtils.showErrorModal('core.courses.errorloadplugins', true); + }); + } else { + // No custom format plugin. We don't need to wait for anything. + this.courseFormatDelegate.openCourse(navCtrl, course); + } + } } diff --git a/src/core/courses/components/course-list-item/course-list-item.ts b/src/core/courses/components/course-list-item/course-list-item.ts index d2f1efad6..c11f17f69 100644 --- a/src/core/courses/components/course-list-item/course-list-item.ts +++ b/src/core/courses/components/course-list-item/course-list-item.ts @@ -16,7 +16,7 @@ import { Component, Input, OnInit, Optional } from '@angular/core'; import { NavController } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreCoursesProvider } from '../../providers/courses'; -import { CoreCourseFormatDelegate } from '@core/course/providers/format-delegate'; +import { CoreCourseHelperProvider } from '@core/course/providers/helper'; /** * This directive is meant to display an item for a list of courses. @@ -33,7 +33,7 @@ export class CoreCoursesCourseListItemComponent implements OnInit { @Input() course: any; // The course to render. constructor(@Optional() private navCtrl: NavController, private translate: TranslateService, - private coursesProvider: CoreCoursesProvider, private courseFormatDelegate: CoreCourseFormatDelegate) { + private coursesProvider: CoreCoursesProvider, private courseHelper: CoreCourseHelperProvider) { } /** @@ -82,7 +82,7 @@ export class CoreCoursesCourseListItemComponent implements OnInit { */ openCourse(course: any): void { if (course.isEnrolled) { - this.courseFormatDelegate.openCourse(this.navCtrl, course); + this.courseHelper.openCourse(this.navCtrl, course); } else { this.navCtrl.push('CoreCoursesCoursePreviewPage', {course: course}); } diff --git a/src/core/courses/components/course-progress/course-progress.ts b/src/core/courses/components/course-progress/course-progress.ts index 7f9e40487..f91778759 100644 --- a/src/core/courses/components/course-progress/course-progress.ts +++ b/src/core/courses/components/course-progress/course-progress.ts @@ -19,7 +19,6 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreUserProvider } from '@core/user/providers/user'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; -import { CoreCourseFormatDelegate } from '@core/course/providers/format-delegate'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseHelperProvider } from '@core/course/providers/helper'; import { CoreCoursesCourseOptionsMenuComponent } from '../course-options-menu/course-options-menu'; @@ -55,7 +54,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy { protected siteUpdatedObserver; constructor(@Optional() private navCtrl: NavController, private courseHelper: CoreCourseHelperProvider, - private courseFormatDelegate: CoreCourseFormatDelegate, private domUtils: CoreDomUtilsProvider, + private domUtils: CoreDomUtilsProvider, private courseProvider: CoreCourseProvider, private eventsProvider: CoreEventsProvider, private sitesProvider: CoreSitesProvider, private coursesProvider: CoreCoursesProvider, private popoverCtrl: PopoverController, private userProvider: CoreUserProvider) { } @@ -64,6 +63,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy { * Component being initialized. */ ngOnInit(): void { + this.downloadCourseEnabled = !this.coursesProvider.isDownloadCourseDisabledInSite(); if (this.downloadCourseEnabled) { @@ -132,7 +132,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy { * @param {any} course The course to open. */ openCourse(course: any): void { - this.courseFormatDelegate.openCourse(this.navCtrl, course); + this.courseHelper.openCourse(this.navCtrl, course); } /** diff --git a/src/core/courses/lang/en.json b/src/core/courses/lang/en.json index e2b1fca0c..9409f2ee0 100644 --- a/src/core/courses/lang/en.json +++ b/src/core/courses/lang/en.json @@ -11,6 +11,7 @@ "enrolme": "Enrol me", "errorloadcategories": "An error occurred while loading categories.", "errorloadcourses": "An error occurred while loading courses.", + "errorloadplugins": "The plugins required by this course could not be loaded correctly. Please restart the app to try again.", "errorsearching": "An error occurred while searching.", "errorselfenrol": "An error occurred while self enrolling.", "filtermycourses": "Filter my courses", diff --git a/src/core/siteplugins/providers/helper.ts b/src/core/siteplugins/providers/helper.ts index a65bc96e1..c041de987 100644 --- a/src/core/siteplugins/providers/helper.ts +++ b/src/core/siteplugins/providers/helper.ts @@ -102,7 +102,9 @@ export class CoreSitePluginsHelperProvider { // Plugins fetched, check that site hasn't changed. if (data.siteId == this.sitesProvider.getCurrentSiteId() && plugins.length) { // Site is still the same. Load the plugins and trigger the event. - this.loadSitePlugins(plugins).then(() => { + this.loadSitePlugins(plugins).catch((error) => { + this.logger.error(error); + }).finally(() => { eventsProvider.trigger(CoreEventsProvider.SITE_PLUGINS_LOADED, {}, data.siteId); }); @@ -367,7 +369,9 @@ export class CoreSitePluginsHelperProvider { const promises = []; plugins.forEach((plugin) => { - promises.push(this.loadSitePlugin(plugin)); + const pluginPromise = this.loadSitePlugin(plugin); + promises.push(pluginPromise); + this.sitePluginsProvider.registerSitePluginPromise(plugin.component, pluginPromise); }); return this.utils.allPromises(promises); @@ -510,7 +514,7 @@ export class CoreSitePluginsHelperProvider { } }); }).catch((err) => { - this.logger.error('Error executing init method', handlerSchema.init, err); + return Promise.reject('Error executing init method ' + handlerSchema.init + ': ' + err.message); }); } diff --git a/src/core/siteplugins/providers/siteplugins.ts b/src/core/siteplugins/providers/siteplugins.ts index d8d5a4ab1..43fd975c7 100644 --- a/src/core/siteplugins/providers/siteplugins.ts +++ b/src/core/siteplugins/providers/siteplugins.ts @@ -24,6 +24,7 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreConfigConstants } from '../../../configconstants'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; +import { CoreEventsProvider } from '@providers/events'; /** * Handler of a site plugin. @@ -65,13 +66,19 @@ export class CoreSitePluginsProvider { protected logger; protected sitePlugins: {[name: string]: CoreSitePluginsHandler} = {}; // Site plugins registered. + protected sitePluginPromises: {[name: string]: Promise} = {}; // Promises of loading plugins. hasSitePluginsLoaded = false; + sitePluginsFinishedLoading = false; constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, private langProvider: CoreLangProvider, private appProvider: CoreAppProvider, private platform: Platform, private filepoolProvider: CoreFilepoolProvider, private coursesProvider: CoreCoursesProvider, - private textUtils: CoreTextUtilsProvider) { + private textUtils: CoreTextUtilsProvider, private eventsProvider: CoreEventsProvider) { this.logger = logger.getInstance('CoreUserProvider'); + const observer = this.eventsProvider.on(CoreEventsProvider.SITE_PLUGINS_LOADED, () => { + this.sitePluginsFinishedLoading = true; + observer && observer.off(); + }); } /** @@ -513,4 +520,34 @@ export class CoreSitePluginsProvider { setSitePluginHandler(name: string, handler: CoreSitePluginsHandler): void { this.sitePlugins[name] = handler; } + + /** + * Store the promise for a plugin that is being initialised. + * + * @param {String} component + * @param {Promise} promise + */ + registerSitePluginPromise(component: string, promise: Promise): void { + this.sitePluginPromises[component] = promise; + } + + /** + * Is a plugin being initialised for the specified component? + * + * @param {String} component + * @return {boolean} + */ + sitePluginPromiseExists(component: string): boolean { + return this.sitePluginPromises.hasOwnProperty(component); + } + + /** + * Get the promise for a plugin that is being initialised. + * + * @param {String} component + * @return {Promise} + */ + sitePluginLoaded(component: string): Promise { + return this.sitePluginPromises[component]; + } }