commit
52f0399b99
|
@ -271,7 +271,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
this.route.queryParams.subscribe(async (params) => {
|
this.route.queryParams.subscribe(async (params) => {
|
||||||
// When a child page loads this callback is triggered too.
|
// 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 });
|
const userId = CoreNavigator.getRouteNumberParam('userId', { params });
|
||||||
if (conversationId || userId) {
|
if (conversationId || userId) {
|
||||||
// Update the selected ones.
|
// Update the selected ones.
|
||||||
|
|
|
@ -1,29 +1,39 @@
|
||||||
@import "~theme/globals";
|
@import "~theme/globals";
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
::ng-deep {
|
--extra-icon-size: 16px;
|
||||||
core-user-avatar .core-avatar-extra {
|
|
||||||
|
::ng-deep core-user-avatar {
|
||||||
|
.core-avatar-extra-img,
|
||||||
|
core-mod-icon {
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: -4px;
|
right: -4px;
|
||||||
bottom: -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;
|
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 {
|
.core-notification-icon {
|
||||||
width: 34px;
|
width: var(--core-avatar-size);
|
||||||
height: 34px;
|
height: var(--core-avatar-size);
|
||||||
margin: 10px !important;
|
@include margin(6px, 8px, 6px, 0px);
|
||||||
|
background: var(--background-color);
|
||||||
|
border-radius: var(--small-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.item core-format-text ::ng-deep {
|
.item core-format-text ::ng-deep {
|
||||||
|
|
|
@ -36,10 +36,11 @@
|
||||||
<core-user-avatar *ngIf="notification.useridfrom > 0" [user]="notification" slot="start"
|
<core-user-avatar *ngIf="notification.useridfrom > 0" [user]="notification" slot="start"
|
||||||
[profileUrl]="notification.profileimageurlfrom" [fullname]="notification.userfromfullname"
|
[profileUrl]="notification.profileimageurlfrom" [fullname]="notification.userfromfullname"
|
||||||
[userId]="notification.useridfrom">
|
[userId]="notification.useridfrom">
|
||||||
<img *ngIf="notification.iconurl && !notification.modname" [src]="notification.iconurl" alt="" role="presentation"
|
<div class="core-avatar-extra-img" *ngIf="notification.iconurl && !notification.modname">
|
||||||
class="core-avatar-extra">
|
<img [src]="notification.iconurl" alt="" role="presentation">
|
||||||
|
</div>
|
||||||
<core-mod-icon *ngIf="notification.modname" [modicon]="notification.iconurl" [modname]="notification.modname"
|
<core-mod-icon *ngIf="notification.modname" [modicon]="notification.iconurl" [modname]="notification.modname"
|
||||||
[showAlt]="false" class="core-avatar-extra">
|
[showAlt]="false">
|
||||||
</core-mod-icon>
|
</core-mod-icon>
|
||||||
</core-user-avatar>
|
</core-user-avatar>
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,10 @@
|
||||||
<ion-item class="ion-text-wrap" [attr.aria-label]="subject">
|
<ion-item class="ion-text-wrap" [attr.aria-label]="subject">
|
||||||
<core-user-avatar *ngIf="userIdFrom > 0" slot="start" [userId]="userIdFrom" [profileUrl]="profileImageUrlFrom"
|
<core-user-avatar *ngIf="userIdFrom > 0" slot="start" [userId]="userIdFrom" [profileUrl]="profileImageUrlFrom"
|
||||||
[fullname]="userFromFullName">
|
[fullname]="userFromFullName">
|
||||||
<img *ngIf="iconUrl && !modname" [src]="iconUrl" alt="" role="presentation" class="core-avatar-extra">
|
<div class="core-avatar-extra-img" *ngIf="iconUrl && !modname">
|
||||||
<core-mod-icon *ngIf="modname" [modicon]="iconUrl" [modname]="modname" [showAlt]="false" class="core-avatar-extra">
|
<img [src]="iconUrl" alt="" role="presentation">
|
||||||
|
</div>
|
||||||
|
<core-mod-icon *ngIf="modname" [modicon]="iconUrl" [modname]="modname" [showAlt]="false">
|
||||||
</core-mod-icon>
|
</core-mod-icon>
|
||||||
</core-user-avatar>
|
</core-user-avatar>
|
||||||
|
|
||||||
|
|
|
@ -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<void>;
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ import { CoreAnimations } from '@components/animations';
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||||
import { CorePromisedValue } from '@classes/promised-value';
|
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.
|
* 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'],
|
styleUrls: ['loading.scss'],
|
||||||
animations: [CoreAnimations.SHOW_HIDE],
|
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() hideUntil: unknown; // Determine when should the contents be shown.
|
||||||
@Input() message?: string; // Message to show while loading.
|
@Input() message?: string; // Message to show while loading.
|
||||||
|
@ -58,7 +59,7 @@ export class CoreLoadingComponent implements OnInit, OnChanges, AfterViewInit {
|
||||||
uniqueId: string;
|
uniqueId: string;
|
||||||
protected element: HTMLElement; // Current element.
|
protected element: HTMLElement; // Current element.
|
||||||
loaded = false; // Only comes true once.
|
loaded = false; // Only comes true once.
|
||||||
protected firstLoadedPromise = new CorePromisedValue<string>();
|
protected onReadyPromise = new CorePromisedValue<void>();
|
||||||
|
|
||||||
constructor(element: ElementRef) {
|
constructor(element: ElementRef) {
|
||||||
this.element = element.nativeElement;
|
this.element = element.nativeElement;
|
||||||
|
@ -108,7 +109,7 @@ export class CoreLoadingComponent implements OnInit, OnChanges, AfterViewInit {
|
||||||
|
|
||||||
if (!this.loaded && loaded) {
|
if (!this.loaded && loaded) {
|
||||||
this.loaded = true; // Only comes true once.
|
this.loaded = true; // Only comes true once.
|
||||||
this.firstLoadedPromise.resolve(this.uniqueId);
|
this.onReadyPromise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event has been deprecated since app 4.0.
|
// Event has been deprecated since app 4.0.
|
||||||
|
@ -119,12 +120,10 @@ export class CoreLoadingComponent implements OnInit, OnChanges, AfterViewInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wait the loading to finish.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @return Promise resolved with the uniqueId when done.
|
|
||||||
*/
|
*/
|
||||||
async whenLoaded(): Promise<string> {
|
async ready(): Promise<void> {
|
||||||
return await this.firstLoadedPromise;
|
return await this.onReadyPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
</core-progress-bar>
|
</core-progress-bar>
|
||||||
</ion-col>
|
</ion-col>
|
||||||
<ion-col class="ion-text-end ion-no-padding core-navigation-arrow" size="auto">
|
<ion-col class="ion-text-end ion-no-padding core-navigation-arrow" size="auto">
|
||||||
<ion-button [disabled]="nextIndex < 0" fill="clear" [attr.aria-label]="nextTitle" (click)="navigate(nextIndex)">
|
<ion-button [disabled]="nextIndex >= items.length" fill="clear" [attr.aria-label]="nextTitle" (click)="navigate(nextIndex)">
|
||||||
<ion-icon name="fas-chevron-right" slot="icon-only" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-chevron-right" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ion-col>
|
</ion-col>
|
||||||
|
|
|
@ -67,15 +67,21 @@ export class CoreNavigationBarComponent implements OnChanges {
|
||||||
this.progress = ((this.currentIndex + 1) / this.items.length) * 100;
|
this.progress = ((this.currentIndex + 1) / this.items.length) * 100;
|
||||||
this.progressText = `${this.currentIndex + 1} / ${this.items.length}`;
|
this.progressText = `${this.currentIndex + 1} / ${this.items.length}`;
|
||||||
|
|
||||||
this.nextIndex = this.items[this.currentIndex + 1]?.enabled ? this.currentIndex + 1 : -1;
|
this.nextIndex = this.currentIndex + 1;
|
||||||
if (this.nextIndex >= 0) {
|
while (this.items[this.nextIndex] && !this.items[this.nextIndex].enabled) {
|
||||||
this.nextTitle = Translate.instant(this.nextTranslate, { $a: this.items[this.nextIndex].title || '' });
|
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;
|
this.previousIndex = this.currentIndex - 1;
|
||||||
if (this.previousIndex >= 0) {
|
while (this.items[this.previousIndex] && !this.items[this.previousIndex].enabled) {
|
||||||
this.previousTitle = Translate.instant(this.previousTranslate, { $a: this.items[this.previousIndex].title || '' });
|
this.previousIndex--;
|
||||||
}
|
}
|
||||||
|
this.previousTitle = this.items[this.previousIndex]
|
||||||
|
? Translate.instant(this.previousTranslate, { $a: this.items[this.previousIndex].title || '' })
|
||||||
|
: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -78,8 +78,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
:host-context(ion-item) {
|
:host-context(ion-item) {
|
||||||
margin-top: 6px;
|
|
||||||
margin-bottom: 6px;
|
|
||||||
@include margin(6px, 8px, 6px, 0px);
|
@include margin(6px, 8px, 6px, 0px);
|
||||||
img {
|
img {
|
||||||
min-width: var(--core-avatar-size);
|
min-width: var(--core-avatar-size);
|
||||||
|
|
|
@ -66,7 +66,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
|
||||||
|
|
||||||
await this.domPromise;
|
await this.domPromise;
|
||||||
await this.waitLoadingsDone();
|
await this.waitLoadingsDone();
|
||||||
await this.waitFormatTextsRendered(this.element);
|
await this.waitFormatTextsRendered();
|
||||||
|
|
||||||
this.content = this.element.closest('ion-content');
|
this.content = this.element.closest('ion-content');
|
||||||
|
|
||||||
|
@ -156,15 +156,9 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wait until all <core-format-text> children inside the element are done rendering.
|
* Wait until all <core-format-text> children inside the element are done rendering.
|
||||||
*
|
|
||||||
* @param element Element.
|
|
||||||
*/
|
*/
|
||||||
protected async waitFormatTextsRendered(element: Element): Promise<void> {
|
protected async waitFormatTextsRendered(): Promise<void> {
|
||||||
await CoreComponentsRegistry.finishRenderingAllElementsInside<CoreFormatTextDirective>(
|
await CoreComponentsRegistry.waitComponentsReady(this.element, 'core-format-text', CoreFormatTextDirective);
|
||||||
element,
|
|
||||||
'core-format-text',
|
|
||||||
'rendered',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -210,13 +204,8 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
|
||||||
const scrollElement = await this.ionContent.getScrollElement();
|
const scrollElement = await this.ionContent.getScrollElement();
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
await CoreComponentsRegistry.finishRenderingAllElementsInside<CoreLoadingComponent>
|
await CoreComponentsRegistry.waitComponentsReady(scrollElement, 'core-loading', CoreLoadingComponent),
|
||||||
(scrollElement, 'core-loading', 'whenLoaded'),
|
await CoreComponentsRegistry.waitComponentsReady(this.element, 'core-loading', CoreLoadingComponent),
|
||||||
await CoreComponentsRegistry.finishRenderingAllElementsInside<CoreLoadingComponent>(
|
|
||||||
this.element,
|
|
||||||
'core-loading',
|
|
||||||
'whenLoaded',
|
|
||||||
),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,7 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
|
||||||
protected enabled = true;
|
protected enabled = true;
|
||||||
protected isWithinContent = false;
|
protected isWithinContent = false;
|
||||||
protected enteredPromise = new CorePromisedValue<void>();
|
protected enteredPromise = new CorePromisedValue<void>();
|
||||||
|
protected mutationObserver?: MutationObserver;
|
||||||
|
|
||||||
constructor(el: ElementRef) {
|
constructor(el: ElementRef) {
|
||||||
this.collapsedHeader = el.nativeElement;
|
this.collapsedHeader = el.nativeElement;
|
||||||
|
@ -126,6 +127,7 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
|
||||||
}
|
}
|
||||||
|
|
||||||
this.resizeListener?.off();
|
this.resizeListener?.off();
|
||||||
|
this.mutationObserver?.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -161,6 +163,31 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
|
||||||
this.subscriptions.push(CoreSettingsHelper.onDarkModeChange().subscribe(() => {
|
this.subscriptions.push(CoreSettingsHelper.onDarkModeChange().subscribe(() => {
|
||||||
this.initializeFloatingTitle();
|
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);
|
floatingTitleWrapper.insertBefore(floatingTitle, originalTitle);
|
||||||
|
|
||||||
originalTitle.classList.add('collapsible-header-original-title');
|
originalTitle.classList.add('collapsible-header-original-title');
|
||||||
|
this.mutationObserver?.observe(originalTitle, { childList: true, subtree: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
const floatingTitleBoundingBox = floatingTitle.getBoundingClientRect();
|
const floatingTitleBoundingBox = floatingTitle.getBoundingClientRect();
|
||||||
|
@ -304,15 +332,13 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wait until all <core-loading> children inside the page.
|
* Wait until all <core-loading> children inside the page.
|
||||||
*
|
|
||||||
* @return Promise resolved when loadings are done.
|
|
||||||
*/
|
*/
|
||||||
protected async waitLoadingsDone(): Promise<void> {
|
protected async waitLoadingsDone(): Promise<void> {
|
||||||
await CoreComponentsRegistry.finishRenderingAllElementsInside<CoreLoadingComponent>(
|
if (!this.page) {
|
||||||
this.page,
|
return;
|
||||||
'core-loading',
|
}
|
||||||
'whenLoaded',
|
|
||||||
);
|
await CoreComponentsRegistry.waitComponentsReady(this.page, 'core-loading', CoreLoadingComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -322,11 +348,7 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
|
||||||
* @return Promise resolved when texts are rendered.
|
* @return Promise resolved when texts are rendered.
|
||||||
*/
|
*/
|
||||||
protected async waitFormatTextsRendered(element: Element): Promise<void> {
|
protected async waitFormatTextsRendered(element: Element): Promise<void> {
|
||||||
await CoreComponentsRegistry.finishRenderingAllElementsInside<CoreFormatTextDirective>(
|
await CoreComponentsRegistry.waitComponentsReady(element, 'core-format-text', CoreFormatTextDirective);
|
||||||
element,
|
|
||||||
'core-format-text',
|
|
||||||
'rendered',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -16,7 +16,6 @@ import { Directive, ElementRef, Input, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
||||||
import { CoreLoadingComponent } from '@components/loading/loading';
|
import { CoreLoadingComponent } from '@components/loading/loading';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||||
import { CoreEventObserver } from '@singletons/events';
|
import { CoreEventObserver } from '@singletons/events';
|
||||||
|
@ -103,28 +102,18 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
|
||||||
|
|
||||||
const page = this.element.closest('.ion-page');
|
const page = this.element.closest('.ion-page');
|
||||||
|
|
||||||
await CoreComponentsRegistry.finishRenderingAllElementsInside<CoreLoadingComponent>(page, 'core-loading', 'whenLoaded');
|
if (!page) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await CoreComponentsRegistry.waitComponentsReady(page, 'core-loading', CoreLoadingComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wait until all <core-format-text> children inside the element are done rendering.
|
* Wait until all <core-format-text> children inside the element are done rendering.
|
||||||
*
|
|
||||||
* @param element Element.
|
|
||||||
*/
|
*/
|
||||||
protected async waitFormatTextsRendered(element: Element): Promise<void> {
|
protected async waitFormatTextsRendered(): Promise<void> {
|
||||||
let formatTextElements: HTMLElement[] = [];
|
await CoreComponentsRegistry.waitComponentsReady(this.element, 'core-format-text', CoreFormatTextDirective);
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -134,7 +123,7 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
|
||||||
// Remove max-height (if any) to calculate the real height.
|
// Remove max-height (if any) to calculate the real height.
|
||||||
this.element.classList.add('collapsible-loading-height');
|
this.element.classList.add('collapsible-loading-height');
|
||||||
|
|
||||||
await this.waitFormatTextsRendered(this.element);
|
await this.waitFormatTextsRendered();
|
||||||
|
|
||||||
this.expandedHeight = this.element.getBoundingClientRect().height;
|
this.expandedHeight = this.element.getBoundingClientRect().height;
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ import { CoreCollapsibleItemDirective } from './collapsible-item';
|
||||||
import { CoreCollapsibleFooterDirective } from './collapsible-footer';
|
import { CoreCollapsibleFooterDirective } from './collapsible-footer';
|
||||||
import { CoreContentDirective } from './content';
|
import { CoreContentDirective } from './content';
|
||||||
import { CoreOnAppearDirective } from './on-appear';
|
import { CoreOnAppearDirective } from './on-appear';
|
||||||
|
import { CoreUpdateNonReactiveAttributesDirective } from './update-non-reactive-attributes';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -55,6 +56,7 @@ import { CoreOnAppearDirective } from './on-appear';
|
||||||
CoreCollapsibleItemDirective,
|
CoreCollapsibleItemDirective,
|
||||||
CoreCollapsibleFooterDirective,
|
CoreCollapsibleFooterDirective,
|
||||||
CoreContentDirective,
|
CoreContentDirective,
|
||||||
|
CoreUpdateNonReactiveAttributesDirective,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
CoreAutoFocusDirective,
|
CoreAutoFocusDirective,
|
||||||
|
@ -76,6 +78,7 @@ import { CoreOnAppearDirective } from './on-appear';
|
||||||
CoreCollapsibleItemDirective,
|
CoreCollapsibleItemDirective,
|
||||||
CoreCollapsibleFooterDirective,
|
CoreCollapsibleFooterDirective,
|
||||||
CoreContentDirective,
|
CoreContentDirective,
|
||||||
|
CoreUpdateNonReactiveAttributesDirective,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class CoreDirectivesModule {}
|
export class CoreDirectivesModule {}
|
||||||
|
|
|
@ -43,6 +43,7 @@ import { CoreSubscriptions } from '@singletons/subscriptions';
|
||||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||||
import { CoreCollapsibleItemDirective } from './collapsible-item';
|
import { CoreCollapsibleItemDirective } from './collapsible-item';
|
||||||
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
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
|
* 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({
|
@Directive({
|
||||||
selector: 'core-format-text',
|
selector: 'core-format-text',
|
||||||
})
|
})
|
||||||
export class CoreFormatTextDirective implements OnChanges, OnDestroy {
|
export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncComponent {
|
||||||
|
|
||||||
@ViewChild(CoreCollapsibleItemDirective) collapsible?: CoreCollapsibleItemDirective;
|
@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<void> {
|
async ready(): Promise<void> {
|
||||||
if (!this.element.classList.contains('core-format-text-loading')) {
|
if (!this.element.classList.contains('core-format-text-loading')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ export class CoreOnAppearDirective implements OnInit, OnDestroy {
|
||||||
@Output() onAppear = new EventEmitter();
|
@Output() onAppear = new EventEmitter();
|
||||||
|
|
||||||
private element: HTMLElement;
|
private element: HTMLElement;
|
||||||
protected domPromise?: CoreCancellablePromise<void>;
|
protected visiblePromise?: CoreCancellablePromise<void>;
|
||||||
|
|
||||||
constructor(element: ElementRef) {
|
constructor(element: ElementRef) {
|
||||||
this.element = element.nativeElement;
|
this.element = element.nativeElement;
|
||||||
|
@ -37,9 +37,9 @@ export class CoreOnAppearDirective implements OnInit, OnDestroy {
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
this.domPromise = CoreDomUtils.waitToBeInDOM(this.element);
|
this.visiblePromise = CoreDomUtils.waitToBeVisible(this.element);
|
||||||
|
|
||||||
await this.domPromise;
|
await this.visiblePromise;
|
||||||
|
|
||||||
this.onAppear.emit();
|
this.onAppear.emit();
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ export class CoreOnAppearDirective implements OnInit, OnDestroy {
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.domPromise?.cancel();
|
this.visiblePromise?.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<HTMLIonButtonElement>) {
|
||||||
|
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<void> {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -24,7 +24,7 @@
|
||||||
|
|
||||||
<core-infinite-loading [enabled]="canLoadMore" (action)="showMoreActivities($event)"></core-infinite-loading>
|
<core-infinite-loading [enabled]="canLoadMore" (action)="showMoreActivities($event)"></core-infinite-loading>
|
||||||
</div>
|
</div>
|
||||||
<div collapsible-footer appearOnBottom *ngIf="displayCourseIndex && hasPreviousOrNextSections" slot="fixed">
|
<div collapsible-footer appearOnBottom *ngIf="displayCourseIndex && (previousSection || nextSection)" slot="fixed">
|
||||||
<div class="core-course-section-nav-buttons safe-area-padding-horizontal list-item-limited-width">
|
<div class="core-course-section-nav-buttons safe-area-padding-horizontal list-item-limited-width">
|
||||||
<ion-button *ngIf="previousSection" (click)="sectionChanged(previousSection)" expand="block"
|
<ion-button *ngIf="previousSection" (click)="sectionChanged(previousSection)" expand="block"
|
||||||
[attr.aria-label]="('core.previous' | translate) + ': ' + previousSection.name">
|
[attr.aria-label]="('core.previous' | translate) + ': ' + previousSection.name">
|
||||||
|
|
|
@ -92,7 +92,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
selectedSection?: CoreCourseSection;
|
selectedSection?: CoreCourseSection;
|
||||||
previousSection?: CoreCourseSection;
|
previousSection?: CoreCourseSection;
|
||||||
nextSection?: CoreCourseSection;
|
nextSection?: CoreCourseSection;
|
||||||
hasPreviousOrNextSections = false;
|
|
||||||
allSectionsId: number = CoreCourseProvider.ALL_SECTIONS_ID;
|
allSectionsId: number = CoreCourseProvider.ALL_SECTIONS_ID;
|
||||||
stealthModulesSectionId: number = CoreCourseProvider.STEALTH_MODULES_SECTION_ID;
|
stealthModulesSectionId: number = CoreCourseProvider.STEALTH_MODULES_SECTION_ID;
|
||||||
loaded = false;
|
loaded = false;
|
||||||
|
@ -489,8 +488,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
this.showMoreActivities();
|
this.showMoreActivities();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hasPreviousOrNextSections = !!this.previousSection || !!this.nextSection;
|
|
||||||
|
|
||||||
// Scroll to module if needed. Give more priority to the input.
|
// Scroll to module if needed. Give more priority to the input.
|
||||||
const moduleIdToScroll = this.moduleId && previousValue === undefined ? this.moduleId : moduleId;
|
const moduleIdToScroll = this.moduleId && previousValue === undefined ? this.moduleId : moduleId;
|
||||||
if (moduleIdToScroll) {
|
if (moduleIdToScroll) {
|
||||||
|
@ -507,8 +504,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
CoreCourse.logView(this.course.id, newSection.section, undefined, this.course.fullname),
|
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?.();
|
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<void> {
|
|
||||||
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).
|
* Show more activities (only used when showing all the sections at the same time).
|
||||||
*
|
*
|
||||||
|
|
|
@ -18,6 +18,7 @@ ion-item.item {
|
||||||
|
|
||||||
&.item-current {
|
&.item-current {
|
||||||
--background: var(--primary-tint);
|
--background: var(--primary-tint);
|
||||||
|
--color: var(--gray-900);
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +35,7 @@ ion-item.item {
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--primary-shade);
|
background: var(--primary-shade);
|
||||||
|
color: var(--gray-100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,7 +45,7 @@ ion-item.item {
|
||||||
margin: 3px;
|
margin: 3px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--gray-300);
|
background: var(--secondary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
</ion-icon>
|
</ion-icon>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="course" class="course-container">
|
<div *ngIf="course" class="course-container">
|
||||||
|
<div class="list-item-limited-width">
|
||||||
<ion-item class="ion-text-wrap course-name">
|
<ion-item class="ion-text-wrap course-name">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p *ngIf="course.displayname && course.shortname && course.fullname != course.displayname"
|
<p *ngIf="course.displayname && course.shortname && course.fullname != course.displayname"
|
||||||
|
@ -38,7 +39,8 @@
|
||||||
<ion-chip color="primary" *ngIf="course.categoryname" class="core-course-category ion-text-nowrap">
|
<ion-chip color="primary" *ngIf="course.categoryname" class="core-course-category ion-text-nowrap">
|
||||||
<span class="sr-only">{{ 'core.courses.aria:coursecategory' | translate }}</span>
|
<span class="sr-only">{{ 'core.courses.aria:coursecategory' | translate }}</span>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<core-format-text [text]="course.categoryname" contextLevel="coursecat" [contextInstanceId]="course.categoryid">
|
<core-format-text [text]="course.categoryname" contextLevel="coursecat"
|
||||||
|
[contextInstanceId]="course.categoryid">
|
||||||
</core-format-text>
|
</core-format-text>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-chip>
|
</ion-chip>
|
||||||
|
@ -125,10 +127,12 @@
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</core-loading>
|
</core-loading>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
||||||
<ion-footer *ngIf="course && dataLoaded">
|
<ion-footer *ngIf="course && dataLoaded">
|
||||||
|
<div class="list-item-limited-width">
|
||||||
<ng-container *ngIf="canAccessCourse">
|
<ng-container *ngIf="canAccessCourse">
|
||||||
<ion-button *ngFor="let item of courseMenuHandlers" (click)="openMenuItem(item)" [class]="'ion-text-wrap '+ item.data.class"
|
<ion-button *ngFor="let item of courseMenuHandlers" (click)="openMenuItem(item)" [class]="'ion-text-wrap '+ item.data.class"
|
||||||
expand="block">
|
expand="block">
|
||||||
|
@ -153,4 +157,5 @@
|
||||||
<ion-icon name="fas-eye" slot="start" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-eye" slot="start" aria-hidden="true"></ion-icon>
|
||||||
{{ 'core.course.viewcourse' | translate }}
|
{{ 'core.course.viewcourse' | translate }}
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
</div>
|
||||||
</ion-footer>
|
</ion-footer>
|
||||||
|
|
|
@ -39,11 +39,14 @@
|
||||||
.course-container {
|
.course-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: calc(var(--thumb-height) - var(--big-radius));
|
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 {
|
.course-name {
|
||||||
border-radius: var(--big-radius) var(--big-radius) 0 0;
|
--background: transparent;
|
||||||
box-shadow: var(--drop-shadow-top);
|
|
||||||
ion-label {
|
ion-label {
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,8 @@
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-item class="core-format-progress-list ion-text-wrap list-item-limited-width" collapsible>
|
<div class="core-course-header">
|
||||||
|
<ion-item class="core-format-progress-list ion-text-wrap list-item-limited-width" collapsible>
|
||||||
<ng-container *ngIf="course">
|
<ng-container *ngIf="course">
|
||||||
<div *ngIf="!course.courseImage" #courseThumb slot="start" class="core-course-thumb">
|
<div *ngIf="!course.courseImage" #courseThumb slot="start" class="core-course-thumb">
|
||||||
<ion-icon *ngIf="!course.courseImage" name="fas-graduation-cap" class="course-icon" aria-hidden="true">
|
<ion-icon *ngIf="!course.courseImage" name="fas-graduation-cap" class="course-icon" aria-hidden="true">
|
||||||
|
@ -34,6 +35,6 @@
|
||||||
</core-progress-bar>
|
</core-progress-bar>
|
||||||
</div>
|
</div>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
</ion-item>
|
</div>
|
||||||
<core-tabs-outlet [tabs]="tabs" [hideUntil]="loaded" (ionChange)="tabSelected()"></core-tabs-outlet>
|
<core-tabs-outlet [tabs]="tabs" [hideUntil]="loaded" (ionChange)="tabSelected()"></core-tabs-outlet>
|
||||||
|
|
|
@ -44,4 +44,8 @@
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.core-course-header {
|
||||||
|
background: var(--ion-item-background);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1064,15 +1064,13 @@ export class CoreCourseHelperProvider {
|
||||||
*
|
*
|
||||||
* @param courses Courses array to get info from.
|
* @param courses Courses array to get info from.
|
||||||
* @param prefetch Prefetch information.
|
* @param prefetch Prefetch information.
|
||||||
* @param minCourses Min course to show icon.
|
|
||||||
* @return Resolved with the prefetch information updated when done.
|
* @return Resolved with the prefetch information updated when done.
|
||||||
*/
|
*/
|
||||||
async initPrefetchCoursesIcons(
|
async initPrefetchCoursesIcons(
|
||||||
courses: CoreCourseBasicData[],
|
courses: CoreCourseBasicData[],
|
||||||
prefetch: CorePrefetchStatusInfo,
|
prefetch: CorePrefetchStatusInfo,
|
||||||
minCourses: number = 2,
|
|
||||||
): Promise<CorePrefetchStatusInfo> {
|
): Promise<CorePrefetchStatusInfo> {
|
||||||
if (!courses || courses.length < minCourses) {
|
if (!courses || courses.length <= 0) {
|
||||||
// Not enough courses.
|
// Not enough courses.
|
||||||
prefetch.icon = '';
|
prefetch.icon = '';
|
||||||
|
|
||||||
|
|
|
@ -294,8 +294,11 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
|
||||||
await this.domPromise;
|
await this.domPromise;
|
||||||
|
|
||||||
const page = this.element.closest('.ion-page');
|
const page = this.element.closest('.ion-page');
|
||||||
|
if (!page) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await CoreComponentsRegistry.finishRenderingAllElementsInside<CoreLoadingComponent>(page, 'core-loading', 'whenLoaded');
|
await CoreComponentsRegistry.waitComponentsReady(page, 'core-loading', CoreLoadingComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -100,7 +100,7 @@ export class CoreDomUtilsProvider {
|
||||||
* @param timeout If defined, timeout to wait before rejecting the promise.
|
* @param timeout If defined, timeout to wait before rejecting the promise.
|
||||||
* @return Cancellable promise.
|
* @return Cancellable promise.
|
||||||
*/
|
*/
|
||||||
waitToBeInDOM(element: Element, timeout?: number): CoreCancellablePromise<void> {
|
waitToBeInDOM(element: HTMLElement, timeout?: number): CoreCancellablePromise<void> {
|
||||||
const root = element.getRootNode({ composed: true });
|
const root = element.getRootNode({ composed: true });
|
||||||
|
|
||||||
if (root === document) {
|
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<void> {
|
||||||
|
const domPromise = CoreDomUtils.waitToBeInDOM(element);
|
||||||
|
|
||||||
|
let interval: number | undefined;
|
||||||
|
|
||||||
|
return new CoreCancellablePromise<void>(
|
||||||
|
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.
|
* 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.
|
* This function helps setting up the debounce feature and remove listener easily.
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
import { AsyncComponent } from '@classes/async-component';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -39,7 +40,7 @@ export class CoreComponentsRegistry {
|
||||||
* @param componentClass Component class.
|
* @param componentClass Component class.
|
||||||
* @returns Component instance.
|
* @returns Component instance.
|
||||||
*/
|
*/
|
||||||
static resolve<T = Component>(element?: Element | null, componentClass?: ComponentConstructor<T>): T | null {
|
static resolve<T>(element?: Element | null, componentClass?: ComponentConstructor<T>): T | null {
|
||||||
const instance = (element && this.instances.get(element) as T) ?? null;
|
const instance = (element && this.instances.get(element) as T) ?? null;
|
||||||
|
|
||||||
return instance && (!componentClass || instance instanceof componentClass)
|
return instance && (!componentClass || instance instanceof componentClass)
|
||||||
|
@ -65,36 +66,46 @@ 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 element Root element.
|
||||||
* @param selector Selector to search on parent.
|
* @param componentClass Component class.
|
||||||
* @param fnName Component function that have to be resolved when rendered.
|
|
||||||
* @param params Params of function that have to be resolved when rendered.
|
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
static async finishRenderingAllElementsInside<T = Component>(
|
static async waitComponentReady<T extends AsyncComponent>(
|
||||||
element: Element | undefined | null,
|
element: Element | null,
|
||||||
selector: string,
|
componentClass?: ComponentConstructor<T>,
|
||||||
fnName: string,
|
|
||||||
params?: unknown[],
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!element) {
|
const instance = this.resolve(element, componentClass);
|
||||||
|
if (!instance) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const components = Array
|
await instance.ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<T extends AsyncComponent>(
|
||||||
|
element: Element,
|
||||||
|
selector: string,
|
||||||
|
componentClass?: ComponentConstructor<T>,
|
||||||
|
): Promise<void> {
|
||||||
|
if (element.matches(selector)) {
|
||||||
|
// Element to wait is myself.
|
||||||
|
await CoreComponentsRegistry.waitComponentReady<T>(element, componentClass);
|
||||||
|
} else {
|
||||||
|
await Promise.all(Array
|
||||||
.from(element.querySelectorAll(selector))
|
.from(element.querySelectorAll(selector))
|
||||||
.map(element => CoreComponentsRegistry.resolve<T>(element));
|
.map(element => CoreComponentsRegistry.waitComponentReady<T>(element, componentClass)));
|
||||||
|
|
||||||
await Promise.all(components.map(component => {
|
|
||||||
if (!component) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return component[fnName].apply(component, params);
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Wait for next tick to ensure components are completely rendered.
|
// Wait for next tick to ensure components are completely rendered.
|
||||||
await CoreUtils.nextTick();
|
await CoreUtils.nextTick();
|
||||||
}
|
}
|
||||||
|
@ -105,4 +116,4 @@ export class CoreComponentsRegistry {
|
||||||
* Component constructor.
|
* Component constructor.
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export type ComponentConstructor<T> = { new(...args: any[]): T };
|
export type ComponentConstructor<T = Component> = { new(...args: any[]): T };
|
||||||
|
|
|
@ -80,7 +80,7 @@
|
||||||
|
|
||||||
body.dark {
|
body.dark {
|
||||||
$dark: map-get($base, '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);
|
@include generate-color-variants($color-name, $dark);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue