Merge pull request #1455 from dpalou/MOBILE-2472

MOBILE-2472 course: Fix spinner shown forever when changing format
main
Juan Leyva 2018-08-21 17:50:58 +01:00 committed by GitHub
commit 1369e9f738
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 121 additions and 36 deletions

View File

@ -67,6 +67,7 @@ export class CoreDynamicComponent implements OnInit, OnChanges, DoCheck {
container: ViewContainerRef; container: ViewContainerRef;
protected logger: any; protected logger: any;
protected differ: any; // To detect changes in the data input. protected differ: any; // To detect changes in the data input.
protected lastComponent: any;
constructor(logger: CoreLoggerProvider, protected factoryResolver: ComponentFactoryResolver, differs: KeyValueDiffers, constructor(logger: CoreLoggerProvider, protected factoryResolver: ComponentFactoryResolver, differs: KeyValueDiffers,
@Optional() protected navCtrl: NavController, protected cdr: ChangeDetectorRef, protected element: ElementRef, @Optional() protected navCtrl: NavController, protected cdr: ChangeDetectorRef, protected element: ElementRef,
@ -87,7 +88,13 @@ export class CoreDynamicComponent implements OnInit, OnChanges, DoCheck {
* Detect changes on input properties. * Detect changes on input properties.
*/ */
ngOnChanges(changes: { [name: string]: SimpleChange }): void { ngOnChanges(changes: { [name: string]: SimpleChange }): void {
if (!this.instance && changes.component) {
if (changes.component && !this.component) {
// Component not set, destroy the instance if any.
this.lastComponent = undefined;
this.instance = undefined;
this.container && this.container.clear();
} else if (changes.component && (!this.instance || this.component != this.lastComponent)) {
this.createComponent(); this.createComponent();
} }
} }
@ -127,6 +134,8 @@ export class CoreDynamicComponent implements OnInit, OnChanges, DoCheck {
* @return {boolean} Whether the component was successfully created. * @return {boolean} Whether the component was successfully created.
*/ */
protected createComponent(): boolean { protected createComponent(): boolean {
this.lastComponent = this.component;
if (!this.component || !this.container) { if (!this.component || !this.container) {
// No component to instantiate or container doesn't exist right now. // No component to instantiate or container doesn't exist right now.
return false; return false;

View File

@ -50,8 +50,8 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
protected eventsProvider: CoreEventsProvider; protected eventsProvider: CoreEventsProvider;
protected modulePrefetchDelegate: CoreCourseModulePrefetchDelegate; protected modulePrefetchDelegate: CoreCourseModulePrefetchDelegate;
constructor(injector: Injector, protected content?: Content) { constructor(injector: Injector, protected content?: Content, loggerName: string = 'CoreCourseModuleMainResourceComponent') {
super(injector); super(injector, loggerName);
this.sitesProvider = injector.get(CoreSitesProvider); this.sitesProvider = injector.get(CoreSitesProvider);
this.courseProvider = injector.get(CoreCourseProvider); this.courseProvider = injector.get(CoreCourseProvider);
@ -120,10 +120,28 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
* @return {Promise<any>} Resolved when done. * @return {Promise<any>} Resolved when done.
*/ */
protected refreshContent(sync: boolean = false, showErrors: boolean = false): Promise<any> { protected refreshContent(sync: boolean = false, showErrors: boolean = false): Promise<any> {
if (!this.module) {
// This can happen if course format changes from single activity to weekly/topics.
return Promise.resolve();
}
this.refreshIcon = 'spinner'; this.refreshIcon = 'spinner';
this.syncIcon = 'spinner'; this.syncIcon = 'spinner';
return this.invalidateContent().catch(() => { // Wrap the call in a try/catch so the workflow isn't interrupted if an error occurs.
// E.g. when changing course format we cannot know when will this.module become undefined, so it could cause errors.
let promise;
try {
promise = this.invalidateContent();
} catch (ex) {
// An error ocurred in the function, log the error and just resolve the promise so the workflow continues.
this.logger.error(ex);
promise = Promise.resolve();
}
return promise.catch(() => {
// Ignore errors. // Ignore errors.
}).then(() => { }).then(() => {
return this.loadContent(true, sync, showErrors); return this.loadContent(true, sync, showErrors);
@ -191,7 +209,25 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
protected loadContent(refresh?: boolean, sync: boolean = false, showErrors: boolean = false): Promise<any> { protected loadContent(refresh?: boolean, sync: boolean = false, showErrors: boolean = false): Promise<any> {
this.isOnline = this.appProvider.isOnline(); this.isOnline = this.appProvider.isOnline();
return this.fetchContent(refresh, sync, showErrors).catch((error) => { if (!this.module) {
// This can happen if course format changes from single activity to weekly/topics.
return Promise.resolve();
}
// Wrap the call in a try/catch so the workflow isn't interrupted if an error occurs.
// E.g. when changing course format we cannot know when will this.module become undefined, so it could cause errors.
let promise;
try {
promise = this.fetchContent(refresh, sync, showErrors);
} catch (ex) {
// An error ocurred in the function, log the error and just resolve the promise so the workflow continues.
this.logger.error(ex);
promise = Promise.resolve();
}
return promise.catch((error) => {
if (!refresh) { if (!refresh) {
// Some call failed, retry without using cache since it might be a new activity. // Some call failed, retry without using cache since it might be a new activity.
return this.refreshContent(sync); return this.refreshContent(sync);

View File

@ -14,6 +14,7 @@
import { OnInit, OnDestroy, Input, Output, EventEmitter, Injector } from '@angular/core'; import { OnInit, OnDestroy, Input, Output, EventEmitter, Injector } from '@angular/core';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreCourseHelperProvider } from '@core/course/providers/helper'; import { CoreCourseHelperProvider } from '@core/course/providers/helper';
@ -54,7 +55,9 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
protected moduleDelegate: CoreCourseModuleDelegate; protected moduleDelegate: CoreCourseModuleDelegate;
protected courseSectionPage: CoreCourseSectionPage; protected courseSectionPage: CoreCourseSectionPage;
constructor(injector: Injector) { protected logger;
constructor(injector: Injector, loggerName: string = 'CoreCourseModuleMainResourceComponent') {
this.textUtils = injector.get(CoreTextUtilsProvider); this.textUtils = injector.get(CoreTextUtilsProvider);
this.courseHelper = injector.get(CoreCourseHelperProvider); this.courseHelper = injector.get(CoreCourseHelperProvider);
this.translate = injector.get(TranslateService); this.translate = injector.get(TranslateService);
@ -62,6 +65,9 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
this.moduleDelegate = injector.get(CoreCourseModuleDelegate); this.moduleDelegate = injector.get(CoreCourseModuleDelegate);
this.courseSectionPage = injector.get(CoreCourseSectionPage, null); this.courseSectionPage = injector.get(CoreCourseSectionPage, null);
this.dataRetrieved = new EventEmitter(); this.dataRetrieved = new EventEmitter();
const loggerProvider = injector.get(CoreLoggerProvider);
this.logger = loggerProvider.getInstance(loggerName);
} }
/** /**
@ -84,7 +90,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
* @return {Promise<any>} Promise resolved when done. * @return {Promise<any>} Promise resolved when done.
*/ */
doRefresh(refresher?: any, done?: () => void, showErrors: boolean = false): Promise<any> { doRefresh(refresher?: any, done?: () => void, showErrors: boolean = false): Promise<any> {
if (this.loaded) { if (this.loaded && this.module) {
/* If it's a single activity course and the refresher is displayed within the component, /* If it's a single activity course and the refresher is displayed within the component,
call doRefresh on the section page to refresh the course data. */ call doRefresh on the section page to refresh the course data. */
let promise; let promise;
@ -113,9 +119,27 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
* @return {Promise<any>} Resolved when done. * @return {Promise<any>} Resolved when done.
*/ */
protected refreshContent(sync: boolean = false, showErrors: boolean = false): Promise<any> { protected refreshContent(sync: boolean = false, showErrors: boolean = false): Promise<any> {
if (!this.module) {
// This can happen if course format changes from single activity to weekly/topics.
return Promise.resolve();
}
this.refreshIcon = 'spinner'; this.refreshIcon = 'spinner';
return this.invalidateContent().catch(() => { // Wrap the call in a try/catch so the workflow isn't interrupted if an error occurs.
// E.g. when changing course format we cannot know when will this.module become undefined, so it could cause errors.
let promise;
try {
promise = this.invalidateContent();
} catch (ex) {
// An error ocurred in the function, log the error and just resolve the promise so the workflow continues.
this.logger.error(ex);
promise = Promise.resolve();
}
return promise.catch(() => {
// Ignore errors. // Ignore errors.
}).then(() => { }).then(() => {
return this.loadContent(true); return this.loadContent(true);
@ -150,6 +174,24 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
* @return {Promise<any>} Promise resolved when done. * @return {Promise<any>} Promise resolved when done.
*/ */
protected loadContent(refresh?: boolean): Promise<any> { protected loadContent(refresh?: boolean): Promise<any> {
if (!this.module) {
// This can happen if course format changes from single activity to weekly/topics.
return Promise.resolve();
}
// Wrap the call in a try/catch so the workflow isn't interrupted if an error occurs.
// E.g. when changing course format we cannot know when will this.module become undefined, so it could cause errors.
let promise;
try {
promise = this.fetchContent(refresh);
} catch (ex) {
// An error ocurred in the function, log the error and just resolve the promise so the workflow continues.
this.logger.error(ex);
promise = Promise.resolve();
}
return this.fetchContent(refresh).catch((error) => { return this.fetchContent(refresh).catch((error) => {
// Error getting data, fail. // Error getting data, fail.
this.domUtils.showErrorModalDefault(error, this.fetchContentDefaultError, true); this.domUtils.showErrorModalDefault(error, this.fetchContentDefaultError, true);

View File

@ -68,6 +68,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
loaded: boolean; loaded: boolean;
protected sectionStatusObserver; protected sectionStatusObserver;
protected lastCourseFormat: string;
constructor(private cfDelegate: CoreCourseFormatDelegate, translate: TranslateService, private injector: Injector, constructor(private cfDelegate: CoreCourseFormatDelegate, translate: TranslateService, private injector: Injector,
private courseHelper: CoreCourseHelperProvider, private domUtils: CoreDomUtilsProvider, private courseHelper: CoreCourseHelperProvider, private domUtils: CoreDomUtilsProvider,
@ -197,34 +198,31 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
* Get the components classes. * Get the components classes.
*/ */
protected getComponents(): void { protected getComponents(): void {
if (this.course) { if (this.course && this.course.format != this.lastCourseFormat) {
if (!this.courseFormatComponent) { this.lastCourseFormat = this.course.format;
// Format has changed or it's the first time, load all the components.
this.cfDelegate.getCourseFormatComponent(this.injector, this.course).then((component) => { this.cfDelegate.getCourseFormatComponent(this.injector, this.course).then((component) => {
this.courseFormatComponent = component; this.courseFormatComponent = component;
}); });
}
if (!this.courseSummaryComponent) {
this.cfDelegate.getCourseSummaryComponent(this.injector, this.course).then((component) => { this.cfDelegate.getCourseSummaryComponent(this.injector, this.course).then((component) => {
this.courseSummaryComponent = component; this.courseSummaryComponent = component;
}); });
}
if (!this.sectionSelectorComponent) {
this.cfDelegate.getSectionSelectorComponent(this.injector, this.course).then((component) => { this.cfDelegate.getSectionSelectorComponent(this.injector, this.course).then((component) => {
this.sectionSelectorComponent = component; this.sectionSelectorComponent = component;
}); });
}
if (!this.singleSectionComponent) {
this.cfDelegate.getSingleSectionComponent(this.injector, this.course).then((component) => { this.cfDelegate.getSingleSectionComponent(this.injector, this.course).then((component) => {
this.singleSectionComponent = component; this.singleSectionComponent = component;
}); });
}
if (!this.allSectionsComponent) {
this.cfDelegate.getAllSectionsComponent(this.injector, this.course).then((component) => { this.cfDelegate.getAllSectionsComponent(this.injector, this.course).then((component) => {
this.allSectionsComponent = component; this.allSectionsComponent = component;
}); });
} }
} }
}
/** /**
* Display the section selector modal. * Display the section selector modal.

View File

@ -1,5 +1,5 @@
<div padding> <div padding>
<core-course-module-description [description]="module.description"></core-course-module-description> <core-course-module-description [description]="module && module.description"></core-course-module-description>
<h2 *ngIf="!isDisabledInSite && isSupportedByTheApp">{{ 'core.whoops' | translate }}</h2> <h2 *ngIf="!isDisabledInSite && isSupportedByTheApp">{{ 'core.whoops' | translate }}</h2>
<h2 *ngIf="isDisabledInSite || !isSupportedByTheApp">{{ 'core.uhoh' | translate }}</h2> <h2 *ngIf="isDisabledInSite || !isSupportedByTheApp">{{ 'core.uhoh' | translate }}</h2>
@ -8,7 +8,7 @@
<p class="core-big" *ngIf="!isDisabledInSite && !isSupportedByTheApp">{{ 'core.course.activitynotyetviewableremoteaddon' | translate }}</p> <p class="core-big" *ngIf="!isDisabledInSite && !isSupportedByTheApp">{{ 'core.course.activitynotyetviewableremoteaddon' | translate }}</p>
<p *ngIf="isDisabledInSite || !isSupportedByTheApp"><strong>{{ 'core.course.askadmintosupport' | translate }}</strong></p> <p *ngIf="isDisabledInSite || !isSupportedByTheApp"><strong>{{ 'core.course.askadmintosupport' | translate }}</strong></p>
<div *ngIf="module.url"> <div *ngIf="module && module.url">
<p><strong>{{ 'core.course.useactivityonbrowser' | translate }}</strong></p> <p><strong>{{ 'core.course.useactivityonbrowser' | translate }}</strong></p>
<a ion-button block icon-end [href]="module.url" core-link> <a ion-button block icon-end [href]="module.url" core-link>
{{ 'core.openinbrowser' | translate }} {{ 'core.openinbrowser' | translate }}