diff --git a/src/addons/block/myoverview/components/myoverview/myoverview.ts b/src/addons/block/myoverview/components/myoverview/myoverview.ts index 4bdfa9d7a..d54b6c3b6 100644 --- a/src/addons/block/myoverview/components/myoverview/myoverview.ts +++ b/src/addons/block/myoverview/components/myoverview/myoverview.ts @@ -683,7 +683,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem * Go to search courses. */ async openSearch(): Promise { - CoreNavigator.navigateToSitePath('/list', { params : { mode: 'search' } }); + CoreNavigator.navigateToSitePath('courses/list', { params : { mode: 'search' } }); } /** diff --git a/src/core/directives/collapsible-footer.ts b/src/core/directives/collapsible-footer.ts index f0b7c90ad..e90806092 100644 --- a/src/core/directives/collapsible-footer.ts +++ b/src/core/directives/collapsible-footer.ts @@ -50,7 +50,8 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { protected endContentScrollListener?: EventListener; protected resizeListener?: CoreEventObserver; protected slotPromise?: CoreCancellablePromise; - protected calcPending = false; + protected viewportPromise?: CoreCancellablePromise; + protected loadingHeight = false; protected pageDidEnterListener?: EventListener; protected page?: HTMLElement; @@ -85,13 +86,14 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { * Calculate the height of the footer. */ protected async calculateHeight(): Promise { - if (!CoreDom.isElementVisible(this.element)) { - this.calcPending = true; - + if (this.loadingHeight) { + // Already calculating, return. return; } + this.loadingHeight = true; - this.calcPending = false; + this.viewportPromise = CoreDom.waitToBeInViewport(this.element); + await this.viewportPromise; this.element.classList.remove('is-active'); await CoreUtils.nextTick(); @@ -110,6 +112,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { this.element.classList.add('is-active'); this.setBarHeight(this.initialHeight); + this.loadingHeight = false; } /** @@ -175,9 +178,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { this.page?.addEventListener( 'ionViewDidEnter', this.pageDidEnterListener = () => { - if (this.calcPending) { - this.calculateHeight(); - } + this.calculateHeight(); }, ); } @@ -255,6 +256,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { this.resizeListener?.off(); this.slotPromise?.cancel(); + this.viewportPromise?.cancel(); } } diff --git a/src/core/directives/collapsible-header.ts b/src/core/directives/collapsible-header.ts index 31a4083bc..887918f05 100644 --- a/src/core/directives/collapsible-header.ts +++ b/src/core/directives/collapsible-header.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Directive, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChange } from '@angular/core'; -import { CorePromisedValue } from '@classes/promised-value'; +import { CoreCancellablePromise } from '@classes/cancellable-promise'; import { CoreLoadingComponent } from '@components/loading/loading'; import { CoreTabsOutletComponent } from '@components/tabs-outlet/tabs-outlet'; import { CoreTabsComponent } from '@components/tabs/tabs'; @@ -69,17 +69,15 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest protected content?: HTMLIonContentElement; protected contentScrollListener?: EventListener; protected endContentScrollListener?: EventListener; - protected pageDidEnterListener?: EventListener; protected resizeListener?: CoreEventObserver; protected floatingTitle?: HTMLHeadingElement; protected scrollingHeight?: number; protected subscriptions: Subscription[] = []; protected enabled = true; protected isWithinContent = false; - protected enteredPromise = new CorePromisedValue(); protected mutationObserver?: MutationObserver; - protected firstEnter = true; - protected initPending = false; + protected loadingFloatingTitle = false; + protected visiblePromise?: CoreCancellablePromise; constructor(el: ElementRef) { this.collapsedHeader = el.nativeElement; @@ -106,10 +104,9 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest await Promise.all([ this.initializeCollapsedHeader(), this.initializeExpandedHeader(), - await this.enteredPromise, ]); - this.initializeFloatingTitle(); + await this.initializeFloatingTitle(); this.initializeContent(); } @@ -117,7 +114,7 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest * @inheritdoc */ async ngOnChanges(changes: {[name: string]: SimpleChange}): Promise { - if (changes.collapsible) { + if (changes.collapsible && !changes.collapsible.firstChange) { this.collapsible = !CoreUtils.isFalseOrZero(changes.collapsible.currentValue); this.enabled = this.collapsible; @@ -141,47 +138,16 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest if (this.content && this.endContentScrollListener) { this.content.removeEventListener('ionScrollEnd', this.endContentScrollListener); } - if (this.page && this.pageDidEnterListener) { - this.page.removeEventListener('ionViewDidEnter', this.pageDidEnterListener); - } this.resizeListener?.off(); this.mutationObserver?.disconnect(); + this.visiblePromise?.cancel(); } /** - * Search the page element, initialize it, and wait until it's ready for the transition to trigger on scroll. + * Listen to changing events. */ - protected initializePage(): void { - if (!this.collapsedHeader.parentElement) { - throw new Error('[collapsible-header] Couldn\'t get page'); - } - - // Find element and prepare classes. - this.page = this.collapsedHeader.parentElement; - this.page.classList.add('collapsible-header-page'); - - this.page.addEventListener( - 'ionViewDidEnter', - this.pageDidEnterListener = () => { - if (this.firstEnter) { - this.firstEnter = false; - clearTimeout(timeout); - this.enteredPromise.resolve(); - } else if (this.initPending) { - this.initializeFloatingTitle(); - } - }, - ); - - // Timeout in case event is never fired. - const timeout = window.setTimeout(() => { - if (this.firstEnter) { - this.firstEnter = false; - this.enteredPromise.reject(new Error('[collapsible-header] Waiting for ionViewDidEnter timeout reached')); - } - }, 5000); - + protected listenEvents(): void { this.resizeListener = CoreDom.onWindowResize(() => { this.initializeFloatingTitle(); }, 50); @@ -216,6 +182,19 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest }); } + /** + * Search the page element, initialize it, and wait until it's ready for the transition to trigger on scroll. + */ + protected initializePage(): void { + if (!this.collapsedHeader.parentElement) { + throw new Error('[collapsible-header] Couldn\'t get page'); + } + + // Find element and prepare classes. + this.page = this.collapsedHeader.parentElement; + this.page.classList.add('collapsible-header-page'); + } + /** * Search the collapsed header element, initialize it, and wait until it's ready for the transition to trigger on scroll. */ @@ -253,6 +232,8 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest return; } + this.listenEvents(); + // Initialize from tabs. const tabs = CoreComponentsRegistry.resolve(this.page.querySelector('core-tabs-outlet'), CoreTabsOutletComponent); @@ -284,21 +265,22 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest /** * Initialize a floating title to mimic transitioning the title from one state to the other. */ - protected initializeFloatingTitle(): void { + protected async initializeFloatingTitle(): Promise { if (!this.page || !this.expandedHeader) { - throw new Error('[collapsible-header] Couldn\'t create floating title'); - } - - if (!CoreDom.isElementVisible(this.expandedHeader)) { - this.initPending = true; - return; } - this.initPending = false; + if (this.loadingFloatingTitle) { + // Already calculating, return. + return; + } + this.loadingFloatingTitle = true; + + this.visiblePromise = CoreDom.waitToBeVisible(this.expandedHeader); + await this.visiblePromise; this.page.classList.remove('collapsible-header-page-is-active'); - CoreUtils.nextTick(); + await CoreUtils.nextTick(); // Add floating title and measure initial position. const collapsedHeaderTitle = this.collapsedHeader.querySelector('h1') as HTMLHeadingElement; @@ -370,6 +352,8 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest this.collapsedFontStyles = collapsedFontStyles; this.expandedFontStyles = expandedFontStyles; this.expandedHeaderHeight = expandedHeaderHeight; + + this.loadingFloatingTitle = false; } /** diff --git a/src/core/directives/collapsible-item.ts b/src/core/directives/collapsible-item.ts index 6e95242f2..1a4950c8b 100644 --- a/src/core/directives/collapsible-item.ts +++ b/src/core/directives/collapsible-item.ts @@ -55,8 +55,9 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy { protected resizeListener?: CoreEventObserver; protected darkModeListener?: Subscription; protected domPromise?: CoreCancellablePromise; + protected visiblePromise?: CoreCancellablePromise; protected uniqueId: string; - protected calcPending = false; + protected loadingHeight = false; protected pageDidEnterListener?: EventListener; protected page?: HTMLElement; @@ -99,9 +100,7 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy { this.page?.addEventListener( 'ionViewDidEnter', this.pageDidEnterListener = () => { - if (this.calcPending) { - this.calculateHeight(); - } + this.calculateHeight(); }, ); @@ -143,20 +142,20 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy { * Calculate the height and check if we need to display show more or not. */ protected async calculateHeight(): Promise { + if (this.loadingHeight) { + // Already calculating, return. + return; + } + this.loadingHeight = true; + + this.visiblePromise = CoreDom.waitToBeVisible(this.element); + await this.visiblePromise; + // Remove max-height (if any) to calculate the real height. this.element.classList.add('collapsible-loading-height'); await this.waitFormatTextsRendered(); - if (!this.element.clientHeight) { - this.calcPending = true; - this.element.classList.remove('collapsible-loading-height'); - - return; - } - - this.calcPending = false; - this.expandedHeight = this.element.getBoundingClientRect().height; // Restore the max height now. @@ -167,6 +166,7 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy { this.setExpandButtonEnabled(enable); this.setGradientColor(); + this.loadingHeight = false; } /** @@ -298,6 +298,7 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy { this.resizeListener?.off(); this.darkModeListener?.unsubscribe(); this.domPromise?.cancel(); + this.visiblePromise?.cancel(); if (this.page && this.pageDidEnterListener) { this.page.removeEventListener('ionViewDidEnter', this.pageDidEnterListener);