forked from CIT/Vmeda.Online
		
	MOBILE-3914 course: Add side blocks component and replace it
This commit is contained in:
		
							parent
							
								
									80f1b96f61
								
							
						
					
					
						commit
						8cd89ead85
					
				@ -1399,6 +1399,8 @@
 | 
			
		||||
  "core.areyousure": "moodle",
 | 
			
		||||
  "core.back": "moodle",
 | 
			
		||||
  "core.block.blocks": "moodle",
 | 
			
		||||
  "core.block.noblocks": "error",
 | 
			
		||||
  "core.block.opendrawerblocks": "moodle",
 | 
			
		||||
  "core.browser": "local_moodlemobileapp",
 | 
			
		||||
  "core.cancel": "moodle",
 | 
			
		||||
  "core.cannotconnect": "local_moodlemobileapp",
 | 
			
		||||
 | 
			
		||||
@ -72,7 +72,3 @@
 | 
			
		||||
        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 { CoreBlockOnlyTitleComponent } from './only-title-block/only-title-block';
 | 
			
		||||
import { CoreBlockPreRenderedComponent } from './pre-rendered-block/pre-rendered-block';
 | 
			
		||||
import { CoreBlockCourseBlocksComponent } from './course-blocks/course-blocks';
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { CoreBlockSideBlocksComponent } from './side-blocks/side-blocks';
 | 
			
		||||
import { CoreBlockSideBlocksButtonComponent } from './side-blocks-button/side-blocks-button';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    declarations: [
 | 
			
		||||
        CoreBlockComponent,
 | 
			
		||||
        CoreBlockOnlyTitleComponent,
 | 
			
		||||
        CoreBlockPreRenderedComponent,
 | 
			
		||||
        CoreBlockCourseBlocksComponent,
 | 
			
		||||
        CoreBlockSideBlocksComponent,
 | 
			
		||||
        CoreBlockSideBlocksButtonComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
@ -33,7 +35,8 @@ import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
        CoreBlockComponent,
 | 
			
		||||
        CoreBlockOnlyTitleComponent,
 | 
			
		||||
        CoreBlockPreRenderedComponent,
 | 
			
		||||
        CoreBlockCourseBlocksComponent,
 | 
			
		||||
        CoreBlockSideBlocksComponent,
 | 
			
		||||
        CoreBlockSideBlocksButtonComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
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
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Component, ViewChildren, Input, OnInit, QueryList, ElementRef } from '@angular/core';
 | 
			
		||||
import { IonContent } from '@ionic/angular';
 | 
			
		||||
import { Component, ViewChildren, Input, OnInit, QueryList } from '@angular/core';
 | 
			
		||||
import { ModalController } from '@singletons';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreCourse, CoreCourseBlock } from '@features/course/services/course';
 | 
			
		||||
import { CoreBlockHelper } from '../../services/block-helper';
 | 
			
		||||
import { CoreBlockComponent } from '../block/block';
 | 
			
		||||
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({
 | 
			
		||||
    selector: 'core-block-course-blocks',
 | 
			
		||||
    templateUrl: 'core-block-course-blocks.html',
 | 
			
		||||
    styleUrls: ['course-blocks.scss'],
 | 
			
		||||
    selector: 'core-block-side-blocks',
 | 
			
		||||
    templateUrl: 'side-blocks.html',
 | 
			
		||||
})
 | 
			
		||||
export class CoreBlockCourseBlocksComponent implements OnInit {
 | 
			
		||||
export class CoreBlockSideBlocksComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
    @Input() courseId!: number;
 | 
			
		||||
    @Input() hideBlocks = false;
 | 
			
		||||
    @Input() hideBottomBlocks = false;
 | 
			
		||||
    @Input() downloadEnabled = false;
 | 
			
		||||
 | 
			
		||||
    @ViewChildren(CoreBlockComponent) blocksComponents?: QueryList<CoreBlockComponent>;
 | 
			
		||||
 | 
			
		||||
    dataLoaded = false;
 | 
			
		||||
    loaded = false;
 | 
			
		||||
    blocks: CoreCourseBlock[] = [];
 | 
			
		||||
 | 
			
		||||
    protected element: HTMLElement;
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        element: ElementRef,
 | 
			
		||||
        protected content: IonContent,
 | 
			
		||||
    ) {
 | 
			
		||||
        this.element = element.nativeElement;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Component being initialized.
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async ngOnInit(): Promise<void> {
 | 
			
		||||
        this.element.classList.add('core-no-blocks');
 | 
			
		||||
        this.loadContent().finally(() => {
 | 
			
		||||
            this.dataLoaded = true;
 | 
			
		||||
            this.loaded = true;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -87,7 +75,6 @@ export class CoreBlockCourseBlocksComponent implements OnInit {
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async loadContent(): Promise<void> {
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            this.blocks = await CoreBlockHelper.getCourseBlocks(this.courseId);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
@ -95,29 +82,26 @@ export class CoreBlockCourseBlocksComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
            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 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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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);
 | 
			
		||||
 | 
			
		||||
@ -6,126 +6,115 @@
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
</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"
 | 
			
		||||
    [hideBottomBlocks]="selectedSection && selectedSection.id == allSectionsId && canLoadMore">
 | 
			
		||||
            <div *ngIf="displaySectionSelector && sections && hasSeveralSections"
 | 
			
		||||
                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">
 | 
			
		||||
        <!-- Default course format. -->
 | 
			
		||||
        <core-loading [hideUntil]="loaded">
 | 
			
		||||
            <!-- Section selector. -->
 | 
			
		||||
            <core-dynamic-component [component]="sectionSelectorComponent" [data]="data">
 | 
			
		||||
 | 
			
		||||
                <div *ngIf="displaySectionSelector && sections && hasSeveralSections"
 | 
			
		||||
                    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>
 | 
			
		||||
        <!-- Course summary. By default we only display the course progress. -->
 | 
			
		||||
        <core-dynamic-component [component]="courseSummaryComponent" [data]="data">
 | 
			
		||||
            <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. -->
 | 
			
		||||
        <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>
 | 
			
		||||
 | 
			
		||||
            <!-- Course summary. By default we only display the course progress. -->
 | 
			
		||||
            <core-dynamic-component [component]="courseSummaryComponent" [data]="data">
 | 
			
		||||
                <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>
 | 
			
		||||
            <core-infinite-loading [enabled]="canLoadMore" (action)="showMoreActivities($event)"></core-infinite-loading>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
            <!-- 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>
 | 
			
		||||
        <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>
 | 
			
		||||
 | 
			
		||||
            <!-- 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-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>
 | 
			
		||||
        <core-block-side-blocks-button *ngIf="course && displayBlocks && hasBlocks" [courseId]="course.id"
 | 
			
		||||
            [downloadEnabled]="downloadEnabled">
 | 
			
		||||
        </core-block-side-blocks-button>
 | 
			
		||||
    </core-loading>
 | 
			
		||||
</core-dynamic-component>
 | 
			
		||||
 | 
			
		||||
<!-- Template to render a section. -->
 | 
			
		||||
<ng-template #sectionTemplate let-section="section">
 | 
			
		||||
    <section *ngIf="!section.hiddenbynumsections && section.id != allSectionsId && section.id != stealthModulesSectionId">
 | 
			
		||||
        <!-- Title is only displayed when viewing all sections. -->
 | 
			
		||||
        <ion-item-divider *ngIf="selectedSection?.id == allSectionsId && section.name" class="ion-text-wrap" color="light"
 | 
			
		||||
            [class.core-section-download]="downloadEnabled"
 | 
			
		||||
            [class.item-dimmed]="section.visible === 0 || section.uservisible === false">
 | 
			
		||||
            [class.core-section-download]="downloadEnabled" [class.item-dimmed]="section.visible === 0 || section.uservisible === false">
 | 
			
		||||
            <ion-label>
 | 
			
		||||
                <h2>
 | 
			
		||||
                    <core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id">
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,6 @@ import {
 | 
			
		||||
    ViewChildren,
 | 
			
		||||
    QueryList,
 | 
			
		||||
    Type,
 | 
			
		||||
    ViewChild,
 | 
			
		||||
    ElementRef,
 | 
			
		||||
} from '@angular/core';
 | 
			
		||||
import { ModalOptions } from '@ionic/core';
 | 
			
		||||
@ -48,8 +47,8 @@ import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
			
		||||
import { IonContent, IonRefresher } from '@ionic/angular';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
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 { 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.
 | 
			
		||||
@ -79,7 +78,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
 | 
			
		||||
    @Output() completionChanged = new EventEmitter<CoreCourseModuleCompletionData>(); // Notify when any module completion changes.
 | 
			
		||||
 | 
			
		||||
    @ViewChildren(CoreDynamicComponent) dynamicComponents?: QueryList<CoreDynamicComponent>;
 | 
			
		||||
    @ViewChild(CoreBlockCourseBlocksComponent) courseBlocksComponent?: CoreBlockCourseBlocksComponent;
 | 
			
		||||
 | 
			
		||||
    // All the possible component classes.
 | 
			
		||||
    courseFormatComponent?: Type<unknown>;
 | 
			
		||||
@ -92,8 +90,9 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
 | 
			
		||||
    showSectionId = 0;
 | 
			
		||||
    data: Record<string, unknown> = {}; // Data to pass to the components.
 | 
			
		||||
 | 
			
		||||
    displaySectionSelector?: boolean;
 | 
			
		||||
    displayBlocks?: boolean;
 | 
			
		||||
    displaySectionSelector = false;
 | 
			
		||||
    displayBlocks = false;
 | 
			
		||||
    hasBlocks = false;
 | 
			
		||||
    selectedSection?: CoreCourseSection;
 | 
			
		||||
    previousSection?: CoreCourseSection;
 | 
			
		||||
    nextSection?: CoreCourseSection;
 | 
			
		||||
@ -180,7 +179,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
 | 
			
		||||
    /**
 | 
			
		||||
     * Detect changes on input properties.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnChanges(changes: { [name: string]: SimpleChange }): void {
 | 
			
		||||
    async ngOnChanges(changes: { [name: string]: SimpleChange }): Promise<void> {
 | 
			
		||||
        this.setInputData();
 | 
			
		||||
        this.sectionSelectorModalOptions.componentProps!.course = this.course;
 | 
			
		||||
        this.sectionSelectorModalOptions.componentProps!.sections = this.sections;
 | 
			
		||||
@ -191,6 +190,9 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
 | 
			
		||||
 | 
			
		||||
            this.displaySectionSelector = CoreCourseFormatDelegate.displaySectionSelector(this.course);
 | 
			
		||||
            this.displayBlocks = CoreCourseFormatDelegate.displayBlocks(this.course);
 | 
			
		||||
 | 
			
		||||
            this.hasBlocks = await CoreBlockHelper.hasCourseBlocks(this.course.id);
 | 
			
		||||
 | 
			
		||||
            this.updateProgress();
 | 
			
		||||
 | 
			
		||||
            if ('overviewfiles' in this.course) {
 | 
			
		||||
@ -498,8 +500,13 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
 | 
			
		||||
            await component.callComponentFunction('doRefresh', [refresher, done, afterCompletionChange]);
 | 
			
		||||
        }) || [];
 | 
			
		||||
 | 
			
		||||
        if (this.courseBlocksComponent) {
 | 
			
		||||
            promises.push(this.courseBlocksComponent.doRefresh());
 | 
			
		||||
        if (this.course) {
 | 
			
		||||
            const courseId = this.course.id;
 | 
			
		||||
            promises.push(CoreCourse.invalidateCourseBlocks(courseId).then(async () => {
 | 
			
		||||
                this.hasBlocks = await CoreBlockHelper.hasCourseBlocks(courseId);
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await Promise.all(promises);
 | 
			
		||||
 | 
			
		||||
@ -3,11 +3,9 @@
 | 
			
		||||
        <ion-icon name="fas-search" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
    </ion-button>
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item [priority]="1000" *ngIf="displayEnableDownload"
 | 
			
		||||
            [content]="'core.settings.showdownloadoptions' | translate" (action)="switchDownload()"
 | 
			
		||||
            iconAction="toggle" [(toggle)]="downloadEnabled"></core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item [priority]="500"
 | 
			
		||||
            [content]="'addon.storagemanager.managestorage' | translate"
 | 
			
		||||
        <core-context-menu-item [priority]="1000" *ngIf="displayEnableDownload" [content]="'core.settings.showdownloadoptions' | translate"
 | 
			
		||||
            (action)="switchDownload()" iconAction="toggle" [(toggle)]="downloadEnabled"></core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item [priority]="500" [content]="'addon.storagemanager.managestorage' | translate"
 | 
			
		||||
            (action)="manageCoursesStorage()" iconAction="fas-archive"></core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
</core-navbar-buttons>
 | 
			
		||||
@ -15,49 +13,52 @@
 | 
			
		||||
    <ion-refresher slot="fixed" [disabled]="!dataLoaded" (ionRefresh)="doRefresh($event.target)">
 | 
			
		||||
        <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
			
		||||
    </ion-refresher>
 | 
			
		||||
    <core-block-course-blocks [courseId]="siteHomeId" [downloadEnabled]="downloadEnabled">
 | 
			
		||||
        <core-loading [hideUntil]="dataLoaded">
 | 
			
		||||
                <ion-list>
 | 
			
		||||
                    <!-- Site home main contents. -->
 | 
			
		||||
                    <ng-container *ngIf="section && section.hasContent">
 | 
			
		||||
                        <ion-item class="ion-text-wrap" *ngIf="section.summary">
 | 
			
		||||
                            <ion-label><core-format-text [text]="section.summary" contextLevel="course" [contextInstanceId]="siteHomeId">
 | 
			
		||||
                            </core-format-text></ion-label>
 | 
			
		||||
                        </ion-item>
 | 
			
		||||
    <core-loading [hideUntil]="dataLoaded">
 | 
			
		||||
        <ion-list>
 | 
			
		||||
            <!-- Site home main contents. -->
 | 
			
		||||
            <ng-container *ngIf="section && section.hasContent">
 | 
			
		||||
                <ion-item class="ion-text-wrap" *ngIf="section.summary">
 | 
			
		||||
                    <ion-label>
 | 
			
		||||
                        <core-format-text [text]="section.summary" contextLevel="course" [contextInstanceId]="siteHomeId">
 | 
			
		||||
                        </core-format-text>
 | 
			
		||||
                    </ion-label>
 | 
			
		||||
                </ion-item>
 | 
			
		||||
 | 
			
		||||
                        <core-course-module *ngFor="let module of section.modules" [module]="module" [courseId]="siteHomeId"
 | 
			
		||||
                            [downloadEnabled]="downloadEnabled" [section]="section"></core-course-module>
 | 
			
		||||
                    </ng-container>
 | 
			
		||||
                <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">
 | 
			
		||||
                        <core-spacer *ngIf="section && section!.hasContent"></core-spacer>
 | 
			
		||||
                        <ng-container *ngFor="let item of items">
 | 
			
		||||
                            <ng-container [ngSwitch]="item">
 | 
			
		||||
                                <ng-container *ngSwitchCase="'LIST_OF_COURSE'">
 | 
			
		||||
                                    <ng-template *ngTemplateOutlet="allCourseList"></ng-template>
 | 
			
		||||
                                </ng-container>
 | 
			
		||||
                                <ng-container *ngSwitchCase="'LIST_OF_CATEGORIES'">
 | 
			
		||||
                                    <ng-template *ngTemplateOutlet="categories"></ng-template>
 | 
			
		||||
                                </ng-container>
 | 
			
		||||
                                <ng-container *ngSwitchCase="'COURSE_SEARCH_BOX'">
 | 
			
		||||
                                    <ng-template *ngTemplateOutlet="courseSearch"></ng-template>
 | 
			
		||||
                                </ng-container>
 | 
			
		||||
                                <ng-container *ngSwitchCase="'ENROLLED_COURSES'">
 | 
			
		||||
                                    <ng-template *ngTemplateOutlet="enrolledCourseList"></ng-template>
 | 
			
		||||
                                </ng-container>
 | 
			
		||||
                                <ng-container *ngSwitchCase="'NEWS_ITEMS'">
 | 
			
		||||
                                    <ng-template *ngTemplateOutlet="news"></ng-template>
 | 
			
		||||
                                </ng-container>
 | 
			
		||||
                            </ng-container>
 | 
			
		||||
            <!-- Site home items: news, categories, courses, etc. -->
 | 
			
		||||
            <ng-container *ngIf="items.length > 0">
 | 
			
		||||
                <core-spacer *ngIf="section && section!.hasContent"></core-spacer>
 | 
			
		||||
                <ng-container *ngFor="let item of items">
 | 
			
		||||
                    <ng-container [ngSwitch]="item">
 | 
			
		||||
                        <ng-container *ngSwitchCase="'LIST_OF_COURSE'">
 | 
			
		||||
                            <ng-template *ngTemplateOutlet="allCourseList"></ng-template>
 | 
			
		||||
                        </ng-container>
 | 
			
		||||
                        <ng-container *ngSwitchCase="'LIST_OF_CATEGORIES'">
 | 
			
		||||
                            <ng-template *ngTemplateOutlet="categories"></ng-template>
 | 
			
		||||
                        </ng-container>
 | 
			
		||||
                        <ng-container *ngSwitchCase="'COURSE_SEARCH_BOX'">
 | 
			
		||||
                            <ng-template *ngTemplateOutlet="courseSearch"></ng-template>
 | 
			
		||||
                        </ng-container>
 | 
			
		||||
                        <ng-container *ngSwitchCase="'ENROLLED_COURSES'">
 | 
			
		||||
                            <ng-template *ngTemplateOutlet="enrolledCourseList"></ng-template>
 | 
			
		||||
                        </ng-container>
 | 
			
		||||
                        <ng-container *ngSwitchCase="'NEWS_ITEMS'">
 | 
			
		||||
                            <ng-template *ngTemplateOutlet="news"></ng-template>
 | 
			
		||||
                        </ng-container>
 | 
			
		||||
                    </ng-container>
 | 
			
		||||
                </ion-list>
 | 
			
		||||
            <core-empty-box *ngIf="!hasContent" icon="fas-box-open" [message]="'core.course.nocontentavailable' | translate">
 | 
			
		||||
                </ng-container>
 | 
			
		||||
            </ng-container>
 | 
			
		||||
        </ion-list>
 | 
			
		||||
        <core-block-side-blocks-button *ngIf="hasBlocks" [courseId]="siteHomeId" [downloadEnabled]="downloadEnabled">
 | 
			
		||||
        </core-block-side-blocks-button>
 | 
			
		||||
 | 
			
		||||
            </core-empty-box>
 | 
			
		||||
        </core-loading>
 | 
			
		||||
    </core-block-course-blocks>
 | 
			
		||||
        <core-empty-box *ngIf="!hasContent" icon="fas-box-open" [message]="'core.course.nocontentavailable' | translate">
 | 
			
		||||
 | 
			
		||||
        </core-empty-box>
 | 
			
		||||
    </core-loading>
 | 
			
		||||
</ion-content>
 | 
			
		||||
 | 
			
		||||
<ng-template #allCourseList>
 | 
			
		||||
@ -88,13 +89,17 @@
 | 
			
		||||
    <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>
 | 
			
		||||
        <ion-label><h2>{{ 'core.courses.mycourses' | translate}}</h2></ion-label>
 | 
			
		||||
        <ion-label>
 | 
			
		||||
            <h2>{{ 'core.courses.mycourses' | translate}}</h2>
 | 
			
		||||
        </ion-label>
 | 
			
		||||
    </ion-item>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
<ng-template #courseSearch>
 | 
			
		||||
    <ion-item button class="ion-text-wrap" (click)="openSearch()" detail="true">
 | 
			
		||||
        <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>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,7 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// 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 { Params } from '@angular/router';
 | 
			
		||||
 | 
			
		||||
@ -24,10 +24,11 @@ import { CoreSiteHome } from '@features/sitehome/services/sitehome';
 | 
			
		||||
import { CoreCourses, CoreCoursesProvider } from '@features//courses/services/courses';
 | 
			
		||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
			
		||||
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 { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
 | 
			
		||||
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.
 | 
			
		||||
@ -38,14 +39,13 @@ import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
})
 | 
			
		||||
export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
    @ViewChild(CoreBlockCourseBlocksComponent) courseBlocksComponent?: CoreBlockCourseBlocksComponent;
 | 
			
		||||
 | 
			
		||||
    dataLoaded = false;
 | 
			
		||||
    section?: CoreCourseWSSection & {
 | 
			
		||||
        hasContent?: boolean;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    hasContent = false;
 | 
			
		||||
    hasBlocks = false;
 | 
			
		||||
    items: string[] = [];
 | 
			
		||||
    siteHomeId = 1;
 | 
			
		||||
    currentSite!: CoreSite;
 | 
			
		||||
@ -106,8 +106,8 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
 | 
			
		||||
        this.items = await CoreSiteHome.getFrontPageItems(config.frontpageloggedin);
 | 
			
		||||
        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 {
 | 
			
		||||
                const forum = await CoreSiteHome.getNewsForum(this.siteHomeId);
 | 
			
		||||
                this.newsForumModule = await CoreCourse.getModule(forum.cmid, forum.course);
 | 
			
		||||
@ -140,17 +140,17 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Add log in Moodle.
 | 
			
		||||
            CoreCourse.logView(
 | 
			
		||||
            CoreUtils.ignoreErrors(CoreCourse.logView(
 | 
			
		||||
                this.siteHomeId,
 | 
			
		||||
                undefined,
 | 
			
		||||
                undefined,
 | 
			
		||||
                this.currentSite.getInfo()?.sitename,
 | 
			
		||||
            ).catch(() => {
 | 
			
		||||
                // Ignore errors.
 | 
			
		||||
            });
 | 
			
		||||
            ));
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            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;
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        promises.push(CoreCourse.invalidateCourseBlocks(this.siteHomeId));
 | 
			
		||||
 | 
			
		||||
        if (this.section && this.section.modules) {
 | 
			
		||||
            // Invalidate modules prefetch data.
 | 
			
		||||
            promises.push(CoreCourseModulePrefetchDelegate.invalidateModules(this.section.modules, this.siteHomeId));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.courseBlocksComponent) {
 | 
			
		||||
            promises.push(this.courseBlocksComponent.invalidateBlocks());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Promise.all(promises).finally(async () => {
 | 
			
		||||
            const p2: Promise<unknown>[] = [];
 | 
			
		||||
 | 
			
		||||
            p2.push(this.loadContent());
 | 
			
		||||
            if (this.courseBlocksComponent) {
 | 
			
		||||
                p2.push(this.courseBlocksComponent.loadContent());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await Promise.all(p2).finally(() => {
 | 
			
		||||
            await this.loadContent().finally(() => {
 | 
			
		||||
                refresher?.complete();
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@ -197,13 +197,6 @@
 | 
			
		||||
        --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-detail-icon-color: var(--gray-darker);
 | 
			
		||||
    --ion-item-detail-icon-font-size: 20px;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user