From 8037168222a2ea3df2d72660c36aecb96b1f4179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 15 Mar 2022 14:26:40 +0100 Subject: [PATCH 01/12] MOBILE-3814 courses: Always init prefetch course icons when not empty --- src/core/features/course/services/course-helper.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/core/features/course/services/course-helper.ts b/src/core/features/course/services/course-helper.ts index 7dc88fd7b..ad36f9899 100644 --- a/src/core/features/course/services/course-helper.ts +++ b/src/core/features/course/services/course-helper.ts @@ -1064,15 +1064,13 @@ export class CoreCourseHelperProvider { * * @param courses Courses array to get info from. * @param prefetch Prefetch information. - * @param minCourses Min course to show icon. * @return Resolved with the prefetch information updated when done. */ async initPrefetchCoursesIcons( courses: CoreCourseBasicData[], prefetch: CorePrefetchStatusInfo, - minCourses: number = 2, ): Promise { - if (!courses || courses.length < minCourses) { + if (!courses || courses.length <= 0) { // Not enough courses. prefetch.icon = ''; From c30a768f35efbd61bb6c9aec33c5e78d137164d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 15 Mar 2022 16:42:41 +0100 Subject: [PATCH 02/12] MOBILE-3814 navigation: Jump disabled navigation items on bar --- .../navigation-bar/core-navigation-bar.html | 2 +- .../navigation-bar/navigation-bar.ts | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/core/components/navigation-bar/core-navigation-bar.html b/src/core/components/navigation-bar/core-navigation-bar.html index 1005bfcd5..24703b279 100644 --- a/src/core/components/navigation-bar/core-navigation-bar.html +++ b/src/core/components/navigation-bar/core-navigation-bar.html @@ -9,7 +9,7 @@ - + diff --git a/src/core/components/navigation-bar/navigation-bar.ts b/src/core/components/navigation-bar/navigation-bar.ts index c749dfa5f..2fb5af91f 100644 --- a/src/core/components/navigation-bar/navigation-bar.ts +++ b/src/core/components/navigation-bar/navigation-bar.ts @@ -67,15 +67,21 @@ export class CoreNavigationBarComponent implements OnChanges { this.progress = ((this.currentIndex + 1) / this.items.length) * 100; this.progressText = `${this.currentIndex + 1} / ${this.items.length}`; - this.nextIndex = this.items[this.currentIndex + 1]?.enabled ? this.currentIndex + 1 : -1; - if (this.nextIndex >= 0) { - this.nextTitle = Translate.instant(this.nextTranslate, { $a: this.items[this.nextIndex].title || '' }); + this.nextIndex =this.currentIndex + 1; + while (this.items[this.nextIndex] && !this.items[this.nextIndex].enabled) { + this.nextIndex++; } + this.nextTitle = this.items[this.nextIndex] + ? Translate.instant(this.nextTranslate, { $a: this.items[this.nextIndex].title || '' }) + : ''; - this.previousIndex = this.items[this.currentIndex - 1]?.enabled ? this.currentIndex - 1 : -1; - if (this.previousIndex >= 0) { - this.previousTitle = Translate.instant(this.previousTranslate, { $a: this.items[this.previousIndex].title || '' }); + this.previousIndex =this.currentIndex - 1; + while (this.items[this.previousIndex] && !this.items[this.previousIndex].enabled) { + this.previousIndex--; } + this.previousTitle = this.items[this.previousIndex] + ? Translate.instant(this.previousTranslate, { $a: this.items[this.previousIndex].title || '' }) + : ''; } /** From 9b7988f26d8b52301d84daa5e99a17b0528d5cd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 15 Mar 2022 17:21:10 +0100 Subject: [PATCH 03/12] MOBILE-3814 collapsible: Observe original title mutation --- src/core/directives/collapsible-header.ts | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/core/directives/collapsible-header.ts b/src/core/directives/collapsible-header.ts index 56365f4da..69f4929ef 100644 --- a/src/core/directives/collapsible-header.ts +++ b/src/core/directives/collapsible-header.ts @@ -76,6 +76,7 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest protected enabled = true; protected isWithinContent = false; protected enteredPromise = new CorePromisedValue(); + protected mutationObserver?: MutationObserver; constructor(el: ElementRef) { this.collapsedHeader = el.nativeElement; @@ -126,6 +127,7 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest } this.resizeListener?.off(); + this.mutationObserver?.disconnect(); } /** @@ -161,6 +163,31 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest this.subscriptions.push(CoreSettingsHelper.onDarkModeChange().subscribe(() => { this.initializeFloatingTitle(); })); + + this.mutationObserver = new MutationObserver(() => { + if (!this.expandedHeader) { + return; + } + + const originalTitle = this.expandedHeader.querySelector('h1.collapsible-header-original-title') || + this.expandedHeader.querySelector('h1') as HTMLHeadingElement; + + const floatingTitleWrapper = originalTitle.parentElement as HTMLElement; + const floatingTitle = floatingTitleWrapper.querySelector('.collapsible-header-floating-title') as HTMLHeadingElement; + + if (!floatingTitle || !originalTitle) { + return; + } + + // Original title changed, change the contents. + const newFloatingTitle = originalTitle.cloneNode(true) as HTMLHeadingElement; + newFloatingTitle.classList.add('collapsible-header-floating-title'); + newFloatingTitle.classList.remove('collapsible-header-original-title'); + + floatingTitleWrapper.replaceChild(newFloatingTitle, floatingTitle); + + this.initializeFloatingTitle(); + }); } /** @@ -247,6 +274,7 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest floatingTitleWrapper.insertBefore(floatingTitle, originalTitle); originalTitle.classList.add('collapsible-header-original-title'); + this.mutationObserver?.observe(originalTitle, { childList: true, subtree: true }); } const floatingTitleBoundingBox = floatingTitle.getBoundingClientRect(); From b1461680a8d676335683c11a1de6ae264655faac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 15 Mar 2022 17:48:33 +0100 Subject: [PATCH 04/12] MOBILE-3814 chore: Update non reactive attributes using a directive --- src/core/directives/directives.module.ts | 3 + .../update-non-reactive-attributes.ts | 68 +++++++++++++++++++ .../course-format/course-format.html | 2 +- .../components/course-format/course-format.ts | 24 ------- 4 files changed, 72 insertions(+), 25 deletions(-) create mode 100644 src/core/directives/update-non-reactive-attributes.ts diff --git a/src/core/directives/directives.module.ts b/src/core/directives/directives.module.ts index 2767f4973..3a89d007f 100644 --- a/src/core/directives/directives.module.ts +++ b/src/core/directives/directives.module.ts @@ -33,6 +33,7 @@ import { CoreCollapsibleItemDirective } from './collapsible-item'; import { CoreCollapsibleFooterDirective } from './collapsible-footer'; import { CoreContentDirective } from './content'; import { CoreOnAppearDirective } from './on-appear'; +import { CoreUpdateNonReactiveAttributesDirective } from './update-non-reactive-attributes'; @NgModule({ declarations: [ @@ -55,6 +56,7 @@ import { CoreOnAppearDirective } from './on-appear'; CoreCollapsibleItemDirective, CoreCollapsibleFooterDirective, CoreContentDirective, + CoreUpdateNonReactiveAttributesDirective, ], exports: [ CoreAutoFocusDirective, @@ -76,6 +78,7 @@ import { CoreOnAppearDirective } from './on-appear'; CoreCollapsibleItemDirective, CoreCollapsibleFooterDirective, CoreContentDirective, + CoreUpdateNonReactiveAttributesDirective, ], }) export class CoreDirectivesModule {} diff --git a/src/core/directives/update-non-reactive-attributes.ts b/src/core/directives/update-non-reactive-attributes.ts new file mode 100644 index 000000000..1c8508c3b --- /dev/null +++ b/src/core/directives/update-non-reactive-attributes.ts @@ -0,0 +1,68 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Directive, ElementRef, OnDestroy, OnInit } from '@angular/core'; + +/** + * Directive to observe mutations on some attributes and propagate them inside. + * Current supported attributes: ion-button.aria-label + * + * This is necessary in order to update some attributes that are not reactive, for example aria-label. + * + * @see https://github.com/ionic-team/ionic-framework/issues/21534 + */ +@Directive({ + selector: 'ion-button', +}) +export class CoreUpdateNonReactiveAttributesDirective implements OnInit, OnDestroy { + + protected element: HTMLIonButtonElement; + protected mutationObserver: MutationObserver; + + constructor(element: ElementRef) { + this.element = element.nativeElement; + this.mutationObserver = new MutationObserver(() => { + const ariaLabel = this.element.getAttribute('aria-label'); + if (!ariaLabel) { + // Aria label unset by ionButton component (when first created). + return; + } + + // Propagate label to button. + const button = this.element.shadowRoot?.querySelector('button'); + button?.setAttribute('aria-label', ariaLabel); + }); + } + + /** + * @inheritdoc + */ + async ngOnInit(): Promise { + await this.element.componentOnReady(); + + if (!this.element.getAttribute('aria-label')) { + return; + } + + this.mutationObserver.observe(this.element, { attributes: true, attributeFilter: ['aria-label'] }); + } + + /** + * @inheritdoc + */ + ngOnDestroy(): void { + this.mutationObserver.disconnect(); + } + +} diff --git a/src/core/features/course/components/course-format/course-format.html b/src/core/features/course/components/course-format/course-format.html index 323216634..2d8cd3391 100644 --- a/src/core/features/course/components/course-format/course-format.html +++ b/src/core/features/course/components/course-format/course-format.html @@ -24,7 +24,7 @@ -
+
diff --git a/src/core/features/course/components/course-format/course-format.ts b/src/core/features/course/components/course-format/course-format.ts index 4e0be0612..50460392d 100644 --- a/src/core/features/course/components/course-format/course-format.ts +++ b/src/core/features/course/components/course-format/course-format.ts @@ -92,7 +92,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { selectedSection?: CoreCourseSection; previousSection?: CoreCourseSection; nextSection?: CoreCourseSection; - hasPreviousOrNextSections = false; allSectionsId: number = CoreCourseProvider.ALL_SECTIONS_ID; stealthModulesSectionId: number = CoreCourseProvider.STEALTH_MODULES_SECTION_ID; loaded = false; @@ -489,8 +488,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { this.showMoreActivities(); } - this.hasPreviousOrNextSections = !!this.previousSection || !!this.nextSection; - // Scroll to module if needed. Give more priority to the input. const moduleIdToScroll = this.moduleId && previousValue === undefined ? this.moduleId : moduleId; if (moduleIdToScroll) { @@ -507,8 +504,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { CoreCourse.logView(this.course.id, newSection.section, undefined, this.course.fullname), ); } - - this.invalidateSectionButtons(); } /** @@ -563,25 +558,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { done?.(); } - /** - * Invalidate section buttons so that they are rendered again. This is necessary in order to update - * some attributes that are not reactive, for example aria-label. - * - * @see https://github.com/ionic-team/ionic-framework/issues/21534 - */ - protected async invalidateSectionButtons(): Promise { - const previousSection = this.previousSection; - const nextSection = this.nextSection; - - this.previousSection = undefined; - this.nextSection = undefined; - - await CoreUtils.nextTick(); - - this.previousSection = previousSection; - this.nextSection = nextSection; - } - /** * Show more activities (only used when showing all the sections at the same time). * From 4cdc6b9dd6934bf5aa5fcf2d79e2cd4fefc3e977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 16 Mar 2022 13:49:58 +0100 Subject: [PATCH 05/12] MOBILE-3814 chore: Improve component registry wait for ready --- src/core/classes/async-component.ts | 24 ++++++++ src/core/components/loading/loading.ts | 15 +++-- src/core/directives/collapsible-footer.ts | 21 ++----- src/core/directives/collapsible-header.ts | 18 ++---- src/core/directives/collapsible-item.ts | 27 +++------ src/core/directives/format-text.ts | 7 ++- .../rich-text-editor/rich-text-editor.ts | 5 +- src/core/singletons/components-registry.ts | 57 +++++++++++-------- 8 files changed, 92 insertions(+), 82 deletions(-) create mode 100644 src/core/classes/async-component.ts diff --git a/src/core/classes/async-component.ts b/src/core/classes/async-component.ts new file mode 100644 index 000000000..77635a70f --- /dev/null +++ b/src/core/classes/async-component.ts @@ -0,0 +1,24 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * Component that is not rendered immediately after being mounted. + */ +export interface AsyncComponent { + + /** + * Wait until the component is fully rendered and ready. + */ + ready(): Promise; +} diff --git a/src/core/components/loading/loading.ts b/src/core/components/loading/loading.ts index 2fbd213cc..d5be19770 100644 --- a/src/core/components/loading/loading.ts +++ b/src/core/components/loading/loading.ts @@ -20,6 +20,7 @@ import { CoreAnimations } from '@components/animations'; import { Translate } from '@singletons'; import { CoreComponentsRegistry } from '@singletons/components-registry'; import { CorePromisedValue } from '@classes/promised-value'; +import { AsyncComponent } from '@classes/async-component'; /** * Component to show a loading spinner and message while data is being loaded. @@ -47,7 +48,7 @@ import { CorePromisedValue } from '@classes/promised-value'; styleUrls: ['loading.scss'], animations: [CoreAnimations.SHOW_HIDE], }) -export class CoreLoadingComponent implements OnInit, OnChanges, AfterViewInit { +export class CoreLoadingComponent implements OnInit, OnChanges, AfterViewInit, AsyncComponent { @Input() hideUntil: unknown; // Determine when should the contents be shown. @Input() message?: string; // Message to show while loading. @@ -58,7 +59,7 @@ export class CoreLoadingComponent implements OnInit, OnChanges, AfterViewInit { uniqueId: string; protected element: HTMLElement; // Current element. loaded = false; // Only comes true once. - protected firstLoadedPromise = new CorePromisedValue(); + protected onReadyPromise = new CorePromisedValue(); constructor(element: ElementRef) { this.element = element.nativeElement; @@ -108,7 +109,7 @@ export class CoreLoadingComponent implements OnInit, OnChanges, AfterViewInit { if (!this.loaded && loaded) { this.loaded = true; // Only comes true once. - this.firstLoadedPromise.resolve(this.uniqueId); + this.onReadyPromise.resolve(); } // Event has been deprecated since app 4.0. @@ -119,12 +120,10 @@ export class CoreLoadingComponent implements OnInit, OnChanges, AfterViewInit { } /** - * Wait the loading to finish. - * - * @return Promise resolved with the uniqueId when done. + * @inheritdoc */ - async whenLoaded(): Promise { - return await this.firstLoadedPromise; + async ready(): Promise { + return await this.onReadyPromise; } } diff --git a/src/core/directives/collapsible-footer.ts b/src/core/directives/collapsible-footer.ts index f63d1f57c..2dccfd760 100644 --- a/src/core/directives/collapsible-footer.ts +++ b/src/core/directives/collapsible-footer.ts @@ -66,7 +66,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { await this.domPromise; await this.waitLoadingsDone(); - await this.waitFormatTextsRendered(this.element); + await this.waitFormatTextsRendered(); this.content = this.element.closest('ion-content'); @@ -156,15 +156,9 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { /** * Wait until all children inside the element are done rendering. - * - * @param element Element. */ - protected async waitFormatTextsRendered(element: Element): Promise { - await CoreComponentsRegistry.finishRenderingAllElementsInside( - element, - 'core-format-text', - 'rendered', - ); + protected async waitFormatTextsRendered(): Promise { + await CoreComponentsRegistry.waitComponentsReady(this.element, 'core-format-text', CoreFormatTextDirective); } /** @@ -210,13 +204,8 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { const scrollElement = await this.ionContent.getScrollElement(); await Promise.all([ - await CoreComponentsRegistry.finishRenderingAllElementsInside - (scrollElement, 'core-loading', 'whenLoaded'), - await CoreComponentsRegistry.finishRenderingAllElementsInside( - this.element, - 'core-loading', - 'whenLoaded', - ), + await CoreComponentsRegistry.waitComponentsReady(scrollElement, 'core-loading', CoreLoadingComponent), + await CoreComponentsRegistry.waitComponentsReady(this.element, 'core-loading', CoreLoadingComponent), ]); } diff --git a/src/core/directives/collapsible-header.ts b/src/core/directives/collapsible-header.ts index 69f4929ef..0bf4d0738 100644 --- a/src/core/directives/collapsible-header.ts +++ b/src/core/directives/collapsible-header.ts @@ -332,15 +332,13 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest /** * Wait until all children inside the page. - * - * @return Promise resolved when loadings are done. */ protected async waitLoadingsDone(): Promise { - await CoreComponentsRegistry.finishRenderingAllElementsInside( - this.page, - 'core-loading', - 'whenLoaded', - ); + if (!this.page) { + return; + } + + await CoreComponentsRegistry.waitComponentsReady(this.page, 'core-loading', CoreLoadingComponent); } /** @@ -350,11 +348,7 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest * @return Promise resolved when texts are rendered. */ protected async waitFormatTextsRendered(element: Element): Promise { - await CoreComponentsRegistry.finishRenderingAllElementsInside( - element, - 'core-format-text', - 'rendered', - ); + await CoreComponentsRegistry.waitComponentsReady(element, 'core-format-text', CoreFormatTextDirective); } /** diff --git a/src/core/directives/collapsible-item.ts b/src/core/directives/collapsible-item.ts index 08e987035..41990f722 100644 --- a/src/core/directives/collapsible-item.ts +++ b/src/core/directives/collapsible-item.ts @@ -16,7 +16,6 @@ import { Directive, ElementRef, Input, OnDestroy, OnInit } from '@angular/core'; import { CoreCancellablePromise } from '@classes/cancellable-promise'; import { CoreLoadingComponent } from '@components/loading/loading'; import { CoreDomUtils } from '@services/utils/dom'; -import { CoreUtils } from '@services/utils/utils'; import { Translate } from '@singletons'; import { CoreComponentsRegistry } from '@singletons/components-registry'; import { CoreEventObserver } from '@singletons/events'; @@ -103,28 +102,18 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy { const page = this.element.closest('.ion-page'); - await CoreComponentsRegistry.finishRenderingAllElementsInside(page, 'core-loading', 'whenLoaded'); + if (!page) { + return; + } + + await CoreComponentsRegistry.waitComponentsReady(page, 'core-loading', CoreLoadingComponent); } /** * Wait until all children inside the element are done rendering. - * - * @param element Element. */ - protected async waitFormatTextsRendered(element: Element): Promise { - let formatTextElements: HTMLElement[] = []; - - if (this.element.tagName == 'CORE-FORMAT-TEXT') { - formatTextElements = [this.element]; - } else { - formatTextElements = Array.from(element.querySelectorAll('core-format-text')); - } - - const formatTexts = formatTextElements.map(element => CoreComponentsRegistry.resolve(element, CoreFormatTextDirective)); - - await Promise.all(formatTexts.map(formatText => formatText?.rendered())); - - await CoreUtils.nextTick(); + protected async waitFormatTextsRendered(): Promise { + await CoreComponentsRegistry.waitComponentsReady(this.element, 'core-format-text', CoreFormatTextDirective); } /** @@ -134,7 +123,7 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy { // Remove max-height (if any) to calculate the real height. this.element.classList.add('collapsible-loading-height'); - await this.waitFormatTextsRendered(this.element); + await this.waitFormatTextsRendered(); this.expandedHeight = this.element.getBoundingClientRect().height; diff --git a/src/core/directives/format-text.ts b/src/core/directives/format-text.ts index 42c780f76..afd53beae 100644 --- a/src/core/directives/format-text.ts +++ b/src/core/directives/format-text.ts @@ -43,6 +43,7 @@ import { CoreSubscriptions } from '@singletons/subscriptions'; import { CoreComponentsRegistry } from '@singletons/components-registry'; import { CoreCollapsibleItemDirective } from './collapsible-item'; import { CoreCancellablePromise } from '@classes/cancellable-promise'; +import { AsyncComponent } from '@classes/async-component'; /** * Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective @@ -56,7 +57,7 @@ import { CoreCancellablePromise } from '@classes/cancellable-promise'; @Directive({ selector: 'core-format-text', }) -export class CoreFormatTextDirective implements OnChanges, OnDestroy { +export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncComponent { @ViewChild(CoreCollapsibleItemDirective) collapsible?: CoreCollapsibleItemDirective; @@ -137,9 +138,9 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy { } /** - * Wait until the text is fully rendered. + * @inheritdoc */ - async rendered(): Promise { + async ready(): Promise { if (!this.element.classList.contains('core-format-text-loading')) { return; } diff --git a/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts b/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts index 550682a86..18a9760fb 100644 --- a/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts +++ b/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts @@ -294,8 +294,11 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn await this.domPromise; const page = this.element.closest('.ion-page'); + if (!page) { + return; + } - await CoreComponentsRegistry.finishRenderingAllElementsInside(page, 'core-loading', 'whenLoaded'); + await CoreComponentsRegistry.waitComponentsReady(page, 'core-loading', CoreLoadingComponent); } /** diff --git a/src/core/singletons/components-registry.ts b/src/core/singletons/components-registry.ts index 5198ee90b..32b08e3dd 100644 --- a/src/core/singletons/components-registry.ts +++ b/src/core/singletons/components-registry.ts @@ -13,6 +13,7 @@ // limitations under the License. import { Component } from '@angular/core'; +import { AsyncComponent } from '@classes/async-component'; import { CoreUtils } from '@services/utils/utils'; /** @@ -39,7 +40,7 @@ export class CoreComponentsRegistry { * @param componentClass Component class. * @returns Component instance. */ - static resolve(element?: Element | null, componentClass?: ComponentConstructor): T | null { + static resolve(element?: Element | null, componentClass?: ComponentConstructor): T | null { const instance = (element && this.instances.get(element) as T) ?? null; return instance && (!componentClass || instance instanceof componentClass) @@ -65,35 +66,45 @@ export class CoreComponentsRegistry { } /** - * Waits all elements to be rendered. + * Get a component instances and wait to be ready. * - * @param element Parent element where to search. - * @param selector Selector to search on parent. - * @param fnName Component function that have to be resolved when rendered. - * @param params Params of function that have to be resolved when rendered. + * @param element Root element. + * @param componentClass Component class. * @return Promise resolved when done. */ - static async finishRenderingAllElementsInside( - element: Element | undefined | null, - selector: string, - fnName: string, - params?: unknown[], + static async waitComponentReady( + element: Element | null, + componentClass?: ComponentConstructor, ): Promise { - if (!element) { + const instance = this.resolve(element, componentClass); + if (!instance) { return; } - const components = Array - .from(element.querySelectorAll(selector)) - .map(element => CoreComponentsRegistry.resolve(element)); + await instance.ready(); + } - await Promise.all(components.map(component => { - if (!component) { - return; - } - - return component[fnName].apply(component, params); - })); + /** + * Waits all elements matching to be ready. + * + * @param element Element where to search. + * @param selector Selector to search on parent. + * @param componentClass Component class. + * @return Promise resolved when done. + */ + static async waitComponentsReady( + element: Element, + selector: string, + componentClass?: ComponentConstructor, + ): Promise { + if (element.matches(selector)) { + // Element to wait is myself. + await CoreComponentsRegistry.waitComponentReady(element, componentClass); + } else { + await Promise.all(Array + .from(element.querySelectorAll(selector)) + .map(element => CoreComponentsRegistry.waitComponentReady(element, componentClass))); + } // Wait for next tick to ensure components are completely rendered. await CoreUtils.nextTick(); @@ -105,4 +116,4 @@ export class CoreComponentsRegistry { * Component constructor. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any -export type ComponentConstructor = { new(...args: any[]): T }; +export type ComponentConstructor = { new(...args: any[]): T }; From aaa8ee3fc8797dbdc9ccd5fc4622068aceeb483c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 16 Mar 2022 11:17:30 +0100 Subject: [PATCH 06/12] MOBILE-3814 styles: Make dark named color a bit darker --- src/theme/globals.mixins.ionic.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/theme/globals.mixins.ionic.scss b/src/theme/globals.mixins.ionic.scss index a5fb16e60..3e9421a1b 100644 --- a/src/theme/globals.mixins.ionic.scss +++ b/src/theme/globals.mixins.ionic.scss @@ -80,7 +80,7 @@ body.dark { $dark: map-get($base, 'dark'); - $dark: mix($light, white, 40%) !default; + $dark: mix($light, white, 80%) !default; @include generate-color-variants($color-name, $dark); } } From 8b3a07d22439e73926c43f1c9574e2c797b084d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 16 Mar 2022 11:55:39 +0100 Subject: [PATCH 07/12] MOBILE-3814 course: Limit width on course summary --- .../pages/course-summary/course-summary.html | 233 +++++++++--------- .../pages/course-summary/course-summary.scss | 7 +- 2 files changed, 124 insertions(+), 116 deletions(-) diff --git a/src/core/features/course/pages/course-summary/course-summary.html b/src/core/features/course/pages/course-summary/course-summary.html index cde4c68c3..d8932c734 100644 --- a/src/core/features/course/pages/course-summary/course-summary.html +++ b/src/core/features/course/pages/course-summary/course-summary.html @@ -23,134 +23,139 @@
- - -

- - -

-

- {{ 'core.courses.aria:coursename' | translate }} - - -

- - {{ 'core.courses.aria:coursecategory' | translate }} - - - - - -
- - - -
- - -
- - -
-
-

- - {{ 'core.course.startdate' | translate }}
- {{ course.startdate * 1000 | coreFormatDate:'strftimedaydatetime' }} -

-

- - {{ 'core.course.enddate' | translate }}
- {{ course.enddate * 1000 | coreFormatDate:'strftimedaydatetime' }} -

-
-
-
- - - -

- {{'core.course.coursesummary' | translate}} -

- - -
-
- - - - +
+ -

- {{ 'core.teachers' | translate }} +

+ +

+

+ {{ 'core.courses.aria:coursename' | translate }} + + +

+ + {{ 'core.courses.aria:coursecategory' | translate }} + + + + + +
+ + + +
+ + +
+ + +
+
+

+ + {{ 'core.course.startdate' | translate }}
+ {{ course.startdate * 1000 | coreFormatDate:'strftimedaydatetime' }} +

+

+ + {{ 'core.course.enddate' | translate }}
+ {{ course.enddate * 1000 | coreFormatDate:'strftimedaydatetime' }} +

+
- - - - + + + +

+ {{'core.course.coursesummary' | translate}} +

+ + +
+
+ + + + -

{{contact.fullname}}

+

+ {{ 'core.teachers' | translate }} +

-
- - - - - - -
- - - - : - - - - - -
+ + + + + +

{{contact.fullname}}

+
+
-
-
+ + + + + + +
+ + + + : + + + + + +
+
+
+
+
- - - - {{item.data.title | translate }} +
+ + + + {{item.data.title | translate }} + + + + + {{ 'core.courses.enrolme' | translate }} - - - {{ 'core.courses.enrolme' | translate }} - + + + + {{ 'core.courses.notenrollable' | translate }} + + - - - - {{ 'core.courses.notenrollable' | translate }} - - - - - - {{ 'core.course.viewcourse' | translate }} - + + + {{ 'core.course.viewcourse' | translate }} + +
diff --git a/src/core/features/course/pages/course-summary/course-summary.scss b/src/core/features/course/pages/course-summary/course-summary.scss index 477f4a454..17f6c76af 100644 --- a/src/core/features/course/pages/course-summary/course-summary.scss +++ b/src/core/features/course/pages/course-summary/course-summary.scss @@ -39,11 +39,14 @@ .course-container { position: relative; top: calc(var(--thumb-height) - var(--big-radius)); + border-radius: var(--big-radius) var(--big-radius) 0 0; + background-color: var(--ion-background-color); + box-shadow: var(--drop-shadow-top); + clip-path: inset(-5px 0px 0px 0px); } .course-name { - border-radius: var(--big-radius) var(--big-radius) 0 0; - box-shadow: var(--drop-shadow-top); + --background: transparent; ion-label { margin-bottom: 0px; } From 1819c743a6ce8100c404fe0958ed54d9797c0928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 16 Mar 2022 11:56:25 +0100 Subject: [PATCH 08/12] MOBILE-3814 chore: Create a wait to be visible function --- src/core/directives/on-appear.ts | 8 +++---- src/core/services/utils/dom.ts | 37 +++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/core/directives/on-appear.ts b/src/core/directives/on-appear.ts index 267fd3f9f..c894878dc 100644 --- a/src/core/directives/on-appear.ts +++ b/src/core/directives/on-appear.ts @@ -27,7 +27,7 @@ export class CoreOnAppearDirective implements OnInit, OnDestroy { @Output() onAppear = new EventEmitter(); private element: HTMLElement; - protected domPromise?: CoreCancellablePromise; + protected visiblePromise?: CoreCancellablePromise; constructor(element: ElementRef) { this.element = element.nativeElement; @@ -37,9 +37,9 @@ export class CoreOnAppearDirective implements OnInit, OnDestroy { * @inheritdoc */ async ngOnInit(): Promise { - this.domPromise = CoreDomUtils.waitToBeInDOM(this.element); + this.visiblePromise = CoreDomUtils.waitToBeVisible(this.element); - await this.domPromise; + await this.visiblePromise; this.onAppear.emit(); } @@ -48,7 +48,7 @@ export class CoreOnAppearDirective implements OnInit, OnDestroy { * @inheritdoc */ ngOnDestroy(): void { - this.domPromise?.cancel(); + this.visiblePromise?.cancel(); } } diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts index 380064f49..0e8f1dfc1 100644 --- a/src/core/services/utils/dom.ts +++ b/src/core/services/utils/dom.ts @@ -100,7 +100,7 @@ export class CoreDomUtilsProvider { * @param timeout If defined, timeout to wait before rejecting the promise. * @return Cancellable promise. */ - waitToBeInDOM(element: Element, timeout?: number): CoreCancellablePromise { + waitToBeInDOM(element: HTMLElement, timeout?: number): CoreCancellablePromise { const root = element.getRootNode({ composed: true }); if (root === document) { @@ -140,6 +140,41 @@ export class CoreDomUtilsProvider { ); } + /** + * Wait an element to be in dom and visible. + * + * @param element Element to wait. + * @return Cancellable promise. + */ + waitToBeVisible(element: HTMLElement): CoreCancellablePromise { + const domPromise = CoreDomUtils.waitToBeInDOM(element); + + let interval: number | undefined; + + return new CoreCancellablePromise( + async (resolve) => { + await domPromise; + + if (CoreDomUtils.isElementVisible(element)) { + return resolve(); + } + + interval = window.setInterval(() => { + if (!CoreDomUtils.isElementVisible(element)) { + return; + } + + resolve(); + window.clearInterval(interval); + }, 50); + }, + () => { + domPromise.cancel(); + window.clearInterval(interval); + }, + ); + } + /** * Window resize is widely checked and may have many performance issues, debouce usage is needed to avoid calling it too much. * This function helps setting up the debounce feature and remove listener easily. From 2887290c30abd1a10c0355836525ae507285c60e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 16 Mar 2022 12:15:32 +0100 Subject: [PATCH 09/12] MOBILE-3814 course: Add course header background color --- .../features/course/pages/index/index.html | 39 ++++++++++--------- .../features/course/pages/index/index.scss | 4 ++ 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/core/features/course/pages/index/index.html b/src/core/features/course/pages/index/index.html index a0e768d08..7c445a8af 100644 --- a/src/core/features/course/pages/index/index.html +++ b/src/core/features/course/pages/index/index.html @@ -16,24 +16,25 @@ - - -
- -
- - - -
+
+ + +
+ +
+ + + +
- -

{{ title }}

-
- - -
-
- -
+ +

{{ title }}

+
+ + +
+
+ +
diff --git a/src/core/features/course/pages/index/index.scss b/src/core/features/course/pages/index/index.scss index 6b75fd1e8..3ae1498bb 100644 --- a/src/core/features/course/pages/index/index.scss +++ b/src/core/features/course/pages/index/index.scss @@ -44,4 +44,8 @@ h1 { font-size: 20px; } + + .core-course-header { + background: var(--ion-item-background); + } } From fed1e399bcaba9039479c1f826c55a0d33bb0cf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 16 Mar 2022 12:37:19 +0100 Subject: [PATCH 10/12] MOBILE-3814 lint: Fix missing spaces after assignment --- .../pages/group-conversations/group-conversations.page.ts | 2 +- src/core/components/navigation-bar/navigation-bar.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/addons/messages/pages/group-conversations/group-conversations.page.ts b/src/addons/messages/pages/group-conversations/group-conversations.page.ts index 3e6216af7..f2d572663 100644 --- a/src/addons/messages/pages/group-conversations/group-conversations.page.ts +++ b/src/addons/messages/pages/group-conversations/group-conversations.page.ts @@ -271,7 +271,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { async ngOnInit(): Promise { this.route.queryParams.subscribe(async (params) => { // When a child page loads this callback is triggered too. - const conversationId =CoreNavigator.getRouteNumberParam('conversationId', { params }); + const conversationId = CoreNavigator.getRouteNumberParam('conversationId', { params }); const userId = CoreNavigator.getRouteNumberParam('userId', { params }); if (conversationId || userId) { // Update the selected ones. diff --git a/src/core/components/navigation-bar/navigation-bar.ts b/src/core/components/navigation-bar/navigation-bar.ts index 2fb5af91f..67b072824 100644 --- a/src/core/components/navigation-bar/navigation-bar.ts +++ b/src/core/components/navigation-bar/navigation-bar.ts @@ -67,7 +67,7 @@ export class CoreNavigationBarComponent implements OnChanges { this.progress = ((this.currentIndex + 1) / this.items.length) * 100; this.progressText = `${this.currentIndex + 1} / ${this.items.length}`; - this.nextIndex =this.currentIndex + 1; + this.nextIndex = this.currentIndex + 1; while (this.items[this.nextIndex] && !this.items[this.nextIndex].enabled) { this.nextIndex++; } @@ -75,7 +75,7 @@ export class CoreNavigationBarComponent implements OnChanges { ? Translate.instant(this.nextTranslate, { $a: this.items[this.nextIndex].title || '' }) : ''; - this.previousIndex =this.currentIndex - 1; + this.previousIndex = this.currentIndex - 1; while (this.items[this.previousIndex] && !this.items[this.previousIndex].enabled) { this.previousIndex--; } From 2c6f2bb6e66c709c59546d23bce39bf5f0cc293a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 16 Mar 2022 12:52:47 +0100 Subject: [PATCH 11/12] MOBILE-3814 course: Fix dark mode issues on course index --- .../features/course/components/course-index/course-index.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/features/course/components/course-index/course-index.scss b/src/core/features/course/components/course-index/course-index.scss index db08d3ad2..f4575ba22 100644 --- a/src/core/features/course/components/course-index/course-index.scss +++ b/src/core/features/course/components/course-index/course-index.scss @@ -18,6 +18,7 @@ ion-item.item { &.item-current { --background: var(--primary-tint); + --color: var(--gray-900); border: 0; } @@ -34,6 +35,7 @@ ion-item.item { &:hover { background: var(--primary-shade); + color: var(--gray-100); } } } @@ -43,7 +45,7 @@ ion-item.item { margin: 3px; border-radius: 50%; &:hover { - background: var(--gray-300); + background: var(--secondary); } } From 4c06b78e8e7984609bb738d5a37d436933b62f89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 16 Mar 2022 13:33:06 +0100 Subject: [PATCH 12/12] MOBILE-3814 notifications: Fix dark mode issues on notifications page --- src/addons/notifications/notifications.scss | 38 ++++++++++++------- src/addons/notifications/pages/list/list.html | 7 ++-- .../pages/notification/notification.html | 6 ++- .../components/user-avatar/user-avatar.scss | 2 - 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/addons/notifications/notifications.scss b/src/addons/notifications/notifications.scss index 23e962567..5285ea7f0 100644 --- a/src/addons/notifications/notifications.scss +++ b/src/addons/notifications/notifications.scss @@ -1,29 +1,39 @@ @import "~theme/globals"; :host { - ::ng-deep { - core-user-avatar .core-avatar-extra { + --extra-icon-size: 16px; + + ::ng-deep core-user-avatar { + .core-avatar-extra-img, + core-mod-icon { margin: 0 !important; position: absolute; right: -4px; bottom: -4px; - } - core-user-avatar img.core-avatar-extra { - background: none; - width: 24px; - height: 24px; - border-radius: 0 !important; - } - core-user-avatar core-mod-icon.core-avatar-extra { - --size: 16px; padding: 0.2rem; } + + .core-avatar-extra-img { + background: var(--background-color); + border-radius: var(--medium-radius); + img { + max-width: var(--extra-icon-size); + max-height: var(--extra-icon-size); + display: block; + } + } + + core-mod-icon { + --size: var(--extra-icon-size); + } } .core-notification-icon { - width: 34px; - height: 34px; - margin: 10px !important; + width: var(--core-avatar-size); + height: var(--core-avatar-size); + @include margin(6px, 8px, 6px, 0px); + background: var(--background-color); + border-radius: var(--small-radius); } .item core-format-text ::ng-deep { diff --git a/src/addons/notifications/pages/list/list.html b/src/addons/notifications/pages/list/list.html index d3b9fe72e..bac0b73c5 100644 --- a/src/addons/notifications/pages/list/list.html +++ b/src/addons/notifications/pages/list/list.html @@ -36,10 +36,11 @@ - +
+ +
+ [showAlt]="false">
diff --git a/src/addons/notifications/pages/notification/notification.html b/src/addons/notifications/pages/notification/notification.html index 371835ddb..c0566dee0 100644 --- a/src/addons/notifications/pages/notification/notification.html +++ b/src/addons/notifications/pages/notification/notification.html @@ -13,8 +13,10 @@ - - +
+ +
+
diff --git a/src/core/components/user-avatar/user-avatar.scss b/src/core/components/user-avatar/user-avatar.scss index ad04a1ec8..38eea5500 100644 --- a/src/core/components/user-avatar/user-avatar.scss +++ b/src/core/components/user-avatar/user-avatar.scss @@ -78,8 +78,6 @@ } :host-context(ion-item) { - margin-top: 6px; - margin-bottom: 6px; @include margin(6px, 8px, 6px, 0px); img { min-width: var(--core-avatar-size);