MOBILE-3914 course: Add side blocks component and replace it
parent
80f1b96f61
commit
8cd89ead85
|
@ -1399,6 +1399,8 @@
|
||||||
"core.areyousure": "moodle",
|
"core.areyousure": "moodle",
|
||||||
"core.back": "moodle",
|
"core.back": "moodle",
|
||||||
"core.block.blocks": "moodle",
|
"core.block.blocks": "moodle",
|
||||||
|
"core.block.noblocks": "error",
|
||||||
|
"core.block.opendrawerblocks": "moodle",
|
||||||
"core.browser": "local_moodlemobileapp",
|
"core.browser": "local_moodlemobileapp",
|
||||||
"core.cancel": "moodle",
|
"core.cancel": "moodle",
|
||||||
"core.cannotconnect": "local_moodlemobileapp",
|
"core.cannotconnect": "local_moodlemobileapp",
|
||||||
|
|
|
@ -72,7 +72,3 @@
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:host-context(core-block-course-blocks) .core-empty-box {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
|
@ -16,15 +16,17 @@ import { NgModule } from '@angular/core';
|
||||||
import { CoreBlockComponent } from './block/block';
|
import { CoreBlockComponent } from './block/block';
|
||||||
import { CoreBlockOnlyTitleComponent } from './only-title-block/only-title-block';
|
import { CoreBlockOnlyTitleComponent } from './only-title-block/only-title-block';
|
||||||
import { CoreBlockPreRenderedComponent } from './pre-rendered-block/pre-rendered-block';
|
import { CoreBlockPreRenderedComponent } from './pre-rendered-block/pre-rendered-block';
|
||||||
import { CoreBlockCourseBlocksComponent } from './course-blocks/course-blocks';
|
|
||||||
import { CoreSharedModule } from '@/core/shared.module';
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
|
import { CoreBlockSideBlocksComponent } from './side-blocks/side-blocks';
|
||||||
|
import { CoreBlockSideBlocksButtonComponent } from './side-blocks-button/side-blocks-button';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
CoreBlockComponent,
|
CoreBlockComponent,
|
||||||
CoreBlockOnlyTitleComponent,
|
CoreBlockOnlyTitleComponent,
|
||||||
CoreBlockPreRenderedComponent,
|
CoreBlockPreRenderedComponent,
|
||||||
CoreBlockCourseBlocksComponent,
|
CoreBlockSideBlocksComponent,
|
||||||
|
CoreBlockSideBlocksButtonComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CoreSharedModule,
|
CoreSharedModule,
|
||||||
|
@ -33,7 +35,8 @@ import { CoreSharedModule } from '@/core/shared.module';
|
||||||
CoreBlockComponent,
|
CoreBlockComponent,
|
||||||
CoreBlockOnlyTitleComponent,
|
CoreBlockOnlyTitleComponent,
|
||||||
CoreBlockPreRenderedComponent,
|
CoreBlockPreRenderedComponent,
|
||||||
CoreBlockCourseBlocksComponent,
|
CoreBlockSideBlocksComponent,
|
||||||
|
CoreBlockSideBlocksButtonComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class CoreBlockComponentsModule {}
|
export class CoreBlockComponentsModule {}
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
<div class="core-course-blocks-content">
|
|
||||||
<ng-content></ng-content>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div *ngIf="blocks && blocks.length > 0 && !hideBlocks" [class.core-hide-blocks]="hideBottomBlocks" class="core-course-blocks-side">
|
|
||||||
<core-loading [hideUntil]="dataLoaded" [fullscreen]="false">
|
|
||||||
<ion-list>
|
|
||||||
<!-- Course expand="block"s. -->
|
|
||||||
<ng-container *ngFor="let block of blocks">
|
|
||||||
<core-block *ngIf="block.visible" [block]="block" contextLevel="course" [instanceId]="courseId"
|
|
||||||
[extraData]="{'downloadEnabled': downloadEnabled}"></core-block>
|
|
||||||
</ng-container>
|
|
||||||
</ion-list>
|
|
||||||
</core-loading>
|
|
||||||
</div>
|
|
|
@ -1,61 +0,0 @@
|
||||||
:host {
|
|
||||||
--side-blocks-box-shadow: var(--core-menu-box-shadow-start);
|
|
||||||
|
|
||||||
&.core-no-blocks .core-course-blocks-content {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.core-has-blocks {
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
|
|
||||||
.core-course-blocks-content {
|
|
||||||
box-shadow: none !important;
|
|
||||||
flex-grow: 1;
|
|
||||||
max-width: 100%;
|
|
||||||
|
|
||||||
--ion-safe-area-right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.core-course-blocks-side {
|
|
||||||
max-width: var(--side-blocks-max-width);
|
|
||||||
min-width: var(--side-blocks-min-width);
|
|
||||||
box-shadow: var(--side-blocks-box-shadow);
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.core-course-blocks-content,
|
|
||||||
div.core-course-blocks-side {
|
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.core-loading-center,
|
|
||||||
core-loading.core-loading-loaded {
|
|
||||||
position: initial;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767.98px) {
|
|
||||||
// Disable scroll on individual columns.
|
|
||||||
div.core-course-blocks-side {
|
|
||||||
height: auto;
|
|
||||||
|
|
||||||
&.core-hide-blocks {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:host-context([dir="rtl"]).core-has-blocks {
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
div.core-course-blocks-side {
|
|
||||||
box-shadow: var(--side-blocks-box-shadow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
<ion-button (click)="openBlocks()" [attr.aria-label]="'core.block.opendrawerblocks' | translate">
|
||||||
|
<ion-icon name="fas-cubes" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
|
</ion-button>
|
|
@ -0,0 +1,29 @@
|
||||||
|
@import "~theme/globals";
|
||||||
|
|
||||||
|
:host {
|
||||||
|
@include position(50%, 0px, null, null);
|
||||||
|
position: fixed;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
ion-button {
|
||||||
|
margin: 0;
|
||||||
|
--padding-start: 0.5em;
|
||||||
|
--padding-end: 0;
|
||||||
|
--border-radius: 2em 0 0 2em;
|
||||||
|
|
||||||
|
&::part(native) {
|
||||||
|
@include core-transition(padding, 200ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
--padding-end: 1.2em;
|
||||||
|
--padding-start: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:host-context([dir=rtl]) {
|
||||||
|
ion-button {
|
||||||
|
--border-radius: 0 2em 2em 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
// (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 { Component, Input } from '@angular/core';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { CoreBlockSideBlocksComponent } from '../side-blocks/side-blocks';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that displays a button to open blocks.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'core-block-side-blocks-button',
|
||||||
|
templateUrl: 'side-blocks-button.html',
|
||||||
|
styleUrls: ['side-blocks-button.scss'],
|
||||||
|
})
|
||||||
|
export class CoreBlockSideBlocksButtonComponent {
|
||||||
|
|
||||||
|
@Input() courseId!: number;
|
||||||
|
@Input() downloadEnabled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open side blocks.
|
||||||
|
*/
|
||||||
|
openBlocks(): void {
|
||||||
|
CoreDomUtils.openSideModal({
|
||||||
|
component: CoreBlockSideBlocksComponent,
|
||||||
|
componentProps: {
|
||||||
|
courseId: this.courseId,
|
||||||
|
downloadEnabled: this.downloadEnabled,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<h1>{{ 'core.block.blocks' | translate }}</h1>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button fill="clear" (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
|
||||||
|
<ion-icon name="fas-times" slot="icon-only" aria-hidden=true></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="doRefresh($event.target)">
|
||||||
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
|
</ion-refresher>
|
||||||
|
<core-loading [hideUntil]="loaded">
|
||||||
|
<ion-list *ngIf="blocks.length > 0">
|
||||||
|
<ng-container *ngFor="let block of blocks">
|
||||||
|
<core-block *ngIf="block.visible" [block]="block" contextLevel="course" [instanceId]="courseId"
|
||||||
|
[extraData]="{'downloadEnabled': downloadEnabled}"></core-block>
|
||||||
|
</ng-container>
|
||||||
|
</ion-list>
|
||||||
|
|
||||||
|
<core-empty-box *ngIf="blocks.length == 0" icon="fas-cubes" [message]="'core.block.noblocks' | translate">
|
||||||
|
</core-empty-box>
|
||||||
|
</core-loading>
|
||||||
|
</ion-content>
|
|
@ -12,50 +12,38 @@
|
||||||
// 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 } from '@angular/core';
|
import { Component, ViewChildren, Input, OnInit, QueryList } from '@angular/core';
|
||||||
import { IonContent } from '@ionic/angular';
|
import { ModalController } from '@singletons';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreCourse, CoreCourseBlock } from '@features/course/services/course';
|
import { CoreCourse, CoreCourseBlock } from '@features/course/services/course';
|
||||||
import { CoreBlockHelper } from '../../services/block-helper';
|
import { CoreBlockHelper } from '../../services/block-helper';
|
||||||
import { CoreBlockComponent } from '../block/block';
|
import { CoreBlockComponent } from '../block/block';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { IonRefresher } from '@ionic/angular';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that displays the list of course blocks.
|
* Component that displays the list of side blocks.
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'core-block-course-blocks',
|
selector: 'core-block-side-blocks',
|
||||||
templateUrl: 'core-block-course-blocks.html',
|
templateUrl: 'side-blocks.html',
|
||||||
styleUrls: ['course-blocks.scss'],
|
|
||||||
})
|
})
|
||||||
export class CoreBlockCourseBlocksComponent implements OnInit {
|
export class CoreBlockSideBlocksComponent implements OnInit {
|
||||||
|
|
||||||
@Input() courseId!: number;
|
@Input() courseId!: number;
|
||||||
@Input() hideBlocks = false;
|
|
||||||
@Input() hideBottomBlocks = false;
|
|
||||||
@Input() downloadEnabled = false;
|
@Input() downloadEnabled = false;
|
||||||
|
|
||||||
@ViewChildren(CoreBlockComponent) blocksComponents?: QueryList<CoreBlockComponent>;
|
@ViewChildren(CoreBlockComponent) blocksComponents?: QueryList<CoreBlockComponent>;
|
||||||
|
|
||||||
dataLoaded = false;
|
loaded = false;
|
||||||
blocks: CoreCourseBlock[] = [];
|
blocks: CoreCourseBlock[] = [];
|
||||||
|
|
||||||
protected element: HTMLElement;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
element: ElementRef,
|
|
||||||
protected content: IonContent,
|
|
||||||
) {
|
|
||||||
this.element = element.nativeElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component being initialized.
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
this.element.classList.add('core-no-blocks');
|
|
||||||
this.loadContent().finally(() => {
|
this.loadContent().finally(() => {
|
||||||
this.dataLoaded = true;
|
this.loaded = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +75,6 @@ export class CoreBlockCourseBlocksComponent implements OnInit {
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async loadContent(): Promise<void> {
|
async loadContent(): Promise<void> {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.blocks = await CoreBlockHelper.getCourseBlocks(this.courseId);
|
this.blocks = await CoreBlockHelper.getCourseBlocks(this.courseId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -95,29 +82,26 @@ export class CoreBlockCourseBlocksComponent implements OnInit {
|
||||||
|
|
||||||
this.blocks = [];
|
this.blocks = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const scrollElement = await this.content.getScrollElement();
|
|
||||||
if (!this.hideBlocks && this.blocks.length > 0) {
|
|
||||||
this.element.classList.add('core-has-blocks');
|
|
||||||
this.element.classList.remove('core-no-blocks');
|
|
||||||
|
|
||||||
scrollElement.classList.add('core-course-block-with-blocks');
|
|
||||||
} else {
|
|
||||||
this.element.classList.remove('core-has-blocks');
|
|
||||||
this.element.classList.add('core-no-blocks');
|
|
||||||
scrollElement.classList.remove('core-course-block-with-blocks');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh data.
|
* Refresh the data.
|
||||||
*
|
*
|
||||||
* @return Promise resolved when done.
|
* @param refresher Refresher.
|
||||||
*/
|
*/
|
||||||
async doRefresh(): Promise<void> {
|
async doRefresh(refresher?: IonRefresher): Promise<void> {
|
||||||
await CoreUtils.ignoreErrors(this.invalidateBlocks());
|
await CoreUtils.ignoreErrors(this.invalidateBlocks());
|
||||||
|
|
||||||
await this.loadContent();
|
await this.loadContent().finally(() => {
|
||||||
|
refresher?.complete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close modal.
|
||||||
|
*/
|
||||||
|
closeModal(): void {
|
||||||
|
ModalController.dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
{
|
{
|
||||||
"blocks": "Blocks"
|
"blocks": "Blocks",
|
||||||
|
"noblocks": "No blocks found!",
|
||||||
|
"opendrawerblocks": "Open block drawer"
|
||||||
}
|
}
|
|
@ -54,6 +54,22 @@ export class CoreBlockHelperProvider {
|
||||||
return blocks;
|
return blocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if the course has any block.
|
||||||
|
*
|
||||||
|
* @param courseId Course ID.
|
||||||
|
* @return Wether course has blocks.
|
||||||
|
*/
|
||||||
|
async hasCourseBlocks(courseId: number): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const blocks = await this.getCourseBlocks(courseId);
|
||||||
|
|
||||||
|
return blocks.length > 0;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CoreBlockHelper = makeSingleton(CoreBlockHelperProvider);
|
export const CoreBlockHelper = makeSingleton(CoreBlockHelperProvider);
|
||||||
|
|
|
@ -6,126 +6,115 @@
|
||||||
</core-context-menu-item>
|
</core-context-menu-item>
|
||||||
</core-context-menu>
|
</core-context-menu>
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
<core-dynamic-component [component]="courseFormatComponent" [data]="data">
|
||||||
|
<!-- Default course format. -->
|
||||||
|
<core-loading [hideUntil]="loaded">
|
||||||
|
<!-- Section selector. -->
|
||||||
|
<core-dynamic-component [component]="sectionSelectorComponent" [data]="data">
|
||||||
|
|
||||||
<core-block-course-blocks *ngIf="loaded" [courseId]="course!.id" [hideBlocks]="!displayBlocks" [downloadEnabled]="downloadEnabled"
|
<div *ngIf="displaySectionSelector && sections && hasSeveralSections"
|
||||||
[hideBottomBlocks]="selectedSection && selectedSection.id == allSectionsId && canLoadMore">
|
class="ion-text-wrap ion-justify-content-between ion-align-items-center core-button-selector-row"
|
||||||
|
[class.core-section-download]="downloadEnabled">
|
||||||
|
<core-combobox [modalOptions]="sectionSelectorModalOptions" interface="modal" listboxId="core-course-section-button"
|
||||||
|
icon="fas-folder" [label]="'core.course.section' | translate"
|
||||||
|
[selection]="selectedSection ? selectedSection.name : 'core.course.sections' | translate"
|
||||||
|
(onChange)="sectionChanged($event)">
|
||||||
|
<span slot="text">
|
||||||
|
<core-format-text *ngIf="selectedSection" [text]="selectedSection.name" contextLevel="course"
|
||||||
|
[contextInstanceId]="course?.id" [clean]="true" [singleLine]="true">
|
||||||
|
</core-format-text>
|
||||||
|
<ng-container *ngIf="!selectedSection">{{ 'core.course.sections' | translate }}</ng-container>
|
||||||
|
</span>
|
||||||
|
</core-combobox>
|
||||||
|
<!-- Section download. -->
|
||||||
|
<ng-container *ngTemplateOutlet="sectionDownloadTemplate; context: {section: selectedSection}"></ng-container>
|
||||||
|
</div>
|
||||||
|
</core-dynamic-component>
|
||||||
|
|
||||||
<core-dynamic-component [component]="courseFormatComponent" [data]="data">
|
<!-- Course summary. By default we only display the course progress. -->
|
||||||
<!-- Default course format. -->
|
<core-dynamic-component [component]="courseSummaryComponent" [data]="data">
|
||||||
<core-loading [hideUntil]="loaded">
|
<ion-list lines="none" class="core-format-progress-list" *ngIf="imageThumb || (selectedSection?.id == allSectionsId && progress !== undefined) ||
|
||||||
<!-- Section selector. -->
|
(selectedSection && selectedSection.id != allSectionsId &&
|
||||||
<core-dynamic-component [component]="sectionSelectorComponent" [data]="data">
|
(selectedSection.availabilityinfo || selectedSection.visible === 0))">
|
||||||
|
<div *ngIf="imageThumb" class="core-course-thumb">
|
||||||
<div *ngIf="displaySectionSelector && sections && hasSeveralSections"
|
<img [src]="imageThumb" core-external-content alt="" />
|
||||||
class="ion-text-wrap ion-justify-content-between ion-align-items-center core-button-selector-row"
|
|
||||||
[class.core-section-download]="downloadEnabled">
|
|
||||||
<core-combobox
|
|
||||||
[modalOptions]="sectionSelectorModalOptions"
|
|
||||||
interface="modal"
|
|
||||||
listboxId="core-course-section-button"
|
|
||||||
icon="fas-folder"
|
|
||||||
[label]="'core.course.section' | translate"
|
|
||||||
[selection]="selectedSection ? selectedSection.name : 'core.course.sections' | translate"
|
|
||||||
(onChange)="sectionChanged($event)"
|
|
||||||
>
|
|
||||||
<span slot="text">
|
|
||||||
<core-format-text *ngIf="selectedSection" [text]="selectedSection.name" contextLevel="course"
|
|
||||||
[contextInstanceId]="course?.id" [clean]="true" [singleLine]="true">
|
|
||||||
</core-format-text>
|
|
||||||
<ng-container *ngIf="!selectedSection">{{ 'core.course.sections' | translate }}</ng-container>
|
|
||||||
</span>
|
|
||||||
</core-combobox>
|
|
||||||
<!-- Section download. -->
|
|
||||||
<ng-container *ngTemplateOutlet="sectionDownloadTemplate; context: {section: selectedSection}"></ng-container>
|
|
||||||
</div>
|
</div>
|
||||||
|
<ng-container *ngIf="selectedSection">
|
||||||
|
<ion-item class="core-course-progress" *ngIf="selectedSection?.id == allSectionsId && progress !== undefined">
|
||||||
|
<core-progress-bar [progress]="progress" a11yText="core.course.aria:sectionprogress">
|
||||||
|
</core-progress-bar>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item *ngIf="selectedSection && selectedSection.id != allSectionsId &&
|
||||||
|
(selectedSection.availabilityinfo || selectedSection.visible === 0)">
|
||||||
|
<ion-badge color="info" class="ion-text-wrap"
|
||||||
|
*ngIf="selectedSection.visible === 0 && selectedSection.uservisible !== false">
|
||||||
|
{{ 'core.course.hiddenfromstudents' | translate }}
|
||||||
|
</ion-badge>
|
||||||
|
<ion-badge color="info" class="ion-text-wrap"
|
||||||
|
*ngIf="selectedSection.visible === 0 && selectedSection.uservisible === false">
|
||||||
|
{{ 'core.notavailable' | translate }}
|
||||||
|
</ion-badge>
|
||||||
|
<ion-badge color="info" class="ion-text-wrap" *ngIf="selectedSection.availabilityinfo">
|
||||||
|
<core-format-text [text]="selectedSection.availabilityinfo" contextLevel="course"
|
||||||
|
[contextInstanceId]="course?.id">
|
||||||
|
</core-format-text>
|
||||||
|
</ion-badge>
|
||||||
|
</ion-item>
|
||||||
|
</ng-container>
|
||||||
|
</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="fas-th-large"
|
||||||
|
[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>
|
||||||
</core-dynamic-component>
|
</core-dynamic-component>
|
||||||
|
|
||||||
<!-- Course summary. By default we only display the course progress. -->
|
<core-infinite-loading [enabled]="canLoadMore" (action)="showMoreActivities($event)"></core-infinite-loading>
|
||||||
<core-dynamic-component [component]="courseSummaryComponent" [data]="data">
|
</div>
|
||||||
<ion-list lines="none" class="core-format-progress-list"
|
|
||||||
*ngIf="imageThumb || (selectedSection?.id == allSectionsId && progress !== undefined) ||
|
|
||||||
(selectedSection && selectedSection.id != allSectionsId &&
|
|
||||||
(selectedSection.availabilityinfo || selectedSection.visible === 0))">
|
|
||||||
<div *ngIf="imageThumb" class="core-course-thumb">
|
|
||||||
<img [src]="imageThumb" core-external-content alt=""/>
|
|
||||||
</div>
|
|
||||||
<ng-container *ngIf="selectedSection">
|
|
||||||
<ion-item class="core-course-progress"
|
|
||||||
*ngIf="selectedSection?.id == allSectionsId && progress !== undefined">
|
|
||||||
<core-progress-bar [progress]="progress" a11yText="core.course.aria:sectionprogress">
|
|
||||||
</core-progress-bar>
|
|
||||||
</ion-item>
|
|
||||||
<ion-item *ngIf="selectedSection && selectedSection.id != allSectionsId &&
|
|
||||||
(selectedSection.availabilityinfo || selectedSection.visible === 0)">
|
|
||||||
<ion-badge color="info" class="ion-text-wrap"
|
|
||||||
*ngIf="selectedSection.visible === 0 && selectedSection.uservisible !== false">
|
|
||||||
{{ 'core.course.hiddenfromstudents' | translate }}
|
|
||||||
</ion-badge>
|
|
||||||
<ion-badge color="info" class="ion-text-wrap"
|
|
||||||
*ngIf="selectedSection.visible === 0 && selectedSection.uservisible === false">
|
|
||||||
{{ 'core.notavailable' | translate }}
|
|
||||||
</ion-badge>
|
|
||||||
<ion-badge color="info" class="ion-text-wrap" *ngIf="selectedSection.availabilityinfo">
|
|
||||||
<core-format-text [text]="selectedSection.availabilityinfo" contextLevel="course"
|
|
||||||
[contextInstanceId]="course?.id">
|
|
||||||
</core-format-text>
|
|
||||||
</ion-badge>
|
|
||||||
</ion-item>
|
|
||||||
</ng-container>
|
|
||||||
</ion-list>
|
|
||||||
</core-dynamic-component>
|
|
||||||
|
|
||||||
<!-- Single section. -->
|
<ion-buttons class="ion-padding core-course-section-nav-buttons safe-area-padding-horizontal"
|
||||||
<div *ngIf="selectedSection && selectedSection.id != allSectionsId">
|
*ngIf="displaySectionSelector && sections?.length">
|
||||||
<core-dynamic-component [component]="singleSectionComponent" [data]="data">
|
<ion-button *ngIf="previousSection" (click)="sectionChanged(previousSection)" fill="outline" color="primary"
|
||||||
<ng-container *ngTemplateOutlet="sectionTemplate; context: {section: selectedSection}"></ng-container>
|
[attr.aria-label]="('core.previous' | translate) + ': ' + previousSection.name">
|
||||||
<core-empty-box *ngIf="!selectedSection.hasContent" icon="fas-th-large"
|
<ion-icon name="fas-chevron-left" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
[message]="'core.course.nocontentavailable' | translate">
|
<core-format-text class="sr-only" [text]="previousSection.name" contextLevel="course" [contextInstanceId]="course?.id">
|
||||||
</core-empty-box>
|
</core-format-text>
|
||||||
</core-dynamic-component>
|
</ion-button>
|
||||||
</div>
|
<ion-button *ngIf="nextSection" (click)="sectionChanged(nextSection)" fill="solid" color="primary"
|
||||||
|
[attr.aria-label]="('core.next' | translate) + ': ' + nextSection.name">
|
||||||
|
<core-format-text class="sr-only" [text]="nextSection.name" contextLevel="course" [contextInstanceId]="course?.id">
|
||||||
|
</core-format-text>
|
||||||
|
<ion-icon name="fas-chevron-right" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
|
||||||
<!-- Multiple sections. -->
|
<core-block-side-blocks-button *ngIf="course && displayBlocks && hasBlocks" [courseId]="course.id"
|
||||||
<div *ngIf="selectedSection && selectedSection.id == allSectionsId">
|
[downloadEnabled]="downloadEnabled">
|
||||||
<core-dynamic-component [component]="allSectionsComponent" [data]="data">
|
</core-block-side-blocks-button>
|
||||||
<ng-container *ngFor="let section of sections; index as i">
|
</core-loading>
|
||||||
<ng-container *ngIf="i <= showSectionId">
|
</core-dynamic-component>
|
||||||
<ng-container *ngTemplateOutlet="sectionTemplate; context: {section: section}"></ng-container>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
|
||||||
</core-dynamic-component>
|
|
||||||
|
|
||||||
<core-infinite-loading [enabled]="canLoadMore" (action)="showMoreActivities($event)"></core-infinite-loading>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ion-buttons class="ion-padding core-course-section-nav-buttons safe-area-padding-horizontal"
|
|
||||||
*ngIf="displaySectionSelector && sections?.length">
|
|
||||||
<ion-button *ngIf="previousSection" (click)="sectionChanged(previousSection)" fill="outline" color="primary"
|
|
||||||
[attr.aria-label]="('core.previous' | translate) + ': ' + previousSection.name">
|
|
||||||
<ion-icon name="fas-chevron-left" slot="icon-only" aria-hidden="true"></ion-icon>
|
|
||||||
<core-format-text class="sr-only" [text]="previousSection.name" contextLevel="course"
|
|
||||||
[contextInstanceId]="course?.id">
|
|
||||||
</core-format-text>
|
|
||||||
</ion-button>
|
|
||||||
<ion-button *ngIf="nextSection" (click)="sectionChanged(nextSection)" fill="solid" color="primary"
|
|
||||||
[attr.aria-label]="('core.next' | translate) + ': ' + nextSection.name">
|
|
||||||
<core-format-text class="sr-only" [text]="nextSection.name" contextLevel="course"
|
|
||||||
[contextInstanceId]="course?.id">
|
|
||||||
</core-format-text>
|
|
||||||
<ion-icon name="fas-chevron-right" slot="icon-only" aria-hidden="true"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
</ion-buttons>
|
|
||||||
</core-loading>
|
|
||||||
</core-dynamic-component>
|
|
||||||
</core-block-course-blocks>
|
|
||||||
|
|
||||||
<!-- Template to render a section. -->
|
<!-- Template to render a section. -->
|
||||||
<ng-template #sectionTemplate let-section="section">
|
<ng-template #sectionTemplate let-section="section">
|
||||||
<section *ngIf="!section.hiddenbynumsections && section.id != allSectionsId && section.id != stealthModulesSectionId">
|
<section *ngIf="!section.hiddenbynumsections && section.id != allSectionsId && section.id != stealthModulesSectionId">
|
||||||
<!-- Title is only displayed when viewing all sections. -->
|
<!-- Title is only displayed when viewing all sections. -->
|
||||||
<ion-item-divider *ngIf="selectedSection?.id == allSectionsId && section.name" class="ion-text-wrap" color="light"
|
<ion-item-divider *ngIf="selectedSection?.id == allSectionsId && section.name" class="ion-text-wrap" color="light"
|
||||||
[class.core-section-download]="downloadEnabled"
|
[class.core-section-download]="downloadEnabled" [class.item-dimmed]="section.visible === 0 || section.uservisible === false">
|
||||||
[class.item-dimmed]="section.visible === 0 || section.uservisible === false">
|
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>
|
<h2>
|
||||||
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id">
|
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id">
|
||||||
|
|
|
@ -24,7 +24,6 @@ import {
|
||||||
ViewChildren,
|
ViewChildren,
|
||||||
QueryList,
|
QueryList,
|
||||||
Type,
|
Type,
|
||||||
ViewChild,
|
|
||||||
ElementRef,
|
ElementRef,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { ModalOptions } from '@ionic/core';
|
import { ModalOptions } from '@ionic/core';
|
||||||
|
@ -48,8 +47,8 @@ import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
import { IonContent, IonRefresher } from '@ionic/angular';
|
import { IonContent, IonRefresher } from '@ionic/angular';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
||||||
import { CoreBlockCourseBlocksComponent } from '@features/block/components/course-blocks/course-blocks';
|
|
||||||
import { CoreCourseSectionSelectorComponent } from '../section-selector/section-selector';
|
import { CoreCourseSectionSelectorComponent } from '../section-selector/section-selector';
|
||||||
|
import { CoreBlockHelper } from '@features/block/services/block-helper';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to display course contents using a certain format. If the format isn't found, use default one.
|
* Component to display course contents using a certain format. If the format isn't found, use default one.
|
||||||
|
@ -79,7 +78,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
@Output() completionChanged = new EventEmitter<CoreCourseModuleCompletionData>(); // Notify when any module completion changes.
|
@Output() completionChanged = new EventEmitter<CoreCourseModuleCompletionData>(); // Notify when any module completion changes.
|
||||||
|
|
||||||
@ViewChildren(CoreDynamicComponent) dynamicComponents?: QueryList<CoreDynamicComponent>;
|
@ViewChildren(CoreDynamicComponent) dynamicComponents?: QueryList<CoreDynamicComponent>;
|
||||||
@ViewChild(CoreBlockCourseBlocksComponent) courseBlocksComponent?: CoreBlockCourseBlocksComponent;
|
|
||||||
|
|
||||||
// All the possible component classes.
|
// All the possible component classes.
|
||||||
courseFormatComponent?: Type<unknown>;
|
courseFormatComponent?: Type<unknown>;
|
||||||
|
@ -92,8 +90,9 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
showSectionId = 0;
|
showSectionId = 0;
|
||||||
data: Record<string, unknown> = {}; // Data to pass to the components.
|
data: Record<string, unknown> = {}; // Data to pass to the components.
|
||||||
|
|
||||||
displaySectionSelector?: boolean;
|
displaySectionSelector = false;
|
||||||
displayBlocks?: boolean;
|
displayBlocks = false;
|
||||||
|
hasBlocks = false;
|
||||||
selectedSection?: CoreCourseSection;
|
selectedSection?: CoreCourseSection;
|
||||||
previousSection?: CoreCourseSection;
|
previousSection?: CoreCourseSection;
|
||||||
nextSection?: CoreCourseSection;
|
nextSection?: CoreCourseSection;
|
||||||
|
@ -180,7 +179,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
/**
|
/**
|
||||||
* Detect changes on input properties.
|
* Detect changes on input properties.
|
||||||
*/
|
*/
|
||||||
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
async ngOnChanges(changes: { [name: string]: SimpleChange }): Promise<void> {
|
||||||
this.setInputData();
|
this.setInputData();
|
||||||
this.sectionSelectorModalOptions.componentProps!.course = this.course;
|
this.sectionSelectorModalOptions.componentProps!.course = this.course;
|
||||||
this.sectionSelectorModalOptions.componentProps!.sections = this.sections;
|
this.sectionSelectorModalOptions.componentProps!.sections = this.sections;
|
||||||
|
@ -191,6 +190,9 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
|
|
||||||
this.displaySectionSelector = CoreCourseFormatDelegate.displaySectionSelector(this.course);
|
this.displaySectionSelector = CoreCourseFormatDelegate.displaySectionSelector(this.course);
|
||||||
this.displayBlocks = CoreCourseFormatDelegate.displayBlocks(this.course);
|
this.displayBlocks = CoreCourseFormatDelegate.displayBlocks(this.course);
|
||||||
|
|
||||||
|
this.hasBlocks = await CoreBlockHelper.hasCourseBlocks(this.course.id);
|
||||||
|
|
||||||
this.updateProgress();
|
this.updateProgress();
|
||||||
|
|
||||||
if ('overviewfiles' in this.course) {
|
if ('overviewfiles' in this.course) {
|
||||||
|
@ -498,8 +500,13 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
await component.callComponentFunction('doRefresh', [refresher, done, afterCompletionChange]);
|
await component.callComponentFunction('doRefresh', [refresher, done, afterCompletionChange]);
|
||||||
}) || [];
|
}) || [];
|
||||||
|
|
||||||
if (this.courseBlocksComponent) {
|
if (this.course) {
|
||||||
promises.push(this.courseBlocksComponent.doRefresh());
|
const courseId = this.course.id;
|
||||||
|
promises.push(CoreCourse.invalidateCourseBlocks(courseId).then(async () => {
|
||||||
|
this.hasBlocks = await CoreBlockHelper.hasCourseBlocks(courseId);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
|
|
@ -3,11 +3,9 @@
|
||||||
<ion-icon name="fas-search" slot="icon-only" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-search" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<core-context-menu>
|
<core-context-menu>
|
||||||
<core-context-menu-item [priority]="1000" *ngIf="displayEnableDownload"
|
<core-context-menu-item [priority]="1000" *ngIf="displayEnableDownload" [content]="'core.settings.showdownloadoptions' | translate"
|
||||||
[content]="'core.settings.showdownloadoptions' | translate" (action)="switchDownload()"
|
(action)="switchDownload()" iconAction="toggle" [(toggle)]="downloadEnabled"></core-context-menu-item>
|
||||||
iconAction="toggle" [(toggle)]="downloadEnabled"></core-context-menu-item>
|
<core-context-menu-item [priority]="500" [content]="'addon.storagemanager.managestorage' | translate"
|
||||||
<core-context-menu-item [priority]="500"
|
|
||||||
[content]="'addon.storagemanager.managestorage' | translate"
|
|
||||||
(action)="manageCoursesStorage()" iconAction="fas-archive"></core-context-menu-item>
|
(action)="manageCoursesStorage()" iconAction="fas-archive"></core-context-menu-item>
|
||||||
</core-context-menu>
|
</core-context-menu>
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
@ -15,49 +13,52 @@
|
||||||
<ion-refresher slot="fixed" [disabled]="!dataLoaded" (ionRefresh)="doRefresh($event.target)">
|
<ion-refresher slot="fixed" [disabled]="!dataLoaded" (ionRefresh)="doRefresh($event.target)">
|
||||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
<core-block-course-blocks [courseId]="siteHomeId" [downloadEnabled]="downloadEnabled">
|
<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 class="ion-text-wrap" *ngIf="section.summary">
|
||||||
<ion-item class="ion-text-wrap" *ngIf="section.summary">
|
<ion-label>
|
||||||
<ion-label><core-format-text [text]="section.summary" contextLevel="course" [contextInstanceId]="siteHomeId">
|
<core-format-text [text]="section.summary" contextLevel="course" [contextInstanceId]="siteHomeId">
|
||||||
</core-format-text></ion-label>
|
</core-format-text>
|
||||||
</ion-item>
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
<core-course-module *ngFor="let module of section.modules" [module]="module" [courseId]="siteHomeId"
|
<core-course-module *ngFor="let module of section.modules" [module]="module" [courseId]="siteHomeId"
|
||||||
[downloadEnabled]="downloadEnabled" [section]="section"></core-course-module>
|
[downloadEnabled]="downloadEnabled" [section]="section"></core-course-module>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- Site home items: news, categories, courses, etc. -->
|
<!-- Site home items: news, categories, courses, etc. -->
|
||||||
<ng-container *ngIf="items.length > 0">
|
<ng-container *ngIf="items.length > 0">
|
||||||
<core-spacer *ngIf="section && section!.hasContent"></core-spacer>
|
<core-spacer *ngIf="section && section!.hasContent"></core-spacer>
|
||||||
<ng-container *ngFor="let item of items">
|
<ng-container *ngFor="let item of items">
|
||||||
<ng-container [ngSwitch]="item">
|
<ng-container [ngSwitch]="item">
|
||||||
<ng-container *ngSwitchCase="'LIST_OF_COURSE'">
|
<ng-container *ngSwitchCase="'LIST_OF_COURSE'">
|
||||||
<ng-template *ngTemplateOutlet="allCourseList"></ng-template>
|
<ng-template *ngTemplateOutlet="allCourseList"></ng-template>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngSwitchCase="'LIST_OF_CATEGORIES'">
|
<ng-container *ngSwitchCase="'LIST_OF_CATEGORIES'">
|
||||||
<ng-template *ngTemplateOutlet="categories"></ng-template>
|
<ng-template *ngTemplateOutlet="categories"></ng-template>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngSwitchCase="'COURSE_SEARCH_BOX'">
|
<ng-container *ngSwitchCase="'COURSE_SEARCH_BOX'">
|
||||||
<ng-template *ngTemplateOutlet="courseSearch"></ng-template>
|
<ng-template *ngTemplateOutlet="courseSearch"></ng-template>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngSwitchCase="'ENROLLED_COURSES'">
|
<ng-container *ngSwitchCase="'ENROLLED_COURSES'">
|
||||||
<ng-template *ngTemplateOutlet="enrolledCourseList"></ng-template>
|
<ng-template *ngTemplateOutlet="enrolledCourseList"></ng-template>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngSwitchCase="'NEWS_ITEMS'">
|
<ng-container *ngSwitchCase="'NEWS_ITEMS'">
|
||||||
<ng-template *ngTemplateOutlet="news"></ng-template>
|
<ng-template *ngTemplateOutlet="news"></ng-template>
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ion-list>
|
</ng-container>
|
||||||
<core-empty-box *ngIf="!hasContent" icon="fas-box-open" [message]="'core.course.nocontentavailable' | translate">
|
</ng-container>
|
||||||
|
</ion-list>
|
||||||
|
<core-block-side-blocks-button *ngIf="hasBlocks" [courseId]="siteHomeId" [downloadEnabled]="downloadEnabled">
|
||||||
|
</core-block-side-blocks-button>
|
||||||
|
|
||||||
</core-empty-box>
|
<core-empty-box *ngIf="!hasContent" icon="fas-box-open" [message]="'core.course.nocontentavailable' | translate">
|
||||||
</core-loading>
|
|
||||||
</core-block-course-blocks>
|
</core-empty-box>
|
||||||
|
</core-loading>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
||||||
<ng-template #allCourseList>
|
<ng-template #allCourseList>
|
||||||
|
@ -88,13 +89,17 @@
|
||||||
<ion-item button class="ion-text-wrap" (click)="openMyCourses()" detail="true">
|
<ion-item button class="ion-text-wrap" (click)="openMyCourses()" detail="true">
|
||||||
<ion-icon name="fas-graduation-cap" fixed-width slot="start" aria-hidden="true">
|
<ion-icon name="fas-graduation-cap" fixed-width slot="start" aria-hidden="true">
|
||||||
</ion-icon>
|
</ion-icon>
|
||||||
<ion-label><h2>{{ 'core.courses.mycourses' | translate}}</h2></ion-label>
|
<ion-label>
|
||||||
|
<h2>{{ 'core.courses.mycourses' | translate}}</h2>
|
||||||
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #courseSearch>
|
<ng-template #courseSearch>
|
||||||
<ion-item button class="ion-text-wrap" (click)="openSearch()" detail="true">
|
<ion-item button class="ion-text-wrap" (click)="openSearch()" detail="true">
|
||||||
<ion-icon name="fas-search" slot="start" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-search" slot="start" aria-hidden="true"></ion-icon>
|
||||||
<ion-label><h2>{{ 'core.courses.searchcourses' | translate}}</h2></ion-label>
|
<ion-label>
|
||||||
|
<h2>{{ 'core.courses.searchcourses' | translate}}</h2>
|
||||||
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
// 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, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { IonRefresher } from '@ionic/angular';
|
||||||
import { Params } from '@angular/router';
|
import { Params } from '@angular/router';
|
||||||
|
|
||||||
|
@ -24,10 +24,11 @@ import { CoreSiteHome } from '@features/sitehome/services/sitehome';
|
||||||
import { CoreCourses, CoreCoursesProvider } from '@features//courses/services/courses';
|
import { CoreCourses, CoreCoursesProvider } from '@features//courses/services/courses';
|
||||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
import { CoreCourseHelper, CoreCourseModule } from '@features/course/services/course-helper';
|
import { CoreCourseHelper, CoreCourseModule } from '@features/course/services/course-helper';
|
||||||
import { CoreBlockCourseBlocksComponent } from '@features/block/components/course-blocks/course-blocks';
|
|
||||||
import { CoreCourseModuleDelegate, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate';
|
import { CoreCourseModuleDelegate, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate';
|
||||||
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { CoreBlockHelper } from '@features/block/services/block-helper';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays site home index.
|
* Page that displays site home index.
|
||||||
|
@ -38,14 +39,13 @@ import { CoreNavigator } from '@services/navigator';
|
||||||
})
|
})
|
||||||
export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
|
export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
@ViewChild(CoreBlockCourseBlocksComponent) courseBlocksComponent?: CoreBlockCourseBlocksComponent;
|
|
||||||
|
|
||||||
dataLoaded = false;
|
dataLoaded = false;
|
||||||
section?: CoreCourseWSSection & {
|
section?: CoreCourseWSSection & {
|
||||||
hasContent?: boolean;
|
hasContent?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
hasContent = false;
|
hasContent = false;
|
||||||
|
hasBlocks = false;
|
||||||
items: string[] = [];
|
items: string[] = [];
|
||||||
siteHomeId = 1;
|
siteHomeId = 1;
|
||||||
currentSite!: CoreSite;
|
currentSite!: CoreSite;
|
||||||
|
@ -106,8 +106,8 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
|
||||||
this.items = await CoreSiteHome.getFrontPageItems(config.frontpageloggedin);
|
this.items = await CoreSiteHome.getFrontPageItems(config.frontpageloggedin);
|
||||||
this.hasContent = this.items.length > 0;
|
this.hasContent = this.items.length > 0;
|
||||||
|
|
||||||
if (this.items.some((item) => item == 'NEWS_ITEMS')) {
|
// Get the news forum.
|
||||||
// Get the news forum.
|
if (this.items.includes('NEWS_ITEMS')) {
|
||||||
try {
|
try {
|
||||||
const forum = await CoreSiteHome.getNewsForum(this.siteHomeId);
|
const forum = await CoreSiteHome.getNewsForum(this.siteHomeId);
|
||||||
this.newsForumModule = await CoreCourse.getModule(forum.cmid, forum.course);
|
this.newsForumModule = await CoreCourse.getModule(forum.cmid, forum.course);
|
||||||
|
@ -140,17 +140,17 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add log in Moodle.
|
// Add log in Moodle.
|
||||||
CoreCourse.logView(
|
CoreUtils.ignoreErrors(CoreCourse.logView(
|
||||||
this.siteHomeId,
|
this.siteHomeId,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
this.currentSite.getInfo()?.sitename,
|
this.currentSite.getInfo()?.sitename,
|
||||||
).catch(() => {
|
));
|
||||||
// Ignore errors.
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'core.course.couldnotloadsectioncontent', true);
|
CoreDomUtils.showErrorModalDefault(error, 'core.course.couldnotloadsectioncontent', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.hasBlocks = await CoreBlockHelper.hasCourseBlocks(this.siteHomeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -170,24 +170,15 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
|
||||||
return;
|
return;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
promises.push(CoreCourse.invalidateCourseBlocks(this.siteHomeId));
|
||||||
|
|
||||||
if (this.section && this.section.modules) {
|
if (this.section && this.section.modules) {
|
||||||
// Invalidate modules prefetch data.
|
// Invalidate modules prefetch data.
|
||||||
promises.push(CoreCourseModulePrefetchDelegate.invalidateModules(this.section.modules, this.siteHomeId));
|
promises.push(CoreCourseModulePrefetchDelegate.invalidateModules(this.section.modules, this.siteHomeId));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.courseBlocksComponent) {
|
|
||||||
promises.push(this.courseBlocksComponent.invalidateBlocks());
|
|
||||||
}
|
|
||||||
|
|
||||||
Promise.all(promises).finally(async () => {
|
Promise.all(promises).finally(async () => {
|
||||||
const p2: Promise<unknown>[] = [];
|
await this.loadContent().finally(() => {
|
||||||
|
|
||||||
p2.push(this.loadContent());
|
|
||||||
if (this.courseBlocksComponent) {
|
|
||||||
p2.push(this.courseBlocksComponent.loadContent());
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(p2).finally(() => {
|
|
||||||
refresher?.complete();
|
refresher?.complete();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -197,13 +197,6 @@
|
||||||
--background: var(--core-progressbar-background);
|
--background: var(--core-progressbar-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
--core-side-blocks-max-width: 30%;
|
|
||||||
--core-side-blocks-min-width: 280px;
|
|
||||||
core-block-course-blocks {
|
|
||||||
--side-blocks-max-width: var(--core-side-blocks-max-width);
|
|
||||||
--side-blocks-min-width: var(--core-side-blocks-min-width);
|
|
||||||
}
|
|
||||||
|
|
||||||
--ion-item-background: #{$ion-item-background};
|
--ion-item-background: #{$ion-item-background};
|
||||||
--ion-item-detail-icon-color: var(--gray-darker);
|
--ion-item-detail-icon-color: var(--gray-darker);
|
||||||
--ion-item-detail-icon-font-size: 20px;
|
--ion-item-detail-icon-font-size: 20px;
|
||||||
|
|
Loading…
Reference in New Issue