MOBILE-3025 blocks: Merge blocks and content scroll
parent
9ea37af4c9
commit
8a3acc2dfd
|
@ -226,6 +226,7 @@ ion-app.app-root {
|
||||||
user-select: text;
|
user-select: text;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
|
white-space: normal;
|
||||||
|
|
||||||
&[maxHeight],
|
&[maxHeight],
|
||||||
&[ng-reflect-max-height] {
|
&[ng-reflect-max-height] {
|
||||||
|
|
|
@ -103,7 +103,7 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init editor
|
* Init editor.
|
||||||
*/
|
*/
|
||||||
ngAfterContentInit(): void {
|
ngAfterContentInit(): void {
|
||||||
this.domUtils.isRichTextEditorEnabled().then((enabled) => {
|
this.domUtils.isRichTextEditorEnabled().then((enabled) => {
|
||||||
|
|
|
@ -121,18 +121,14 @@ export class CoreTabComponent implements OnInit, OnDestroy {
|
||||||
this.showHideNavBarButtons(true);
|
this.showHideNavBarButtons(true);
|
||||||
|
|
||||||
// Setup tab scrolling.
|
// Setup tab scrolling.
|
||||||
setTimeout(() => {
|
this.domUtils.waitElementToExist(() => this.content ? this.content.getScrollElement() :
|
||||||
// Workaround to solve undefined this.scroll on tab change.
|
this.element.querySelector('ion-content > .scroll-content')).then((scroll) => {
|
||||||
const scroll: HTMLElement = this.content ? this.content.getScrollElement() :
|
scroll.addEventListener('scroll', (e): void => {
|
||||||
this.element.querySelector('ion-content > .scroll-content');
|
this.tabs.showHideTabs(e.target);
|
||||||
|
});
|
||||||
|
|
||||||
if (scroll) {
|
this.tabs.showHideTabs(scroll);
|
||||||
scroll.onscroll = (e): void => {
|
});
|
||||||
this.tabs.showHideTabs(e.target);
|
|
||||||
};
|
|
||||||
this.tabs.showHideTabs(scroll);
|
|
||||||
}
|
|
||||||
}, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
ion-app.app-root core-block {
|
ion-app.app-root core-block {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
|
||||||
core-loading.core-loading-center {
|
core-loading.core-loading-center {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
@ -2,13 +2,15 @@
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ion-content *ngIf="blocks && blocks.length > 0" [class.core-hide-blocks]="hideBlocks" class="core-course-blocks-side">
|
<div *ngIf="blocks && blocks.length > 0" [class.core-hide-blocks]="hideBlocks" class="core-course-blocks-side">
|
||||||
<core-loading [hideUntil]="dataLoaded" class="core-loading-center">
|
<div class="core-course-blocks-side-scroll">
|
||||||
<ion-list>
|
<core-loading [hideUntil]="dataLoaded" class="core-loading-center">
|
||||||
<!-- Course blocks. -->
|
<ion-list>
|
||||||
<ng-container *ngFor="let block of blocks">
|
<!-- Course blocks. -->
|
||||||
<core-block [block]="block" contextLevel="course" [instanceId]="courseId" [extraData]="{'downloadEnabled': downloadEnabled}"></core-block>
|
<ng-container *ngFor="let block of blocks">
|
||||||
</ng-container>
|
<core-block [block]="block" contextLevel="course" [instanceId]="courseId" [extraData]="{'downloadEnabled': downloadEnabled}"></core-block>
|
||||||
</ion-list>
|
</ng-container>
|
||||||
</core-loading>
|
</ion-list>
|
||||||
</ion-content>
|
</core-loading>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
$core-side-blocks-max-small-width: 300px;
|
$core-side-blocks-max-width: 30%;
|
||||||
$core-side-blocks-min-small-width: 25%;
|
$core-side-blocks-min-width: 280px;
|
||||||
|
|
||||||
$core-side-blocks-max-width: 320px;
|
|
||||||
$core-side-blocks-min-width: 30%;
|
|
||||||
|
|
||||||
.core-course-block-with-blocks > .scroll-content {
|
.core-course-block-with-blocks > .scroll-content {
|
||||||
overflow-y: visible;
|
overflow-y: visible;
|
||||||
|
@ -10,15 +7,8 @@ $core-side-blocks-min-width: 30%;
|
||||||
|
|
||||||
ion-app.app-root core-block-course-blocks {
|
ion-app.app-root core-block-course-blocks {
|
||||||
|
|
||||||
&.core-no-blocks {
|
&.core-no-blocks .core-course-blocks-content {
|
||||||
.core-course-blocks-content > ion-content {
|
height: auto;
|
||||||
height: auto;
|
|
||||||
|
|
||||||
> .scroll-content {
|
|
||||||
overflow-y: visible;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.core-has-blocks {
|
&.core-has-blocks {
|
||||||
|
@ -33,49 +23,51 @@ ion-app.app-root core-block-course-blocks {
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
|
|
||||||
.core-course-blocks-content {
|
.core-course-blocks-content {
|
||||||
min-width: calc(100% - #{($core-side-blocks-max-small-width)});
|
|
||||||
max-width: calc(100% - #{($core-side-blocks-min-small-width)});
|
|
||||||
z-index: 0;
|
|
||||||
flex: 1;
|
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
|
flex-grow: 1;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-content.core-course-blocks-side {
|
div.core-course-blocks-side {
|
||||||
transform: none !important;
|
position: relative;
|
||||||
position: sticky;
|
@include position(auto, 0, auto, auto);
|
||||||
@include position(0, 0, 0, auto);
|
|
||||||
z-index: 30;
|
|
||||||
max-width: $core-side-blocks-max-small-width;
|
|
||||||
min-width: $core-side-blocks-min-small-width;
|
|
||||||
@include border-start(1px, solid, $list-md-border-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include media-breakpoint-up(lg) {
|
|
||||||
.core-course-blocks-content {
|
|
||||||
min-width: calc(100% - #{($core-side-blocks-max-width)});
|
|
||||||
max-width: calc(100% - #{($core-side-blocks-min-width)});
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-content.core-course-blocks-side {
|
|
||||||
max-width: $core-side-blocks-max-width;
|
max-width: $core-side-blocks-max-width;
|
||||||
min-width: $core-side-blocks-min-width;
|
min-width: $core-side-blocks-min-width;
|
||||||
|
@include border-start(1px, solid, $list-md-border-color);
|
||||||
|
|
||||||
|
.core-course-blocks-side-scroll {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 100%;
|
||||||
|
|
||||||
|
&.core-course-blocks-fixed-bottom {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
top: auto;
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
core-block {
|
||||||
|
max-width: $core-side-blocks-max-width;
|
||||||
|
min-width: $core-side-blocks-min-width;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include media-breakpoint-down(sm) {
|
@include media-breakpoint-down(sm) {
|
||||||
// Disable scroll on individual columns.
|
// Disable scroll on individual columns.
|
||||||
.core-course-blocks-content > ion-content,
|
div.core-course-blocks-side {
|
||||||
ion-content.core-course-blocks-side {
|
transform: none !important;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
|
||||||
&.core-hide-blocks {
|
&.core-hide-blocks {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .scroll-content {
|
.core-course-blocks-side-scroll {
|
||||||
overflow-y: visible;
|
transform: none !important;
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,10 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, ViewChildren, Input, OnInit, QueryList, ElementRef, Optional } from '@angular/core';
|
import { Component, ViewChildren, Input, OnInit, QueryList, ElementRef, OnDestroy } from '@angular/core';
|
||||||
import { Content } from 'ionic-angular';
|
import { Content } from 'ionic-angular';
|
||||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
|
import { CoreAppProvider } from '@providers/app';
|
||||||
import { CoreCourseProvider } from '@core/course/providers/course';
|
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||||
import { CoreBlockComponent } from '../block/block';
|
import { CoreBlockComponent } from '../block/block';
|
||||||
import { CoreBlockHelperProvider } from '../../providers/helper';
|
import { CoreBlockHelperProvider } from '../../providers/helper';
|
||||||
|
@ -26,7 +27,7 @@ import { CoreBlockHelperProvider } from '../../providers/helper';
|
||||||
selector: 'core-block-course-blocks',
|
selector: 'core-block-course-blocks',
|
||||||
templateUrl: 'core-block-course-blocks.html',
|
templateUrl: 'core-block-course-blocks.html',
|
||||||
})
|
})
|
||||||
export class CoreBlockCourseBlocksComponent implements OnInit {
|
export class CoreBlockCourseBlocksComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
@Input() courseId: number;
|
@Input() courseId: number;
|
||||||
@Input() hideBlocks = false;
|
@Input() hideBlocks = false;
|
||||||
|
@ -38,13 +39,17 @@ export class CoreBlockCourseBlocksComponent implements OnInit {
|
||||||
blocks = [];
|
blocks = [];
|
||||||
|
|
||||||
protected element: HTMLElement;
|
protected element: HTMLElement;
|
||||||
protected parentContent: HTMLElement;
|
protected lastScroll;
|
||||||
|
protected translationY = 0;
|
||||||
|
protected blocksScrollHeight = 0;
|
||||||
|
protected sideScroll: HTMLElement;
|
||||||
|
protected vpHeight = 0; // Viewport height.
|
||||||
|
protected scrollWorking = false;
|
||||||
|
|
||||||
constructor(private domUtils: CoreDomUtilsProvider, private courseProvider: CoreCourseProvider,
|
constructor(private domUtils: CoreDomUtilsProvider, private courseProvider: CoreCourseProvider,
|
||||||
protected blockHelper: CoreBlockHelperProvider, element: ElementRef,
|
protected blockHelper: CoreBlockHelperProvider, element: ElementRef,
|
||||||
@Optional() content: Content) {
|
protected content: Content, protected appProvider: CoreAppProvider) {
|
||||||
this.element = element.nativeElement;
|
this.element = element.nativeElement;
|
||||||
this.parentContent = content.getElementRef().nativeElement;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -53,9 +58,77 @@ export class CoreBlockCourseBlocksComponent implements OnInit {
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.loadContent().finally(() => {
|
this.loadContent().finally(() => {
|
||||||
this.dataLoaded = true;
|
this.dataLoaded = true;
|
||||||
|
|
||||||
|
window.addEventListener('resize', this.initScroll.bind(this));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup scrolling.
|
||||||
|
*/
|
||||||
|
protected initScroll(): void {
|
||||||
|
const scroll: HTMLElement = this.content && this.content.getScrollElement();
|
||||||
|
|
||||||
|
this.domUtils.waitElementToExist(() => scroll && scroll.querySelector('.core-course-blocks-side')).then((sideElement) => {
|
||||||
|
const contentHeight = parseInt(this.content.getNativeElement().querySelector('.scroll-content').scrollHeight, 10);
|
||||||
|
|
||||||
|
this.sideScroll = scroll.querySelector('.core-course-blocks-side-scroll');
|
||||||
|
this.blocksScrollHeight = this.sideScroll.scrollHeight;
|
||||||
|
this.vpHeight = sideElement.clientHeight;
|
||||||
|
|
||||||
|
// Check if needed and event was not init before.
|
||||||
|
if (this.appProvider.isWide() && this.vpHeight && contentHeight > this.vpHeight &&
|
||||||
|
this.blocksScrollHeight > this.vpHeight) {
|
||||||
|
if (typeof this.lastScroll == 'undefined') {
|
||||||
|
this.lastScroll = 0;
|
||||||
|
scroll.addEventListener('scroll', this.scrollFunction.bind(this));
|
||||||
|
}
|
||||||
|
this.scrollWorking = true;
|
||||||
|
} else {
|
||||||
|
this.sideScroll.style.transform = 'translate(0, 0)';
|
||||||
|
this.sideScroll.classList.remove('core-course-blocks-fixed-bottom');
|
||||||
|
this.scrollWorking = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scroll function that moves the sidebar if needed.
|
||||||
|
*
|
||||||
|
* @param {Event} e Event to get the target from.
|
||||||
|
*/
|
||||||
|
protected scrollFunction(e: Event): void {
|
||||||
|
if (!this.scrollWorking) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const target: any = e.target,
|
||||||
|
top = parseInt(target.scrollTop, 10),
|
||||||
|
goingUp = top < this.lastScroll;
|
||||||
|
if (goingUp) {
|
||||||
|
this.sideScroll.classList.remove('core-course-blocks-fixed-bottom');
|
||||||
|
if (top <= this.translationY ) {
|
||||||
|
// Fixed to top.
|
||||||
|
this.translationY = top;
|
||||||
|
this.sideScroll.style.transform = 'translate(0, ' + this.translationY + 'px)';
|
||||||
|
}
|
||||||
|
} else if (top - this.translationY >= (this.blocksScrollHeight - this.vpHeight)) {
|
||||||
|
// Fixed to bottom.
|
||||||
|
this.sideScroll.classList.add('core-course-blocks-fixed-bottom');
|
||||||
|
this.translationY = top - (this.blocksScrollHeight - this.vpHeight);
|
||||||
|
this.sideScroll.style.transform = 'translate(0, ' + this.translationY + 'px)';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastScroll = top;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component destroyed.
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
window.removeEventListener('resize', this.initScroll);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidate blocks data.
|
* Invalidate blocks data.
|
||||||
*
|
*
|
||||||
|
@ -95,11 +168,13 @@ export class CoreBlockCourseBlocksComponent implements OnInit {
|
||||||
this.element.classList.add('core-has-blocks');
|
this.element.classList.add('core-has-blocks');
|
||||||
this.element.classList.remove('core-no-blocks');
|
this.element.classList.remove('core-no-blocks');
|
||||||
|
|
||||||
this.parentContent.classList.add('core-course-block-with-blocks');
|
this.content.getElementRef().nativeElement.classList.add('core-course-block-with-blocks');
|
||||||
|
|
||||||
|
this.initScroll();
|
||||||
} else {
|
} else {
|
||||||
this.element.classList.remove('core-has-blocks');
|
this.element.classList.remove('core-has-blocks');
|
||||||
this.element.classList.add('core-no-blocks');
|
this.element.classList.add('core-no-blocks');
|
||||||
this.parentContent.classList.remove('core-course-block-with-blocks');
|
this.content.getElementRef().nativeElement.classList.remove('core-course-block-with-blocks');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,69 +6,67 @@
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<core-block-course-blocks [courseId]="course.id" [hideBlocks]="selectedSection && selectedSection.id == allSectionsId && canLoadMore" [downloadEnabled]="downloadEnabled">
|
<core-block-course-blocks [courseId]="course.id" [hideBlocks]="selectedSection && selectedSection.id == allSectionsId && canLoadMore" [downloadEnabled]="downloadEnabled">
|
||||||
<ion-content>
|
<!-- Default course format. -->
|
||||||
<!-- Default course format. -->
|
<core-dynamic-component [component]="courseFormatComponent" [data]="data">
|
||||||
<core-dynamic-component [component]="courseFormatComponent" [data]="data">
|
<core-loading [hideUntil]="loaded">
|
||||||
<core-loading [hideUntil]="loaded">
|
<!-- Section selector. -->
|
||||||
<!-- Section selector. -->
|
<core-dynamic-component [component]="sectionSelectorComponent" [data]="data">
|
||||||
<core-dynamic-component [component]="sectionSelectorComponent" [data]="data">
|
<div text-wrap *ngIf="displaySectionSelector && sections && sections.length" padding class="clearfix" ion-row justify-content-between class="safe-padding-horizontal" [class.core-section-download]="downloadEnabled">
|
||||||
<div text-wrap *ngIf="displaySectionSelector && sections && sections.length" padding class="clearfix" ion-row justify-content-between class="safe-padding-horizontal" [class.core-section-download]="downloadEnabled">
|
<button float-start ion-button icon-start icon-end (click)="showSectionSelector($event)" color="light" class="core-button-select button-no-uppercase" ion-col [attr.aria-label]="('core.course.sections' | translate) + ': ' + (selectedSection && (selectedSection.formattedName || selectedSection.name))" aria-haspopup="true" [attr.aria-expanded]="sectionSelectorExpanded" aria-controls="core-course-section-selector" id="core-course-section-button">
|
||||||
<button float-start ion-button icon-start icon-end (click)="showSectionSelector($event)" color="light" class="core-button-select button-no-uppercase" ion-col [attr.aria-label]="('core.course.sections' | translate) + ': ' + (selectedSection && (selectedSection.formattedName || selectedSection.name))" aria-haspopup="true" [attr.aria-expanded]="sectionSelectorExpanded" aria-controls="core-course-section-selector" id="core-course-section-button">
|
<core-icon name="fa-folder"></core-icon>
|
||||||
<core-icon name="fa-folder"></core-icon>
|
<span class="core-section-selector-text">{{selectedSection && (selectedSection.formattedName || selectedSection.name) || 'core.course.sections' | translate }}</span>
|
||||||
<span class="core-section-selector-text">{{selectedSection && (selectedSection.formattedName || selectedSection.name) || 'core.course.sections' | translate }}</span>
|
<ion-icon name="arrow-dropdown" ios="md-arrow-dropdown"></ion-icon>
|
||||||
<ion-icon name="arrow-dropdown" ios="md-arrow-dropdown"></ion-icon>
|
</button>
|
||||||
</button>
|
<!-- Section download. -->
|
||||||
<!-- Section download. -->
|
<ng-container *ngTemplateOutlet="sectionDownloadTemplate; context: {section: selectedSection}"></ng-container>
|
||||||
<ng-container *ngTemplateOutlet="sectionDownloadTemplate; context: {section: selectedSection}"></ng-container>
|
</div>
|
||||||
|
</core-dynamic-component>
|
||||||
|
|
||||||
|
<!-- Course summary. By default we only display the course progress. -->
|
||||||
|
<core-dynamic-component [component]="courseSummaryComponent" [data]="data">
|
||||||
|
<ion-list no-lines *ngIf="course.imageThumb || (selectedSection && selectedSection.id == allSectionsId && course.progress != null && course.progress >= 0)" class="core-format-progress-list">
|
||||||
|
<div *ngIf="course.imageThumb" class="core-course-thumb">
|
||||||
|
<img [src]="course.imageThumb" core-external-content alt=""/>
|
||||||
</div>
|
</div>
|
||||||
|
<ion-item class="core-course-progress" *ngIf="selectedSection && selectedSection.id == allSectionsId && course.progress != null && course.progress >= 0 && course.completionusertracked !== false">
|
||||||
|
<core-progress-bar [progress]="course.progress"></core-progress-bar>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
</core-dynamic-component>
|
||||||
|
|
||||||
|
<!-- Single section. -->
|
||||||
|
<div *ngIf="selectedSection && selectedSection.id != allSectionsId">
|
||||||
|
<core-dynamic-component [component]="singleSectionComponent" [data]="data">
|
||||||
|
<ng-container *ngTemplateOutlet="sectionTemplate; context: {section: selectedSection}"></ng-container>
|
||||||
|
<core-empty-box *ngIf="!selectedSection.hasContent" icon="qr-scanner" [message]="'core.course.nocontentavailable' | translate"></core-empty-box>
|
||||||
</core-dynamic-component>
|
</core-dynamic-component>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Course summary. By default we only display the course progress. -->
|
<!-- Multiple sections. -->
|
||||||
<core-dynamic-component [component]="courseSummaryComponent" [data]="data">
|
<div *ngIf="selectedSection && selectedSection.id == allSectionsId">
|
||||||
<ion-list no-lines *ngIf="course.imageThumb || (selectedSection && selectedSection.id == allSectionsId && course.progress != null && course.progress >= 0)" class="core-format-progress-list">
|
<core-dynamic-component [component]="allSectionsComponent" [data]="data">
|
||||||
<div *ngIf="course.imageThumb" class="core-course-thumb">
|
<ng-container *ngFor="let section of sections; index as i">
|
||||||
<img [src]="course.imageThumb" core-external-content alt=""/>
|
<ng-container *ngIf="i <= showSectionId">
|
||||||
</div>
|
<ng-container *ngTemplateOutlet="sectionTemplate; context: {section: section}"></ng-container>
|
||||||
<ion-item class="core-course-progress" *ngIf="selectedSection && selectedSection.id == allSectionsId && course.progress != null && course.progress >= 0 && course.completionusertracked !== false">
|
|
||||||
<core-progress-bar [progress]="course.progress"></core-progress-bar>
|
|
||||||
</ion-item>
|
|
||||||
</ion-list>
|
|
||||||
</core-dynamic-component>
|
|
||||||
|
|
||||||
<!-- Single section. -->
|
|
||||||
<div *ngIf="selectedSection && selectedSection.id != allSectionsId">
|
|
||||||
<core-dynamic-component [component]="singleSectionComponent" [data]="data">
|
|
||||||
<ng-container *ngTemplateOutlet="sectionTemplate; context: {section: selectedSection}"></ng-container>
|
|
||||||
<core-empty-box *ngIf="!selectedSection.hasContent" icon="qr-scanner" [message]="'core.course.nocontentavailable' | translate"></core-empty-box>
|
|
||||||
</core-dynamic-component>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Multiple sections. -->
|
|
||||||
<div *ngIf="selectedSection && selectedSection.id == allSectionsId">
|
|
||||||
<core-dynamic-component [component]="allSectionsComponent" [data]="data">
|
|
||||||
<ng-container *ngFor="let section of sections; index as i">
|
|
||||||
<ng-container *ngIf="i <= showSectionId">
|
|
||||||
<ng-container *ngTemplateOutlet="sectionTemplate; context: {section: section}"></ng-container>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</core-dynamic-component>
|
</ng-container>
|
||||||
|
</core-dynamic-component>
|
||||||
|
|
||||||
<core-infinite-loading [enabled]="canLoadMore" (action)="showMoreActivities($event)"></core-infinite-loading>
|
<core-infinite-loading [enabled]="canLoadMore" (action)="showMoreActivities($event)"></core-infinite-loading>
|
||||||
</div>
|
</div>
|
||||||
</core-loading>
|
</core-loading>
|
||||||
<ion-buttons padding end class="core-course-section-nav-buttons" *ngIf="displaySectionSelector && sections && sections.length">
|
<ion-buttons padding end class="core-course-section-nav-buttons" *ngIf="displaySectionSelector && sections && sections.length">
|
||||||
<button *ngIf="previousSection" ion-button color="light" icon-only (click)="sectionChanged(previousSection)" title="{{ 'core.previous' | translate }}">
|
<button *ngIf="previousSection" ion-button color="light" icon-only (click)="sectionChanged(previousSection)" title="{{ 'core.previous' | translate }}">
|
||||||
<ion-icon name="arrow-back" md="ios-arrow-back"></ion-icon>
|
<ion-icon name="arrow-back" md="ios-arrow-back"></ion-icon>
|
||||||
<core-format-text class="accesshide" [text]="previousSection.formattedName || previousSection.name"></core-format-text>
|
<core-format-text class="accesshide" [text]="previousSection.formattedName || previousSection.name"></core-format-text>
|
||||||
</button>
|
</button>
|
||||||
<button *ngIf="nextSection" ion-button icon-only (click)="sectionChanged(nextSection)" title="{{ 'core.next' | translate }}">
|
<button *ngIf="nextSection" ion-button icon-only (click)="sectionChanged(nextSection)" title="{{ 'core.next' | translate }}">
|
||||||
<core-format-text class="accesshide" [text]="nextSection.formattedName || nextSection.name"></core-format-text>
|
<core-format-text class="accesshide" [text]="nextSection.formattedName || nextSection.name"></core-format-text>
|
||||||
<ion-icon name="arrow-forward" md="ios-arrow-forward"></ion-icon>
|
<ion-icon name="arrow-forward" md="ios-arrow-forward"></ion-icon>
|
||||||
</button>
|
</button>
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
|
|
||||||
</core-dynamic-component>
|
</core-dynamic-component>
|
||||||
</ion-content>
|
|
||||||
</core-block-course-blocks>
|
</core-block-course-blocks>
|
||||||
|
|
||||||
<!-- Template to render a section. -->
|
<!-- Template to render a section. -->
|
||||||
|
|
|
@ -1,30 +1,28 @@
|
||||||
<core-block-course-blocks [courseId]="siteHomeId" [downloadEnabled]="downloadEnabled">
|
<core-block-course-blocks [courseId]="siteHomeId" [downloadEnabled]="downloadEnabled">
|
||||||
<ion-content>
|
<core-loading [hideUntil]="dataLoaded">
|
||||||
<core-loading [hideUntil]="dataLoaded">
|
<ion-list>
|
||||||
<ion-list>
|
<!-- Site home main contents. -->
|
||||||
<!-- Site home main contents. -->
|
<ng-container *ngIf="section && section.hasContent">
|
||||||
<ng-container *ngIf="section && section.hasContent">
|
<ion-item text-wrap *ngIf="section.summary">
|
||||||
<ion-item text-wrap *ngIf="section.summary">
|
<core-format-text [text]="section.summary"></core-format-text>
|
||||||
<core-format-text [text]="section.summary"></core-format-text>
|
</ion-item>
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
<core-course-module *ngFor="let module of section.modules" [module]="module" [courseId]="siteHomeId" [downloadEnabled]="downloadEnabled" [section]="section"></core-course-module>
|
<core-course-module *ngFor="let module of section.modules" [module]="module" [courseId]="siteHomeId" [downloadEnabled]="downloadEnabled" [section]="section"></core-course-module>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Site home items: news, categories, courses, etc. -->
|
||||||
|
<ng-container *ngIf="items.length > 0">
|
||||||
|
<ion-item-divider *ngIf="section && section.hasContent"></ion-item-divider>
|
||||||
|
<ng-container *ngFor="let item of items">
|
||||||
|
<core-sitehome-all-course-list class="item" *ngIf="item == 'all-course-list'"></core-sitehome-all-course-list>
|
||||||
|
<core-sitehome-categories *ngIf="item == 'categories'"></core-sitehome-categories>
|
||||||
|
<core-sitehome-course-search *ngIf="item == 'course-search'"></core-sitehome-course-search>
|
||||||
|
<core-sitehome-enrolled-course-list *ngIf="item == 'enrolled-course-list'"></core-sitehome-enrolled-course-list>
|
||||||
|
<core-sitehome-news *ngIf="item == 'news'"></core-sitehome-news>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</ion-list>
|
||||||
|
|
||||||
<!-- Site home items: news, categories, courses, etc. -->
|
<core-empty-box *ngIf="!hasContent" icon="qr-scanner" [message]="'core.course.nocontentavailable' | translate"></core-empty-box>
|
||||||
<ng-container *ngIf="items.length > 0">
|
</core-loading>
|
||||||
<ion-item-divider *ngIf="section && section.hasContent"></ion-item-divider>
|
|
||||||
<ng-container *ngFor="let item of items">
|
|
||||||
<core-sitehome-all-course-list class="item" *ngIf="item == 'all-course-list'"></core-sitehome-all-course-list>
|
|
||||||
<core-sitehome-categories *ngIf="item == 'categories'"></core-sitehome-categories>
|
|
||||||
<core-sitehome-course-search *ngIf="item == 'course-search'"></core-sitehome-course-search>
|
|
||||||
<core-sitehome-enrolled-course-list *ngIf="item == 'enrolled-course-list'"></core-sitehome-enrolled-course-list>
|
|
||||||
<core-sitehome-news *ngIf="item == 'news'"></core-sitehome-news>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
|
||||||
</ion-list>
|
|
||||||
|
|
||||||
<core-empty-box *ngIf="!hasContent" icon="qr-scanner" [message]="'core.course.nocontentavailable' | translate"></core-empty-box>
|
|
||||||
</core-loading>
|
|
||||||
</ion-content>
|
|
||||||
</core-block-course-blocks>
|
</core-block-course-blocks>
|
||||||
|
|
|
@ -702,6 +702,45 @@ export class CoreDomUtilsProvider {
|
||||||
return this.instances[id];
|
return this.instances[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait an element to exists using the findFunction.
|
||||||
|
*
|
||||||
|
* @param {Function} findFunction The function used to find the element.
|
||||||
|
* @return {Promise<HTMLElement>} Resolved if found, rejected if too many tries.
|
||||||
|
*/
|
||||||
|
waitElementToExist(findFunction: Function): Promise<HTMLElement> {
|
||||||
|
const promiseInterval = {
|
||||||
|
promise: null,
|
||||||
|
resolve: null,
|
||||||
|
reject: null
|
||||||
|
};
|
||||||
|
|
||||||
|
let tries = 100;
|
||||||
|
|
||||||
|
promiseInterval.promise = new Promise((resolve, reject): void => {
|
||||||
|
promiseInterval.resolve = resolve;
|
||||||
|
promiseInterval.reject = reject;
|
||||||
|
});
|
||||||
|
|
||||||
|
const clear = setInterval(() => {
|
||||||
|
const element: HTMLElement = findFunction();
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
clearInterval(clear);
|
||||||
|
promiseInterval.resolve(element);
|
||||||
|
} else {
|
||||||
|
tries--;
|
||||||
|
|
||||||
|
if (tries <= 0) {
|
||||||
|
clearInterval(clear);
|
||||||
|
promiseInterval.reject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
return promiseInterval.promise;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle bootstrap tooltips in a certain element.
|
* Handle bootstrap tooltips in a certain element.
|
||||||
*
|
*
|
||||||
|
|
|
@ -9,6 +9,10 @@ ion-app.app-root core-rich-text-editor .core-rte-editor {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-overflow {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
// Fix lists styles in core-format-text.
|
// Fix lists styles in core-format-text.
|
||||||
ul {
|
ul {
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
|
|
Loading…
Reference in New Issue