MOBILE-3320 core: Add format text placeholder when loading

main
Pau Ferrer Ocaña 2021-06-04 12:25:30 +02:00
parent 530ac1a109
commit 64dd36a5e2
5 changed files with 104 additions and 30 deletions

View File

@ -72,6 +72,7 @@ export class CoreFormatTextDirective implements OnChanges {
@Input() wsNotFiltered?: boolean | string; // If true it means the WS didn't filter the text for some reason. @Input() wsNotFiltered?: boolean | string; // If true it means the WS didn't filter the text for some reason.
@Input() captureLinks?: boolean; // Whether links should tried to be opened inside the app. Defaults to true. @Input() captureLinks?: boolean; // Whether links should tried to be opened inside the app. Defaults to true.
@Input() openLinksInApp?: boolean; // Whether links should be opened in InAppBrowser. @Input() openLinksInApp?: boolean; // Whether links should be opened in InAppBrowser.
@Input() hideIfEmpty = false; // If true, the tag will contain nothing if text is empty.
/** /**
* Max height in pixels to render the content box. It should be 50 at least to make sense. * Max height in pixels to render the content box. It should be 50 at least to make sense.
@ -86,6 +87,8 @@ export class CoreFormatTextDirective implements OnChanges {
protected element: HTMLElement; protected element: HTMLElement;
protected showMoreDisplayed = false; protected showMoreDisplayed = false;
protected loadingChangedListener?: CoreEventObserver; protected loadingChangedListener?: CoreEventObserver;
protected emptyText = '';
protected contentSpan: HTMLElement;
constructor( constructor(
element: ElementRef, element: ElementRef,
@ -93,9 +96,20 @@ export class CoreFormatTextDirective implements OnChanges {
protected viewContainerRef: ViewContainerRef, protected viewContainerRef: ViewContainerRef,
protected sanitizer: DomSanitizer, protected sanitizer: DomSanitizer,
) { ) {
this.element = element.nativeElement; this.element = element.nativeElement;
this.element.classList.add('opacity-hide'); // Hide contents until they're treated. this.element.classList.add('core-format-text-loading'); // Hide contents until they're treated.
const placeholder = document.createElement('span');
placeholder.classList.add('core-format-text-loader');
this.element.appendChild(placeholder);
this.contentSpan = document.createElement('span');
this.contentSpan.classList.add('core-format-text-content');
this.element.appendChild(this.contentSpan);
this.emptyText = this.hideIfEmpty ? '' : ' ';
this.contentSpan.innerHTML = this.emptyText;
this.afterRender = new EventEmitter<void>(); this.afterRender = new EventEmitter<void>();
this.element.addEventListener('click', this.elementClicked.bind(this)); this.element.addEventListener('click', this.elementClicked.bind(this));
@ -183,7 +197,7 @@ export class CoreFormatTextDirective implements OnChanges {
* Add magnifying glass icons to view adapted images at full size. * Add magnifying glass icons to view adapted images at full size.
*/ */
addMagnifyingGlasses(): void { addMagnifyingGlasses(): void {
const imgs = Array.from(this.element.querySelectorAll('.core-adapted-img-container > img')); const imgs = Array.from(this.contentSpan.querySelectorAll('.core-adapted-img-container > img'));
if (!imgs.length) { if (!imgs.length) {
return; return;
} }
@ -339,7 +353,7 @@ export class CoreFormatTextDirective implements OnChanges {
*/ */
protected finishRender(): void { protected finishRender(): void {
// Show the element again. // Show the element again.
this.element.classList.remove('opacity-hide'); this.element.classList.remove('core-format-text-loading');
// Emit the afterRender output. // Emit the afterRender output.
this.afterRender.emit(); this.afterRender.emit();
} }
@ -349,7 +363,7 @@ export class CoreFormatTextDirective implements OnChanges {
*/ */
protected async formatAndRenderContents(): Promise<void> { protected async formatAndRenderContents(): Promise<void> {
if (!this.text) { if (!this.text) {
this.element.innerHTML = ''; // Remove current contents. this.contentSpan.innerHTML = this.emptyText; // Remove current contents.
this.finishRender(); this.finishRender();
return; return;
@ -370,12 +384,12 @@ export class CoreFormatTextDirective implements OnChanges {
// Disable media adapt to correctly calculate the height. // Disable media adapt to correctly calculate the height.
this.element.classList.add('core-disable-media-adapt'); this.element.classList.add('core-disable-media-adapt');
this.element.innerHTML = ''; // Remove current contents. this.contentSpan.innerHTML = ''; // Remove current contents.
if (this.maxHeight && result.div.innerHTML != '' && if (this.maxHeight && result.div.innerHTML != '' &&
(this.fullOnClick || (window.innerWidth < 576 || window.innerHeight < 576))) { // Don't collapse in big screens. (this.fullOnClick || (window.innerWidth < 576 || window.innerHeight < 576))) { // Don't collapse in big screens.
// Move the children to the current element to be able to calculate the height. // Move the children to the current element to be able to calculate the height.
CoreDomUtils.moveChildren(result.div, this.element); CoreDomUtils.moveChildren(result.div, this.contentSpan);
// Calculate the height now. // Calculate the height now.
this.calculateHeight(); this.calculateHeight();
@ -396,7 +410,7 @@ export class CoreFormatTextDirective implements OnChanges {
}); });
} }
} else { } else {
CoreDomUtils.moveChildren(result.div, this.element); CoreDomUtils.moveChildren(result.div, this.contentSpan);
// Add magnifying glasses to images. // Add magnifying glasses to images.
this.addMagnifyingGlasses(); this.addMagnifyingGlasses();
@ -405,7 +419,7 @@ export class CoreFormatTextDirective implements OnChanges {
if (result.options.filter) { if (result.options.filter) {
// Let filters handle HTML. We do it here because we don't want them to block the render of the text. // Let filters handle HTML. We do it here because we don't want them to block the render of the text.
CoreFilterDelegate.handleHtml( CoreFilterDelegate.handleHtml(
this.element, this.contentSpan,
result.filters, result.filters,
this.viewContainerRef, this.viewContainerRef,
result.options, result.options,

View File

@ -66,7 +66,7 @@ describe('CoreFormatTextDirective', () => {
); );
// Assert // Assert
const text = fixture.nativeElement.querySelector('core-format-text'); const text = fixture.nativeElement.querySelector('core-format-text .core-format-text-content');
expect(text).not.toBeNull(); expect(text).not.toBeNull();
expect(text.innerHTML).toEqual(sentence); expect(text.innerHTML).toEqual(sentence);
}); });

View File

@ -142,7 +142,10 @@
</ion-item-divider> </ion-item-divider>
<ion-item class="ion-text-wrap" *ngIf="section.summary"> <ion-item class="ion-text-wrap" *ngIf="section.summary">
<core-format-text [text]="section.summary" contextLevel="course" [contextInstanceId]="course?.id"></core-format-text> <ion-label>
<core-format-text [text]="section.summary" contextLevel="course" [contextInstanceId]="course?.id">
</core-format-text>
</ion-label>
</ion-item> </ion-item>
<ng-container *ngFor="let module of section.modules"> <ng-container *ngFor="let module of section.modules">

View File

@ -9,6 +9,7 @@
clip: rect(0, 0, 0, 0); clip: rect(0, 0, 0, 0);
white-space: nowrap; white-space: nowrap;
border: 0; border: 0;
display: block !important;
} }
.sr-only-focusable:active, .sr-only-focusable:focus { .sr-only-focusable:active, .sr-only-focusable:focus {

View File

@ -2,22 +2,70 @@
/** Styles of elements inside the directive should be placed in format-text.scss */ /** Styles of elements inside the directive should be placed in format-text.scss */
@import "~theme/globals"; @import "~theme/globals";
:root { core-format-text {
--background: var(--background, #{$ion-item-background}); --core-format-text-background: var(--background, #{$ion-item-background});
--background-gradient-rgb: var(--background-rgb, #{color-to-rgb-list($ion-item-background)}); --core-format-text-background-gradient-rgb: var(--background-rgb, #{color-to-rgb-list($ion-item-background)});
--viewer-icon-background: rgba(255, 255, 255, .5); --core-format-text-viewer-icon-background: rgba(255, 255, 255, .5);
--core-format-text-loader-shine: 251,251,251;
} }
:root body.dark { body.dark core-format-text {
--background: var(--background, #{$ion-item-background-dark}); --core-format-text-background: var(--background, #{$ion-item-background-dark});
--background-gradient-rgb: var(--background-rgb, #{color-to-rgb-list($ion-item-background-dark)}); --core-format-text-background-gradient-rgb: var(--background-rgb, #{color-to-rgb-list($ion-item-background-dark)});
--viewer-icon-background: rgba(0, 0, 0, .5); --core-format-text-viewer-icon-background: rgba(0, 0, 0, .5);
--core-format-text-loader-shine: 90,90,90;
} }
core-format-text { core-format-text {
display: contents;
.core-format-text-loader {
opacity: 0;
@include core-transition(opacity, 200ms);
display: contents;
}
&.core-format-text-loading {
position: relative;
width: 100%;
height: 100%;
opacity: 1;
background-color: rgba(0,0,0,.1);
overflow: hidden;
border-radius: 5px;
display: block;
.core-format-text-loader {
position: absolute;
left: -45%;
height: 100%;
width: 45%;
background-image: -webkit-linear-gradient(to left, rgba(var(--core-format-text-loader-shine), .05), rgba(var(--core-format-text-loader-shine), .3), rgba(var(--core-format-text-loader-shine), .6), rgba(var(--core-format-text-loader-shine), .3), rgba(var(--core-format-text-loader-shine), .05));
background-image: linear-gradient(to left, rgba(var(--core-format-text-loader-shine), .05), rgba(var(--core-format-text-loader-shine), .3), rgba(var(--core-format-text-loader-shine), .6), rgba(var(--core-format-text-loader-shine), .3), rgba(var(--core-format-text-loader-shine), .05));
animation: loading 1s infinite;
opacity: 1;
display: inline;
}
.core-format-text-content {
opacity: 0;
display: inline;
}
.core-show-more {
display: none !important;
}
}
.core-format-text-content {
opacity: 1;
@include core-transition(opacity, 200ms);
display: contents;
user-select: text; user-select: text;
word-break: break-word; word-break: break-word;
word-wrap: break-word; word-wrap: break-word;
}
&[maxHeight], &[maxHeight],
&[ng-reflect-max-height] { &[ng-reflect-max-height] {
@ -57,7 +105,7 @@ core-format-text {
position: absolute; position: absolute;
@include position(null, 0, 0, null); @include position(null, 0, 0, null);
z-index: 7; z-index: 7;
background-color: var(--background); background-color: var(--core-format-text-background);
color: var(--text-color); color: var(--text-color);
@include padding(null, null, null, 10px); @include padding(null, null, null, 10px);
margin: 0; margin: 0;
@ -68,10 +116,8 @@ core-format-text {
height: 100%; height: 100%;
position: absolute; position: absolute;
@include position(null, 0, 0, 0); @include position(null, 0, 0, 0);
background: -moz-linear-gradient(top, rgba(var(--background-gradient-rgb), 0) calc(100% - 50px), rgba(var(--background-gradient-rgb), 1) calc(100% - 15px)); background: -webkit-linear-gradient(top, rgba(var(--core-format-text-background-gradient-rgb), 0) calc(100% - 50px), rgba(var(--core-format-text-background-gradient-rgb), 1) calc(100% - 15px));
background: -webkit-gradient(left top, left bottom, color-stop(calc(100% - 50px), rgba(var(--background-gradient-rgb), 0)), color-stop(calc(100% - 15px), rgba(var(--background-gradient-rgb), 1))); background: linear-gradient(to bottom, rgba(var(--core-format-text-background-gradient-rgb), 0) calc(100% - 50px), rgba(var(--core-format-text-background-gradient-rgb), 1) calc(100% - 15px));
background: -webkit-linear-gradient(top, rgba(var(--background-gradient-rgb), 0) calc(100% - 50px), rgba(var(--background-gradient-rgb), 1) calc(100% - 15px));
background: linear-gradient(to bottom, rgba(var(--background-gradient-rgb), 0) calc(100% - 50px), rgba(var(--background-gradient-rgb), 1) calc(100% - 15px));
z-index: 6; z-index: 6;
} }
} }
@ -115,15 +161,15 @@ core-format-text {
.core-adapted-img-container { .core-adapted-img-container {
position: relative; position: relative;
display: inline-block; display: inline-block;
width: 100%; max-width: 100%;
} }
.core-image-viewer-icon { .core-image-viewer-icon {
position: absolute; position: absolute;
@include position(null, 10px, 10px, null); @include position(null, 10px, 10px, null);
color: var(--black); color: var(--ion-text-color);
border-radius: 5px; border-radius: 5px;
background-color: var(--viewer-icon-background); background-color: var(--core-format-text-viewer-icon-background);
display: flex; display: flex;
width: var(--a11y-min-target-size); width: var(--a11y-min-target-size);
@ -141,3 +187,13 @@ core-format-text {
} }
} }
} }
@keyframes loading {
0% {
left: -45%;
}
100% {
left: 100%;
}
}