forked from EVOgeek/Vmeda.Online
		
	
						commit
						17305cbac6
					
				@ -40,11 +40,13 @@
 | 
			
		||||
  "addon.block_learningplans.pluginname": "block_lp",
 | 
			
		||||
  "addon.block_myoverview.all": "block_myoverview",
 | 
			
		||||
  "addon.block_myoverview.allincludinghidden": "block_myoverview",
 | 
			
		||||
  "addon.block_myoverview.favourites": "block_myoverview",
 | 
			
		||||
  "addon.block_myoverview.aria:hiddencourses": "block_myoverview",
 | 
			
		||||
  "addon.block_myoverview.card": "block_myoverview",
 | 
			
		||||
  "addon.block_myoverview.favouritesonly": "local_moodlemobileapp",
 | 
			
		||||
  "addon.block_myoverview.future": "block_myoverview",
 | 
			
		||||
  "addon.block_myoverview.hiddencourses": "block_myoverview",
 | 
			
		||||
  "addon.block_myoverview.inprogress": "block_myoverview",
 | 
			
		||||
  "addon.block_myoverview.lastaccessed": "block_myoverview",
 | 
			
		||||
  "addon.block_myoverview.list": "block_myoverview",
 | 
			
		||||
  "addon.block_myoverview.nocourses": "block_myoverview",
 | 
			
		||||
  "addon.block_myoverview.past": "block_myoverview",
 | 
			
		||||
  "addon.block_myoverview.pluginname": "block_myoverview",
 | 
			
		||||
@ -1400,6 +1402,7 @@
 | 
			
		||||
  "core.allparticipants": "moodle",
 | 
			
		||||
  "core.answer": "moodle",
 | 
			
		||||
  "core.answered": "quiz",
 | 
			
		||||
  "core.applyfilters": "user",
 | 
			
		||||
  "core.areyousure": "moodle",
 | 
			
		||||
  "core.back": "moodle",
 | 
			
		||||
  "core.block.blocks": "moodle",
 | 
			
		||||
@ -1553,6 +1556,7 @@
 | 
			
		||||
  "core.courses.password": "local_moodlemobileapp",
 | 
			
		||||
  "core.courses.paymentrequired": "moodle",
 | 
			
		||||
  "core.courses.paypalaccepted": "enrol_paypal",
 | 
			
		||||
  "core.courses.refreshcourses": "local_moodlemobileapp",
 | 
			
		||||
  "core.courses.reload": "moodle",
 | 
			
		||||
  "core.courses.removefromfavourites": "block_myoverview",
 | 
			
		||||
  "core.courses.search": "moodle",
 | 
			
		||||
 | 
			
		||||
@ -17,10 +17,12 @@ import { NgModule } from '@angular/core';
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { CoreCoursesComponentsModule } from '@features/courses/components/components.module';
 | 
			
		||||
import { AddonBlockMyOverviewComponent } from './myoverview/myoverview';
 | 
			
		||||
import { AddonBlockMyOverviewFilterOptionsComponent } from './filteroptions/filteroptions';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    declarations: [
 | 
			
		||||
        AddonBlockMyOverviewComponent,
 | 
			
		||||
        AddonBlockMyOverviewFilterOptionsComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,56 @@
 | 
			
		||||
<ion-header>
 | 
			
		||||
    <ion-toolbar>
 | 
			
		||||
        <h1>{{ 'core.courses.filtermycourses' | 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-list>
 | 
			
		||||
        <ion-radio-group [(ngModel)]="options.timeFilterSelected">
 | 
			
		||||
            <ion-item class="ion-text-wrap" *ngIf="options.show.all">
 | 
			
		||||
                <ion-label>{{'addon.block_myoverview.allincludinghidden' | translate}}</ion-label>
 | 
			
		||||
                <ion-radio slot="end" value="all"></ion-radio>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
            <ion-item class="ion-text-wrap" *ngIf="options.show.inprogress">
 | 
			
		||||
                <ion-label>{{'addon.block_myoverview.inprogress' | translate}}</ion-label>
 | 
			
		||||
                <ion-radio slot="end" value="inprogress"></ion-radio>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
            <ion-item class="ion-text-wrap" *ngIf="options.show.future">
 | 
			
		||||
                <ion-label>{{'addon.block_myoverview.future' | translate}}</ion-label>
 | 
			
		||||
                <ion-radio slot="end" value="future"></ion-radio>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
            <ion-item class="ion-text-wrap" *ngIf="options.show.past">
 | 
			
		||||
                <ion-label>{{'addon.block_myoverview.past' | translate}}</ion-label>
 | 
			
		||||
                <ion-radio slot="end" value="past"></ion-radio>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
        </ion-radio-group>
 | 
			
		||||
 | 
			
		||||
        <core-spacer *ngIf="options.show.custom"></core-spacer>
 | 
			
		||||
        <ion-radio-group [(ngModel)]="options.customSelected" *ngIf="options.show.custom" allowEmptySelection="true">
 | 
			
		||||
            <ion-item class="ion-text-wrap" *ngFor="let customOption of options.customFilters">
 | 
			
		||||
                <ion-label>{{customOption.name}}</ion-label>
 | 
			
		||||
                <ion-radio slot="end" [value]="customOption.value"></ion-radio>
 | 
			
		||||
            </ion-item>
 | 
			
		||||
        </ion-radio-group>
 | 
			
		||||
 | 
			
		||||
        <core-spacer></core-spacer>
 | 
			
		||||
        <ion-item *ngIf="options.show.favourite">
 | 
			
		||||
            <ion-label>{{ 'addon.block_myoverview.favouritesonly' | translate }}</ion-label>
 | 
			
		||||
            <ion-toggle [(ngModel)]="options.favouriteSelected"></ion-toggle>
 | 
			
		||||
        </ion-item>
 | 
			
		||||
 | 
			
		||||
        <ion-item *ngIf="options.show.hidden">
 | 
			
		||||
            <ion-label>{{ 'addon.block_myoverview.aria:hiddencourses' | translate }}</ion-label>
 | 
			
		||||
            <ion-toggle [(ngModel)]="options.hiddenSelected"></ion-toggle>
 | 
			
		||||
        </ion-item>
 | 
			
		||||
    </ion-list>
 | 
			
		||||
</ion-content>
 | 
			
		||||
<ion-footer class="ion-padding">
 | 
			
		||||
    <ion-button (click)="apply()" expand="block" [attr.aria-label]="'core.applyfilters' | translate" class="ion-text-wrap">
 | 
			
		||||
        {{ 'core.applyfilters' | translate }}
 | 
			
		||||
    </ion-button>
 | 
			
		||||
</ion-footer>
 | 
			
		||||
@ -0,0 +1,44 @@
 | 
			
		||||
// (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 { ModalController } from '@singletons';
 | 
			
		||||
import { AddonBlockMyOverviewFilterOptions } from '../myoverview/myoverview';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component to render a my overview filter options.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'addon-block-myoverview-filter-options',
 | 
			
		||||
    templateUrl: 'filteroptions.html',
 | 
			
		||||
})
 | 
			
		||||
export class AddonBlockMyOverviewFilterOptionsComponent {
 | 
			
		||||
 | 
			
		||||
    @Input() options!: AddonBlockMyOverviewFilterOptions;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Appl filters.
 | 
			
		||||
     */
 | 
			
		||||
    apply(): void {
 | 
			
		||||
        ModalController.dismiss(this.options);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Close modal.
 | 
			
		||||
     */
 | 
			
		||||
    closeModal(): void {
 | 
			
		||||
        ModalController.dismiss();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -4,97 +4,82 @@
 | 
			
		||||
    </ion-label>
 | 
			
		||||
    <div slot="end" class="flex-row">
 | 
			
		||||
        <!-- Download all courses. -->
 | 
			
		||||
        <div *ngIf="downloadCoursesEnabled && downloadEnabled && filteredCourses.length > 1 && !showFilter"
 | 
			
		||||
            class="core-button-spinner">
 | 
			
		||||
            <ion-button *ngIf="!prefetchCoursesData[selectedFilter].loading" fill="clear" color="dark" (click)="prefetchCourses()"
 | 
			
		||||
                [attr.aria-label]="'core.courses.downloadcourses' | translate">
 | 
			
		||||
                <ion-icon [name]="prefetchCoursesData[selectedFilter].icon" slot="icon-only" aria-hidden="true">
 | 
			
		||||
        <div *ngIf="downloadCoursesEnabled && downloadEnabled && filteredCourses.length > 1" class="core-button-spinner">
 | 
			
		||||
            <ion-button *ngIf="!prefetchCoursesData.loading" fill="clear" color="dark" (click)="prefetchCourses()"
 | 
			
		||||
                [attr.aria-label]="prefetchCoursesData.statusTranslatable | translate">
 | 
			
		||||
                <ion-icon [name]="prefetchCoursesData.icon" slot="icon-only" aria-hidden="true">
 | 
			
		||||
                </ion-icon>
 | 
			
		||||
            </ion-button>
 | 
			
		||||
            <ion-badge class="core-course-download-courses-progress" *ngIf="prefetchCoursesData[selectedFilter].badge"
 | 
			
		||||
                role="progressbar" [attr.aria-valuemax]="prefetchCoursesData[selectedFilter].total"
 | 
			
		||||
                [attr.aria-valuenow]="prefetchCoursesData[selectedFilter].count"
 | 
			
		||||
                [attr.aria-valuetext]="prefetchCoursesData[selectedFilter].badgeA11yText">
 | 
			
		||||
                {{prefetchCoursesData[selectedFilter].badge}}
 | 
			
		||||
            <ion-badge class="core-course-download-courses-progress" *ngIf="prefetchCoursesData.badge" role="progressbar"
 | 
			
		||||
                [attr.aria-valuemax]="prefetchCoursesData.total" [attr.aria-valuenow]="prefetchCoursesData.count"
 | 
			
		||||
                [attr.aria-valuetext]="prefetchCoursesData.badgeA11yText">
 | 
			
		||||
                {{prefetchCoursesData.badge}}
 | 
			
		||||
            </ion-badge>
 | 
			
		||||
            <ion-spinner *ngIf="prefetchCoursesData[selectedFilter].loading" [attr.aria-label]="'core.loading' | translate">
 | 
			
		||||
            <ion-spinner *ngIf="prefetchCoursesData.loading" [attr.aria-label]="'core.loading' | translate">
 | 
			
		||||
            </ion-spinner>
 | 
			
		||||
        </div>
 | 
			
		||||
        <core-context-menu>
 | 
			
		||||
            <core-context-menu-item *ngIf="loaded && showFilterSwitchButton()" [priority]="1000"
 | 
			
		||||
                [content]="'core.courses.filtermycourses' | translate" (action)="switchFilter()" iconAction="fas-filter"
 | 
			
		||||
                (onClosed)="switchFilterClosed()"></core-context-menu-item>
 | 
			
		||||
            <core-context-menu-item *ngIf="loaded && showSortFilter" [priority]="900"
 | 
			
		||||
                content="{{('core.sortby' | translate) + ' ' + ('addon.block_myoverview.title' | translate)}}"
 | 
			
		||||
                (action)="switchSort('fullname')" [iconAction]="sort == 'fullname' ? 'far-dot-circle' : 'far-circle'">
 | 
			
		||||
            </core-context-menu-item>
 | 
			
		||||
            <core-context-menu-item *ngIf="loaded && showSortFilter && showSortByShortName" [priority]="800"
 | 
			
		||||
                content="{{('core.sortby' | translate) + ' ' + ('addon.block_myoverview.shortname' | translate)}}"
 | 
			
		||||
                (action)="switchSort('shortname')" [iconAction]="sort == 'shortname' ? 'far-dot-circle' : 'far-circle'">
 | 
			
		||||
            </core-context-menu-item>
 | 
			
		||||
            <core-context-menu-item *ngIf="loaded && showSortFilter" [priority]="700"
 | 
			
		||||
                content="{{('core.sortby' | translate) + ' ' + ('addon.block_myoverview.lastaccessed' | translate)}}"
 | 
			
		||||
                (action)="switchSort('lastaccess')" [iconAction]="sort == 'lastaccess' ? 'far-dot-circle' : 'far-circle'">
 | 
			
		||||
            </core-context-menu-item>
 | 
			
		||||
        </core-context-menu>
 | 
			
		||||
    </div>
 | 
			
		||||
</ion-item-divider>
 | 
			
		||||
<core-loading [hideUntil]="loaded" [fullscreen]="false">
 | 
			
		||||
    <div class="safe-area-padding-horizontal" [hidden]="showFilter || !showSelectorFilter">
 | 
			
		||||
        <!-- "Time" selector. -->
 | 
			
		||||
        <core-combobox [label]="'core.show' | translate" [selection]="selectedFilter" (onChange)="selectedChanged($event)">
 | 
			
		||||
            <ion-select-option class="ion-text-wrap" value="allincludinghidden" *ngIf="showFilters.allincludinghidden != 'hidden'">
 | 
			
		||||
                {{ 'addon.block_myoverview.allincludinghidden' | translate }}
 | 
			
		||||
            </ion-select-option>
 | 
			
		||||
            <ion-select-option class="ion-text-wrap" value="all" *ngIf="showFilters.all != 'hidden'">
 | 
			
		||||
                {{ 'addon.block_myoverview.all' | translate }}
 | 
			
		||||
            </ion-select-option>
 | 
			
		||||
            <ion-select-option class="ion-text-wrap" value="inprogress" *ngIf="showFilters.inprogress != 'hidden'"
 | 
			
		||||
                [disabled]="showFilters.inprogress == 'disabled'">
 | 
			
		||||
                {{ 'addon.block_myoverview.inprogress' | translate }}
 | 
			
		||||
            </ion-select-option>
 | 
			
		||||
            <ion-select-option class="ion-text-wrap" value="future" *ngIf="showFilters.future != 'hidden'"
 | 
			
		||||
                [disabled]="showFilters.future == 'disabled'">
 | 
			
		||||
                {{ 'addon.block_myoverview.future' | translate }}
 | 
			
		||||
            </ion-select-option>
 | 
			
		||||
            <ion-select-option class="ion-text-wrap" value="past" *ngIf="showFilters.past != 'hidden'"
 | 
			
		||||
                [disabled]="showFilters.past == 'disabled'">
 | 
			
		||||
                {{ 'addon.block_myoverview.past' | translate }}
 | 
			
		||||
            </ion-select-option>
 | 
			
		||||
            <ng-container *ngIf="showFilters.custom != 'hidden'">
 | 
			
		||||
                <ng-container *ngFor="let customOption of customFilter; let index = index">
 | 
			
		||||
                    <ion-select-option class="ion-text-wrap" value="custom-{{index}}">{{ customOption.name }}</ion-select-option>
 | 
			
		||||
                </ng-container>
 | 
			
		||||
            </ng-container>
 | 
			
		||||
            <ion-select-option class="ion-text-wrap" value="favourite" *ngIf="showFilters.favourite != 'hidden'"
 | 
			
		||||
                [disabled]="showFilters.favourite == 'disabled'">
 | 
			
		||||
                {{ 'addon.block_myoverview.favourites' | translate }}
 | 
			
		||||
            </ion-select-option>
 | 
			
		||||
            <ion-select-option class="ion-text-wrap" value="hidden" *ngIf="showFilters.hidden != 'hidden'"
 | 
			
		||||
                [disabled]="showFilters.hidden == 'disabled'">
 | 
			
		||||
                {{ 'addon.block_myoverview.hiddencourses' | translate }}
 | 
			
		||||
            </ion-select-option>
 | 
			
		||||
        </core-combobox>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- Filter courses. -->
 | 
			
		||||
    <ion-searchbar #searchbar *ngIf="showFilter" [(ngModel)]="courses.filter" (ionInput)="filterChanged($event)"
 | 
			
		||||
        (ionCancel)="filterChanged($event)" [placeholder]="'core.courses.filtermycourses' | translate">
 | 
			
		||||
    </ion-searchbar>
 | 
			
		||||
    <ion-row class="ion-no-padding ion-justify-content-between" *ngIf="hasCourses">
 | 
			
		||||
        <ion-col size="auto" class="ion-no-padding" *ngIf="filters.enabled">
 | 
			
		||||
            <core-combobox interface="modal" [label]="'core.courses.filtermycourses' | translate" (onChange)="filterOptionsChanged($event)"
 | 
			
		||||
                icon="fas-filter" [badge]="filters.count" [modalOptions]="filterModalOptions">
 | 
			
		||||
            </core-combobox>
 | 
			
		||||
        </ion-col>
 | 
			
		||||
        <ion-col class="ion-no-padding">
 | 
			
		||||
            <!-- Filter courses. -->
 | 
			
		||||
            <ion-searchbar class="ion-hide-md-down" [(ngModel)]="textFilter" (ionInput)="filterTextChanged($event.target)"
 | 
			
		||||
                (ionCancel)="filterTextChanged($event.target)" [placeholder]="'core.filter' | translate">
 | 
			
		||||
            </ion-searchbar>
 | 
			
		||||
        </ion-col>
 | 
			
		||||
        <ion-col size="auto" class="ion-no-padding" *ngIf="sort.enabled">
 | 
			
		||||
            <core-combobox [label]="'core.sortby' | translate" [selection]="sort.selected" (onChange)="sortCourses($event)"
 | 
			
		||||
                icon="fas-sort-amount-down-alt">
 | 
			
		||||
                <ion-select-option class="ion-text-wrap" value="fullname">
 | 
			
		||||
                    {{'addon.block_myoverview.title' | translate}}
 | 
			
		||||
                </ion-select-option>
 | 
			
		||||
                <ion-select-option class="ion-text-wrap" value="shortname" *ngIf="sort.shortnameEnabled">
 | 
			
		||||
                    {{'addon.block_myoverview.shortname' | translate}}
 | 
			
		||||
                </ion-select-option>
 | 
			
		||||
                <ion-select-option class="ion-text-wrap" value="lastaccess">
 | 
			
		||||
                    {{'addon.block_myoverview.lastaccessed' | translate}}
 | 
			
		||||
                </ion-select-option>
 | 
			
		||||
            </core-combobox>
 | 
			
		||||
        </ion-col>
 | 
			
		||||
        <ion-col size="auto" class="ion-no-padding" *ngIf="layouts.options.length > 1">
 | 
			
		||||
            <!-- "Layouts" selector. -->
 | 
			
		||||
            <core-combobox [label]="'core.show' | translate" [selection]="layouts.selected" (onChange)="saveLayout($event)" icon="fas-th">
 | 
			
		||||
                <ng-container *ngFor="let layout of layouts.options">
 | 
			
		||||
                    <ion-select-option class="ion-text-wrap" [value]="layout">{{ 'addon.block_myoverview.'+layout | translate }}
 | 
			
		||||
                    </ion-select-option>
 | 
			
		||||
                </ng-container>
 | 
			
		||||
            </core-combobox>
 | 
			
		||||
        </ion-col>
 | 
			
		||||
    </ion-row>
 | 
			
		||||
    <ion-row class="ion-no-padding ion-hide-md-up" *ngIf="hasCourses">
 | 
			
		||||
        <ion-col class="ion-no-padding">
 | 
			
		||||
            <!-- Filter courses. -->
 | 
			
		||||
            <ion-searchbar [(ngModel)]="textFilter" (ionInput)="filterTextChanged($event.target)"
 | 
			
		||||
                (ionCancel)="filterTextChanged($event.target)" [placeholder]="'core.filter' | translate">
 | 
			
		||||
            </ion-searchbar>
 | 
			
		||||
        </ion-col>
 | 
			
		||||
    </ion-row>
 | 
			
		||||
 | 
			
		||||
    <core-empty-box *ngIf="filteredCourses.length == 0" image="assets/img/icons/courses.svg"
 | 
			
		||||
        [message]="'addon.block_myoverview.nocourses' | translate" inline="true">
 | 
			
		||||
    </core-empty-box>
 | 
			
		||||
 | 
			
		||||
    <!-- List of courses. -->
 | 
			
		||||
    <div class="safe-area-padding">
 | 
			
		||||
        <ion-grid class="ion-no-padding">
 | 
			
		||||
    <div class="safe-area-padding" *ngIf="hasCourses">
 | 
			
		||||
        <ion-grid class="ion-no-padding" [class.core-no-grid]="layouts.selected != 'card'">
 | 
			
		||||
            <ion-row class="ion-no-padding">
 | 
			
		||||
                <ion-col *ngFor="let course of filteredCourses" class="ion-no-padding"
 | 
			
		||||
                    size="12" size-sm="6" size-md="6" size-lg="4" size-xl="3">
 | 
			
		||||
                    <core-courses-course-progress [course]="course" class="core-courseoverview" showAll="true"
 | 
			
		||||
                        [showDownload]="downloadCourseEnabled && downloadEnabled">
 | 
			
		||||
                    </core-courses-course-progress>
 | 
			
		||||
                <ion-col *ngFor="let course of filteredCourses" class="ion-no-padding" size="12" size-sm="6" size-md="6" size-lg="4"
 | 
			
		||||
                    size-xl="3">
 | 
			
		||||
                    <core-courses-course-list-item [course]="course" class="core-courseoverview"
 | 
			
		||||
                        [showDownload]="downloadCourseEnabled && downloadEnabled" [layout]="layouts.selected">
 | 
			
		||||
                    </core-courses-course-list-item>
 | 
			
		||||
                </ion-col>
 | 
			
		||||
            </ion-row>
 | 
			
		||||
        </ion-grid>
 | 
			
		||||
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -1,11 +1,13 @@
 | 
			
		||||
{
 | 
			
		||||
    "all": "All (except removed from view)",
 | 
			
		||||
    "allincludinghidden": "All",
 | 
			
		||||
    "favourites": "Starred",
 | 
			
		||||
    "aria:hiddencourses": "Show courses removed from view",
 | 
			
		||||
    "card": "Card",
 | 
			
		||||
    "favouritesonly": "Show starred courses only",
 | 
			
		||||
    "future": "Future",
 | 
			
		||||
    "hiddencourses": "Removed from view",
 | 
			
		||||
    "inprogress": "In progress",
 | 
			
		||||
    "lastaccessed": "Last accessed",
 | 
			
		||||
    "list": "List",
 | 
			
		||||
    "nocourses": "No courses",
 | 
			
		||||
    "past": "Past",
 | 
			
		||||
    "pluginname": "Course overview",
 | 
			
		||||
 | 
			
		||||
@ -3,20 +3,6 @@
 | 
			
		||||
        <h2>{{ 'addon.block_recentlyaccessedcourses.pluginname' | translate }}</h2>
 | 
			
		||||
    </ion-label>
 | 
			
		||||
    <div slot="end" class="flex-row">
 | 
			
		||||
        <div *ngIf="downloadCoursesEnabled && downloadEnabled && courses && courses.length > 1" class="core-button-spinner">
 | 
			
		||||
            <ion-button *ngIf="prefetchCoursesData.icon && !prefetchCoursesData.loading" fill="clear" color="dark"
 | 
			
		||||
                (click)="prefetchCourses()" [attr.aria-label]="'core.courses.downloadcourses' | translate">
 | 
			
		||||
                <ion-icon [name]="prefetchCoursesData.icon" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
            </ion-button>
 | 
			
		||||
            <ion-badge class="core-course-download-courses-progress" *ngIf="prefetchCoursesData.badge"
 | 
			
		||||
                role="progressbar" [attr.aria-valuemax]="prefetchCoursesData.total"
 | 
			
		||||
                [attr.aria-valuenow]="prefetchCoursesData.count" [attr.aria-valuetext]="prefetchCoursesData.badgeA11yText">
 | 
			
		||||
                {{prefetchCoursesData.badge}}
 | 
			
		||||
            </ion-badge>
 | 
			
		||||
            <ion-spinner *ngIf="!prefetchCoursesData.icon || prefetchCoursesData.loading"
 | 
			
		||||
                [attr.aria-label]="'core.loading' | translate"></ion-spinner>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <core-horizontal-scroll-controls #scrollControls [aria-controls]="scrollElementId">
 | 
			
		||||
        </core-horizontal-scroll-controls>
 | 
			
		||||
    </div>
 | 
			
		||||
@ -25,16 +11,13 @@
 | 
			
		||||
    <core-empty-box *ngIf="courses.length == 0" image="assets/img/icons/courses.svg" inline="true"
 | 
			
		||||
        [message]="'addon.block_recentlyaccessedcourses.nocourses' | translate"></core-empty-box>
 | 
			
		||||
    <!-- List of courses. -->
 | 
			
		||||
    <div
 | 
			
		||||
        [id]="scrollElementId"
 | 
			
		||||
        class="core-horizontal-scroll"
 | 
			
		||||
        (scroll)="scrollControls.updateScrollPosition()"
 | 
			
		||||
    >
 | 
			
		||||
    <div [hidden]="courses.length === 0" [id]="scrollElementId" class="core-horizontal-scroll"
 | 
			
		||||
        (scroll)="scrollControls.updateScrollPosition()">
 | 
			
		||||
        <div (onResize)="scrollControls.updateScrollPosition()" class="flex-row">
 | 
			
		||||
            <div class="safe-area-pseudo-padding-start"></div>
 | 
			
		||||
            <ng-container *ngFor="let course of courses">
 | 
			
		||||
                <core-courses-course-progress [course]="course" class="core-recentlyaccessedcourses"
 | 
			
		||||
                    [showDownload]="downloadCourseEnabled && downloadEnabled"></core-courses-course-progress>
 | 
			
		||||
                <core-courses-course-list-item [course]="course" class="core-recentlyaccessedcourses" layout="summarycard"
 | 
			
		||||
                    [showDownload]="downloadCourseEnabled && downloadEnabled"></core-courses-course-list-item>
 | 
			
		||||
            </ng-container>
 | 
			
		||||
            <div class="safe-area-pseudo-padding-end"></div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,7 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Component, OnInit, OnDestroy, Input, OnChanges, SimpleChange } from '@angular/core';
 | 
			
		||||
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
 | 
			
		||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import {
 | 
			
		||||
@ -21,13 +21,11 @@ import {
 | 
			
		||||
    CoreCourses,
 | 
			
		||||
    CoreCourseSummaryData,
 | 
			
		||||
} from '@features/courses/services/courses';
 | 
			
		||||
import { CoreCourseSearchedDataWithExtraInfoAndOptions, CoreCoursesHelper } from '@features/courses/services/courses-helper';
 | 
			
		||||
import { CoreCourseHelper, CorePrefetchStatusInfo } from '@features/course/services/course-helper';
 | 
			
		||||
import { CoreCourseSearchedDataWithExtraInfoAndOptions } from '@features/courses/services/courses-helper';
 | 
			
		||||
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
 | 
			
		||||
import { AddonCourseCompletion } from '@/addons/coursecompletion/services/coursecompletion';
 | 
			
		||||
import { CoreBlockBaseComponent } from '@features/block/classes/base-block-component';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component to render a recent courses block.
 | 
			
		||||
@ -36,24 +34,15 @@ import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
    selector: 'addon-block-recentlyaccessedcourses',
 | 
			
		||||
    templateUrl: 'addon-block-recentlyaccessedcourses.html',
 | 
			
		||||
})
 | 
			
		||||
export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseComponent implements OnInit, OnChanges, OnDestroy {
 | 
			
		||||
export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseComponent implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
    @Input() downloadEnabled = false;
 | 
			
		||||
 | 
			
		||||
    courses: (Omit<CoreCourseSummaryData, 'visible'> & CoreCourseSearchedDataWithExtraInfoAndOptions)[] = [];
 | 
			
		||||
    prefetchCoursesData: CorePrefetchStatusInfo = {
 | 
			
		||||
        icon: '',
 | 
			
		||||
        statusTranslatable: 'core.loading',
 | 
			
		||||
        status: '',
 | 
			
		||||
        loading: true,
 | 
			
		||||
        badge: '',
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    downloadCourseEnabled = false;
 | 
			
		||||
    downloadCoursesEnabled = false;
 | 
			
		||||
    scrollElementId!: string;
 | 
			
		||||
 | 
			
		||||
    protected prefetchIconsInitialized = false;
 | 
			
		||||
    protected isDestroyed = false;
 | 
			
		||||
    protected coursesObserver?: CoreEventObserver;
 | 
			
		||||
    protected updateSiteObserver?: CoreEventObserver;
 | 
			
		||||
@ -74,12 +63,10 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
 | 
			
		||||
 | 
			
		||||
        // Refresh the enabled flags if enabled.
 | 
			
		||||
        this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
 | 
			
		||||
        this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
 | 
			
		||||
 | 
			
		||||
        // Refresh the enabled flags if site is updated.
 | 
			
		||||
        this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
 | 
			
		||||
            this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
 | 
			
		||||
            this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
 | 
			
		||||
 | 
			
		||||
        }, CoreSites.getCurrentSiteId());
 | 
			
		||||
 | 
			
		||||
@ -94,16 +81,6 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
 | 
			
		||||
        super.ngOnInit();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    ngOnChanges(changes: {[name: string]: SimpleChange}): void {
 | 
			
		||||
        if (changes.downloadEnabled && !changes.downloadEnabled.previousValue && this.downloadEnabled && this.loaded) {
 | 
			
		||||
            // Download all courses is enabled now, initialize it.
 | 
			
		||||
            this.initPrefetchCoursesIcons();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
@ -136,9 +113,7 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
 | 
			
		||||
            promises.push(CoreCourses.invalidateCoursesByField('ids', courseIds.join(',')));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await CoreUtils.allPromises(promises).finally(() => {
 | 
			
		||||
            this.prefetchIconsInitialized = false;
 | 
			
		||||
        });
 | 
			
		||||
        await CoreUtils.allPromises(promises);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -170,24 +145,6 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
 | 
			
		||||
                course.categoryname = '';
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        await CoreCoursesHelper.loadCoursesColorAndImage(courses);
 | 
			
		||||
 | 
			
		||||
        this.initPrefetchCoursesIcons();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initialize the prefetch icon for selected courses.
 | 
			
		||||
     */
 | 
			
		||||
    protected async initPrefetchCoursesIcons(): Promise<void> {
 | 
			
		||||
        if (this.prefetchIconsInitialized || !this.downloadEnabled) {
 | 
			
		||||
            // Already initialized.
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.prefetchIconsInitialized = true;
 | 
			
		||||
 | 
			
		||||
        this.prefetchCoursesData = await CoreCourseHelper.initPrefetchCoursesIcons(this.courses, this.prefetchCoursesData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -221,26 +178,6 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
 | 
			
		||||
            data.state == CoreCoursesProvider.STATE_FAVOURITE && course) {
 | 
			
		||||
            course.isfavourite = !!data.value;
 | 
			
		||||
            await this.invalidateCourses([course.id]);
 | 
			
		||||
 | 
			
		||||
            this.initPrefetchCoursesIcons();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Prefetch all the shown courses.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async prefetchCourses(): Promise<void> {
 | 
			
		||||
        const initialIcon = this.prefetchCoursesData.icon;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await CoreCourseHelper.prefetchCourses(this.courses, this.prefetchCoursesData);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (!this.isDestroyed) {
 | 
			
		||||
                CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
 | 
			
		||||
                this.prefetchCoursesData.icon = initialIcon;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -3,20 +3,6 @@
 | 
			
		||||
        <h2>{{ 'addon.block_starredcourses.pluginname' | translate }}</h2>
 | 
			
		||||
    </ion-label>
 | 
			
		||||
    <div slot="end" class="flex-row">
 | 
			
		||||
        <div *ngIf="downloadCoursesEnabled && downloadEnabled && courses && courses.length > 1" class="core-button-spinner">
 | 
			
		||||
            <ion-button *ngIf="prefetchCoursesData.icon && !prefetchCoursesData.loading" fill="clear" color="dark"
 | 
			
		||||
                (click)="prefetchCourses()" [attr.aria-label]="'core.courses.downloadcourses' | translate">
 | 
			
		||||
                <ion-icon [name]="prefetchCoursesData.icon" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
            </ion-button>
 | 
			
		||||
            <ion-badge class="core-course-download-courses-progress" *ngIf="prefetchCoursesData.badge"
 | 
			
		||||
                role="progressbar" [attr.aria-valuemax]="prefetchCoursesData.total"
 | 
			
		||||
                [attr.aria-valuenow]="prefetchCoursesData.count" [attr.aria-valuetext]="prefetchCoursesData.badgeA11yText">
 | 
			
		||||
                {{prefetchCoursesData.badge}}
 | 
			
		||||
            </ion-badge>
 | 
			
		||||
            <ion-spinner *ngIf="!prefetchCoursesData.icon || prefetchCoursesData.loading"
 | 
			
		||||
                [attr.aria-label]="'core.loading' | translate"></ion-spinner>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <core-horizontal-scroll-controls #scrollControls [aria-controls]="scrollElementId">
 | 
			
		||||
        </core-horizontal-scroll-controls>
 | 
			
		||||
    </div>
 | 
			
		||||
@ -25,17 +11,13 @@
 | 
			
		||||
    <core-empty-box *ngIf="courses.length == 0" image="assets/img/icons/courses.svg" inline="true"
 | 
			
		||||
        [message]="'addon.block_starredcourses.nocourses' | translate"></core-empty-box>
 | 
			
		||||
    <!-- List of courses. -->
 | 
			
		||||
    <div
 | 
			
		||||
        [hidden]="courses.length === 0"
 | 
			
		||||
        [id]="scrollElementId"
 | 
			
		||||
        class="core-horizontal-scroll"
 | 
			
		||||
        (scroll)="scrollControls.updateScrollPosition()"
 | 
			
		||||
    >
 | 
			
		||||
    <div [hidden]="courses.length === 0" [id]="scrollElementId" class="core-horizontal-scroll"
 | 
			
		||||
        (scroll)="scrollControls.updateScrollPosition()">
 | 
			
		||||
        <div (onResize)="scrollControls.updateScrollPosition()" class="flex-row">
 | 
			
		||||
            <div class="safe-area-pseudo-padding-start"></div>
 | 
			
		||||
            <ng-container *ngFor="let course of courses">
 | 
			
		||||
                <core-courses-course-progress [course]="course" class="core-block_starredcourses"
 | 
			
		||||
                    [showDownload]="downloadCourseEnabled && downloadEnabled"></core-courses-course-progress>
 | 
			
		||||
                <core-courses-course-list-item [course]="course" class="core-block_starredcourses" layout="summarycard"
 | 
			
		||||
                    [showDownload]="downloadCourseEnabled && downloadEnabled"></core-courses-course-list-item>
 | 
			
		||||
            </ng-container>
 | 
			
		||||
            <div class="safe-area-pseudo-padding-end"></div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
@ -12,17 +12,15 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Component, OnInit, OnDestroy, Input, OnChanges, SimpleChange } from '@angular/core';
 | 
			
		||||
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
 | 
			
		||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreCoursesProvider, CoreCoursesMyCoursesUpdatedEventData, CoreCourses } from '@features/courses/services/courses';
 | 
			
		||||
import { CoreCoursesHelper, CoreEnrolledCourseDataWithOptions } from '@features/courses/services/courses-helper';
 | 
			
		||||
import { CoreCourseHelper, CorePrefetchStatusInfo } from '@features/course/services/course-helper';
 | 
			
		||||
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
 | 
			
		||||
import { AddonCourseCompletion } from '@/addons/coursecompletion/services/coursecompletion';
 | 
			
		||||
import { CoreBlockBaseComponent } from '@features/block/classes/base-block-component';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component to render a starred courses block.
 | 
			
		||||
@ -31,24 +29,15 @@ import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
    selector: 'addon-block-starredcourses',
 | 
			
		||||
    templateUrl: 'addon-block-starredcourses.html',
 | 
			
		||||
})
 | 
			
		||||
export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent implements OnInit, OnChanges, OnDestroy {
 | 
			
		||||
export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
    @Input() downloadEnabled = false;
 | 
			
		||||
 | 
			
		||||
    courses: CoreEnrolledCourseDataWithOptions [] = [];
 | 
			
		||||
    prefetchCoursesData: CorePrefetchStatusInfo = {
 | 
			
		||||
        icon: '',
 | 
			
		||||
        statusTranslatable: 'core.loading',
 | 
			
		||||
        status: '',
 | 
			
		||||
        loading: true,
 | 
			
		||||
        badge: '',
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    downloadCourseEnabled = false;
 | 
			
		||||
    downloadCoursesEnabled = false;
 | 
			
		||||
    scrollElementId!: string;
 | 
			
		||||
 | 
			
		||||
    protected prefetchIconsInitialized = false;
 | 
			
		||||
    protected isDestroyed = false;
 | 
			
		||||
    protected coursesObserver?: CoreEventObserver;
 | 
			
		||||
    protected updateSiteObserver?: CoreEventObserver;
 | 
			
		||||
@ -69,12 +58,10 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im
 | 
			
		||||
 | 
			
		||||
        // Refresh the enabled flags if enabled.
 | 
			
		||||
        this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
 | 
			
		||||
        this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
 | 
			
		||||
 | 
			
		||||
        // Refresh the enabled flags if site is updated.
 | 
			
		||||
        this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
 | 
			
		||||
            this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
 | 
			
		||||
            this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
 | 
			
		||||
        }, CoreSites.getCurrentSiteId());
 | 
			
		||||
 | 
			
		||||
        this.coursesObserver = CoreEvents.on(
 | 
			
		||||
@ -89,16 +76,6 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im
 | 
			
		||||
        super.ngOnInit();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    ngOnChanges(changes: {[name: string]: SimpleChange}): void {
 | 
			
		||||
        if (changes.downloadEnabled && !changes.downloadEnabled.previousValue && this.downloadEnabled && this.loaded) {
 | 
			
		||||
            // Download all courses is enabled now, initialize it.
 | 
			
		||||
            this.initPrefetchCoursesIcons();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
@ -131,9 +108,7 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im
 | 
			
		||||
            promises.push(CoreCourses.invalidateCoursesByField('ids', courseIds.join(',')));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await CoreUtils.allPromises(promises).finally(() => {
 | 
			
		||||
            this.prefetchIconsInitialized = false;
 | 
			
		||||
        });
 | 
			
		||||
        await CoreUtils.allPromises(promises);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -145,8 +120,6 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im
 | 
			
		||||
 | 
			
		||||
        // @TODO: Sort won't coincide with website because timemodified is not informed.
 | 
			
		||||
        this.courses = await CoreCoursesHelper.getUserCoursesWithOptions('timemodified', 0, 'isfavourite', showCategories);
 | 
			
		||||
 | 
			
		||||
        this.initPrefetchCoursesIcons();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -182,43 +155,10 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await this.invalidateCourses([course.id]);
 | 
			
		||||
            this.initPrefetchCoursesIcons();
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initialize the prefetch icon for selected courses.
 | 
			
		||||
     */
 | 
			
		||||
    protected async initPrefetchCoursesIcons(): Promise<void> {
 | 
			
		||||
        if (this.prefetchIconsInitialized || !this.downloadEnabled) {
 | 
			
		||||
            // Already initialized.
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.prefetchIconsInitialized = true;
 | 
			
		||||
 | 
			
		||||
        this.prefetchCoursesData = await CoreCourseHelper.initPrefetchCoursesIcons(this.courses, this.prefetchCoursesData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Prefetch all the shown courses.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async prefetchCourses(): Promise<void> {
 | 
			
		||||
        const initialIcon = this.prefetchCoursesData.icon;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            return CoreCourseHelper.prefetchCourses(this.courses, this.prefetchCoursesData);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (!this.isDestroyed) {
 | 
			
		||||
                CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
 | 
			
		||||
                this.prefetchCoursesData.icon = initialIcon;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
@ -429,7 +429,7 @@ export class CoreCourseHelperProvider {
 | 
			
		||||
        const siteId = CoreSites.getCurrentSiteId();
 | 
			
		||||
 | 
			
		||||
        // Confirm the download without checking size because it could take a while.
 | 
			
		||||
        await CoreDomUtils.showConfirm(Translate.instant('core.areyousure'));
 | 
			
		||||
        await CoreDomUtils.showConfirm(Translate.instant('core.areyousure'), Translate.instant('core.courses.downloadcourses'));
 | 
			
		||||
 | 
			
		||||
        const total = courses.length;
 | 
			
		||||
        let count = 0;
 | 
			
		||||
@ -1209,7 +1209,7 @@ export class CoreCourseHelperProvider {
 | 
			
		||||
 | 
			
		||||
        const status = await this.determineCoursesStatus(courses);
 | 
			
		||||
 | 
			
		||||
        prefetch = this.getCoursePrefetchStatusInfo(status);
 | 
			
		||||
        prefetch = this.getCoursesPrefetchStatusInfo(status);
 | 
			
		||||
 | 
			
		||||
        if (prefetch.loading) {
 | 
			
		||||
            // It seems all courses are being downloaded, show a download button instead.
 | 
			
		||||
@ -1381,6 +1381,33 @@ export class CoreCourseHelperProvider {
 | 
			
		||||
        return prefetchStatus;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a courses status icon and the langkey to use as a title from status.
 | 
			
		||||
     *
 | 
			
		||||
     * @param status Courses status.
 | 
			
		||||
     * @return Prefetch status info.
 | 
			
		||||
     */
 | 
			
		||||
    getCoursesPrefetchStatusInfo(status: string): CorePrefetchStatusInfo {
 | 
			
		||||
        const prefetchStatus: CorePrefetchStatusInfo = {
 | 
			
		||||
            status: status,
 | 
			
		||||
            icon: this.getPrefetchStatusIcon(status, false),
 | 
			
		||||
            statusTranslatable: '',
 | 
			
		||||
            loading: false,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (status == CoreConstants.DOWNLOADED) {
 | 
			
		||||
            // Always show refresh icon, we cannot know if there's anything new in course options.
 | 
			
		||||
            prefetchStatus.statusTranslatable = 'core.courses.refreshcourses';
 | 
			
		||||
        } else if (status == CoreConstants.DOWNLOADING) {
 | 
			
		||||
            prefetchStatus.statusTranslatable = 'core.downloading';
 | 
			
		||||
            prefetchStatus.loading = true;
 | 
			
		||||
        } else {
 | 
			
		||||
            prefetchStatus.statusTranslatable = 'core.courses.downloadcourses';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return prefetchStatus;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the icon given the status and if trust the download status.
 | 
			
		||||
     *
 | 
			
		||||
 | 
			
		||||
@ -1,47 +1,106 @@
 | 
			
		||||
<ion-item class="ion-text-wrap" (click)="openCourse()" [class.item-disabled]="course.visible == 0"
 | 
			
		||||
    [attr.aria-label]="course.displayname || course.fullname" detail="true" button>
 | 
			
		||||
    <ion-icon *ngIf="!course.courseImage" name="fas-graduation-cap" slot="start" class="course-icon"
 | 
			
		||||
        [attr.course-color]="course.color ? null : course.colorNumber" [style.color]="course.color"></ion-icon>
 | 
			
		||||
    <ion-avatar *ngIf="course.courseImage" slot="start">
 | 
			
		||||
        <img [src]="course.courseImage" core-external-content alt=""/>
 | 
			
		||||
<ion-item class="ion-text-wrap core-course-list-item" (click)="openCourse()" [class.item-disabled]="course.visible == 0"
 | 
			
		||||
    [class.item-dimmed]="course.hidden" [attr.aria-label]="course.displayname || course.fullname" detail="true" button
 | 
			
		||||
    *ngIf="layout == 'list' || layout == 'listwithenrol'">
 | 
			
		||||
    <ion-icon *ngIf="!course.courseImage" name="fas-graduation-cap" slot="start" class="course-icon core-course-thumb"
 | 
			
		||||
        [attr.course-color]="course.color ? null : course.colorNumber" [style.color]="course.color">
 | 
			
		||||
    </ion-icon>
 | 
			
		||||
    <ion-avatar *ngIf="course.courseImage" slot="start" class="core-course-thumb">
 | 
			
		||||
        <img [src]="course.courseImage" core-external-content alt="" />
 | 
			
		||||
    </ion-avatar>
 | 
			
		||||
    <ion-label>
 | 
			
		||||
        <h2>
 | 
			
		||||
            <core-format-text [text]="course.displayname || course.fullname" contextLevel="course" [contextInstanceId]="course.id">
 | 
			
		||||
            </core-format-text>
 | 
			
		||||
        </h2>
 | 
			
		||||
        <p *ngIf="course.categoryname || (course.displayname && course.shortname && course.fullname != course.displayname)"
 | 
			
		||||
            class="core-course-additional-info">
 | 
			
		||||
            <span *ngIf="course.categoryname" class="core-course-category">
 | 
			
		||||
                <core-format-text [text]="course.categoryname"></core-format-text>
 | 
			
		||||
            </span>
 | 
			
		||||
            <span *ngIf="course.categoryname && course.displayname && course.shortname && course.fullname != course.displayname"
 | 
			
		||||
                class="core-course-category"> | </span>
 | 
			
		||||
            <span *ngIf="course.displayname && course.shortname && course.fullname != course.displayname"
 | 
			
		||||
                class="core-course-shortname">
 | 
			
		||||
                <core-format-text [text]="course.shortname" contextLevel="course" [contextInstanceId]="course.id">
 | 
			
		||||
                </core-format-text>
 | 
			
		||||
            </span>
 | 
			
		||||
        </p>
 | 
			
		||||
        <p *ngIf="isEnrolled && course.progress! >= 0 && course.completionusertracked !== false">
 | 
			
		||||
            <core-progress-bar [progress]="course.progress" a11yText="core.courses.aria:courseprogress"></core-progress-bar>
 | 
			
		||||
        <ion-row>
 | 
			
		||||
            <ion-col class="ion-align-self-center">
 | 
			
		||||
                <ng-container *ngTemplateOutlet="mainInfo"></ng-container>
 | 
			
		||||
            </ion-col>
 | 
			
		||||
            <ion-col size="auto" class="ion-align-self-center">
 | 
			
		||||
                <ng-container *ngIf="!isEnrolled">
 | 
			
		||||
                    <ion-icon *ngFor="let icon of enrolmentIcons" color="dark" size="small" [name]="icon.icon"
 | 
			
		||||
                        [title]="icon.label | translate" [attr.aria-label]="icon.label | translate">
 | 
			
		||||
                    </ion-icon>
 | 
			
		||||
                </ng-container>
 | 
			
		||||
                <ng-container *ngIf="isEnrolled">
 | 
			
		||||
                    <ng-container *ngTemplateOutlet="download"></ng-container>
 | 
			
		||||
                </ng-container>
 | 
			
		||||
            </ion-col>
 | 
			
		||||
        </ion-row>
 | 
			
		||||
        <p *ngIf="isEnrolled && progress! >= 0 && completionUserTracked !== false">
 | 
			
		||||
            <core-progress-bar [progress]="progress" a11yText="core.courses.aria:courseprogress"></core-progress-bar>
 | 
			
		||||
        </p>
 | 
			
		||||
    </ion-label>
 | 
			
		||||
    <ng-container *ngIf="!isEnrolled">
 | 
			
		||||
        <ion-icon *ngFor="let icon of icons" color="dark" size="small" [name]="icon.icon"
 | 
			
		||||
            [title]="icon.label | translate"
 | 
			
		||||
            [attr.aria-label]="icon.label | translate"
 | 
			
		||||
            slot="end">
 | 
			
		||||
        </ion-icon>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
</ion-item>
 | 
			
		||||
 | 
			
		||||
    <div class="core-button-spinner" *ngIf="isEnrolled && showDownload" slot="end">
 | 
			
		||||
        <core-download-refresh
 | 
			
		||||
            [status]="prefetchCourseData.status"
 | 
			
		||||
            [statusTranslatable]="prefetchCourseData.statusTranslatable"
 | 
			
		||||
            [enabled]="true"
 | 
			
		||||
            canTrustDownload="false"
 | 
			
		||||
            [loading]="prefetchCourseData.loading"
 | 
			
		||||
<ion-card [attr.course-color]="course.color ? null : course.colorNumber" *ngIf="layout == 'card' || layout == 'summarycard'"
 | 
			
		||||
    class="core-course-list-card" [class.item-dimmed]="course.hidden" [attr.aria-label]="course.displayname || course.fullname">
 | 
			
		||||
    <div (click)="openCourse()" class="core-course-thumb" [class.core-course-color-img]="course.courseImage"
 | 
			
		||||
        [style.background-color]="course.color">
 | 
			
		||||
        <img *ngIf="course.courseImage" [src]="course.courseImage" core-external-content alt="" />
 | 
			
		||||
    </div>
 | 
			
		||||
    <ion-item button lines="none" (click)="openCourse()" [attr.aria-label]="course.displayname || course.fullname"
 | 
			
		||||
        class="core-course-header" [class.item-disabled]="course.visible == 0"
 | 
			
		||||
        [class.core-course-only-title]="layout == 'summarycard' || progress < 0 && completionUserTracked === false" detail="false">
 | 
			
		||||
        <ion-label class="ion-text-wrap core-course-title">
 | 
			
		||||
            <ion-row>
 | 
			
		||||
                <ion-col>
 | 
			
		||||
                    <ng-container *ngTemplateOutlet="mainInfo"></ng-container>
 | 
			
		||||
                </ion-col>
 | 
			
		||||
                <ion-col size="auto"
 | 
			
		||||
                    *ngIf="isEnrolled && ((downloadCourseEnabled && !courseOptionMenuEnabled && showDownload) || courseOptionMenuEnabled)">
 | 
			
		||||
                    <ng-container *ngTemplateOutlet="download"></ng-container>
 | 
			
		||||
                </ion-col>
 | 
			
		||||
            </ion-row>
 | 
			
		||||
            <div *ngIf="layout == 'card' && progress >= 0 && completionUserTracked !== false" lines="none" class="core-course-progress">
 | 
			
		||||
                <core-progress-bar [progress]="progress" a11yText="core.courses.aria:courseprogress"></core-progress-bar>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
        </ion-label>
 | 
			
		||||
    </ion-item>
 | 
			
		||||
</ion-card>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<ng-template #download>
 | 
			
		||||
    <div class="core-button-spinner" *ngIf="downloadCourseEnabled && !courseOptionMenuEnabled && showDownload">
 | 
			
		||||
        <core-download-refresh [status]="prefetchCourseData.status" [enabled]="downloadCourseEnabled"
 | 
			
		||||
            [statusTranslatable]="prefetchCourseData.statusTranslatable" canTrustDownload="false" [loading]="prefetchCourseData.loading"
 | 
			
		||||
            (action)="prefetchCourse()"></core-download-refresh>
 | 
			
		||||
    </div>
 | 
			
		||||
</ion-item>
 | 
			
		||||
 | 
			
		||||
    <div class="core-button-spinner" *ngIf="courseOptionMenuEnabled">
 | 
			
		||||
        <!-- Download course spinner. -->
 | 
			
		||||
        <ion-spinner *ngIf="(downloadCourseEnabled && prefetchCourseData.icon == 'spinner') || showSpinner"
 | 
			
		||||
            [attr.aria-label]="'core.loading' | translate"></ion-spinner>
 | 
			
		||||
 | 
			
		||||
        <!-- Downloaded icon. -->
 | 
			
		||||
        <ion-icon *ngIf="downloadCourseEnabled && prefetchCourseData.downloadSucceeded && !showSpinner" class="core-icon-downloaded"
 | 
			
		||||
            name="cloud-done" color="success" role="status" [attr.aria-label]="'core.downloaded' | translate"></ion-icon>
 | 
			
		||||
 | 
			
		||||
        <!-- Options menu. -->
 | 
			
		||||
        <ion-button fill="clear" color="dark" (click)="showCourseOptionsMenu($event)" *ngIf="!showSpinner"
 | 
			
		||||
            [attr.aria-label]="('core.displayoptions' | translate)">
 | 
			
		||||
            <ion-icon name="ellipsis-vertical" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
        </ion-button>
 | 
			
		||||
    </div>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
<ng-template #mainInfo>
 | 
			
		||||
    <p *ngIf="course.categoryname || (course.displayname && course.shortname && course.fullname != course.displayname)"
 | 
			
		||||
        class="core-course-additional-info">
 | 
			
		||||
        <span *ngIf="course.categoryname" class="core-course-category">
 | 
			
		||||
            <span class="sr-only">{{ 'core.courses.aria:coursecategory' | translate }}</span>
 | 
			
		||||
            <core-format-text [text]="course.categoryname"></core-format-text>
 | 
			
		||||
        </span>
 | 
			
		||||
        <span *ngIf="course.categoryname && course.displayname && course.shortname && course.fullname != course.displayname"
 | 
			
		||||
            class="core-course-category"> | </span>
 | 
			
		||||
        <span *ngIf="course.displayname && course.shortname && course.fullname != course.displayname" class="core-course-shortname">
 | 
			
		||||
            <core-format-text [text]="course.shortname" contextLevel="course" [contextInstanceId]="course.id">
 | 
			
		||||
            </core-format-text>
 | 
			
		||||
        </span>
 | 
			
		||||
    </p>
 | 
			
		||||
    <p class="item-heading">
 | 
			
		||||
        <ion-icon name="fas-star" *ngIf="course.isfavourite" [attr.aria-label]="'core.courses.favourite' | translate">
 | 
			
		||||
        </ion-icon>
 | 
			
		||||
        <span class="sr-only" *ngIf="course.isfavourite">{{ 'core.courses.aria:favourite' | translate }}</span>
 | 
			
		||||
        <span class="sr-only">{{ 'core.courses.aria:coursename' | translate }}</span>
 | 
			
		||||
        <core-format-text [text]="course.fullname" contextLevel="course" [contextInstanceId]="course.id">
 | 
			
		||||
        </core-format-text>
 | 
			
		||||
    </p>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,12 @@
 | 
			
		||||
@import "~theme/globals";
 | 
			
		||||
 | 
			
		||||
:host {
 | 
			
		||||
.core-course-list-item {
 | 
			
		||||
    .course-icon {
 | 
			
		||||
        color: white;
 | 
			
		||||
        background: var(--gray-light);
 | 
			
		||||
        padding: 8px;
 | 
			
		||||
        font-size: 24px;
 | 
			
		||||
        border-radius: 50%;
 | 
			
		||||
        margin-inline-end: 16px;
 | 
			
		||||
        -webkit-transition: all 50ms ease-in-out;
 | 
			
		||||
        transition: all 50ms ease-in-out;
 | 
			
		||||
    }
 | 
			
		||||
@ -22,4 +21,177 @@
 | 
			
		||||
        -webkit-transition: all 50ms ease-in-out;
 | 
			
		||||
        transition: all 50ms ease-in-out;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .core-course-thumb {
 | 
			
		||||
        @include margin(12px, 16px, 12px, null);
 | 
			
		||||
        align-self: flex-start;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .core-course-summary {
 | 
			
		||||
        margin-top: 12px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.item-heading ion-icon {
 | 
			
		||||
    margin-right: 4px;
 | 
			
		||||
    color: var(--core-star-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ion-card {
 | 
			
		||||
    --vertical-margin: 12px;
 | 
			
		||||
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    align-self: stretch;
 | 
			
		||||
    height: calc(100% - var(--vertical-margin) - var(--vertical-margin));
 | 
			
		||||
    margin-top: var(--vertical-margin);
 | 
			
		||||
    margin-bottom: var(--vertical-margin);
 | 
			
		||||
 | 
			
		||||
    @for $i from 0 to length($core-course-image-background) {
 | 
			
		||||
        &[course-color="#{$i}"] .core-course-thumb {
 | 
			
		||||
            background: var(--core-course-color-#{$i});
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ion-row {
 | 
			
		||||
        min-height: var(--a11y-min-target-size);
 | 
			
		||||
        ion-col .core-button-spinner {
 | 
			
		||||
            min-width: calc(var(--a11y-min-target-size) + 16px);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .core-course-thumb {
 | 
			
		||||
        padding-top: 40%;
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
        pointer-events: auto;
 | 
			
		||||
        position: relative;
 | 
			
		||||
        background-position: center;
 | 
			
		||||
        background-size: cover;
 | 
			
		||||
        -webkit-transition: all 50ms ease-in-out;
 | 
			
		||||
        transition: all 50ms ease-in-out;
 | 
			
		||||
 | 
			
		||||
        &.core-course-color-img {
 | 
			
		||||
            background: var(--ion-item-background);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        img {
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            top: 0;
 | 
			
		||||
            bottom: 0;
 | 
			
		||||
            right: 0;
 | 
			
		||||
            left: 0;
 | 
			
		||||
            margin: auto;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @if ($core-course-hide-thumb-on-cards) {
 | 
			
		||||
        .core-course-thumb {
 | 
			
		||||
            display: none;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @if ($core-course-thumb-on-cards-background) {
 | 
			
		||||
        .core-course-thumb {
 | 
			
		||||
            background: $core-course-thumb-on-cards-background !important;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .core-course-additional-info {
 | 
			
		||||
        margin-bottom: 8px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .core-course-header {
 | 
			
		||||
        flex-grow: 1;
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
 | 
			
		||||
        --inner-padding-end: 0px;
 | 
			
		||||
 | 
			
		||||
        &::part(native) {
 | 
			
		||||
            flex-grow: 1;
 | 
			
		||||
            align-items: self-start;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &.core-course-only-title {
 | 
			
		||||
            &::part(native) {
 | 
			
		||||
                flex-grow: 1;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .core-course-title {
 | 
			
		||||
            margin: 12px 0;
 | 
			
		||||
            flex-grow: 1;
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            max-width: 100%;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .core-button-spinner {
 | 
			
		||||
            margin: 0;
 | 
			
		||||
        }
 | 
			
		||||
        .core-button-spinner ion-spinner {
 | 
			
		||||
            vertical-align: top; // the better option for most scenarios
 | 
			
		||||
            vertical-align: -webkit-baseline-middle; // the best for those that support it
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .core-button-spinner .core-icon-downloaded {
 | 
			
		||||
            font-size: 28.8px;
 | 
			
		||||
            margin-top: 8px;
 | 
			
		||||
            vertical-align: top;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .item-button[icon-only] {
 | 
			
		||||
            min-width: 50px;
 | 
			
		||||
            width: 50px;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @if ($core-course-hide-progress-on-cards) {
 | 
			
		||||
        .core-course-progress {
 | 
			
		||||
            display: none;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button {
 | 
			
		||||
    z-index: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
:host-context(.core-horizontal-scroll) {
 | 
			
		||||
    @include horizontal_scroll_item(80%, 250px, 300px);
 | 
			
		||||
    
 | 
			
		||||
    ion-card {
 | 
			
		||||
        .core-course-thumb {
 | 
			
		||||
            padding-top: 30%;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ion-item.core-course-header {
 | 
			
		||||
            --padding-start: 4px;
 | 
			
		||||
            
 | 
			
		||||
            .core-course-title {
 | 
			
		||||
                margin: 7px 0;
 | 
			
		||||
 | 
			
		||||
                .item-heading ion-icon {
 | 
			
		||||
                    margin-right: 2px;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .core-button-spinner {
 | 
			
		||||
                min-height: 40px;
 | 
			
		||||
                min-width: 40px;
 | 
			
		||||
 | 
			
		||||
                ion-spinner {
 | 
			
		||||
                    width: 20px;
 | 
			
		||||
                    height: 20px;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            .item-button[icon-only] {
 | 
			
		||||
                min-width: 40px;
 | 
			
		||||
                width: 40px;
 | 
			
		||||
                padding: 8px;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -12,15 +12,19 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { CoreConstants } from '@/core/constants';
 | 
			
		||||
import { Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core';
 | 
			
		||||
import { CoreCourseProvider, CoreCourse } from '@features/course/services/course';
 | 
			
		||||
import { CoreCourseHelper, CorePrefetchStatusInfo } from '@features/course/services/course-helper';
 | 
			
		||||
import { CoreUser } from '@features/user/services/user';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { Translate } from '@singletons';
 | 
			
		||||
import { CoreEventCourseStatusChanged, CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
			
		||||
import { CoreCourseListItem, CoreCourses } from '../../services/courses';
 | 
			
		||||
import { CoreCoursesHelper } from '../../services/courses-helper';
 | 
			
		||||
import { CoreCourseListItem, CoreCourses, CoreCoursesProvider } from '../../services/courses';
 | 
			
		||||
import { CoreCoursesHelper, CoreEnrolledCourseDataWithExtraInfoAndOptions } from '../../services/courses-helper';
 | 
			
		||||
import { CoreCoursesCourseOptionsMenuComponent } from '../course-options-menu/course-options-menu';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This directive is meant to display an item for a list of courses.
 | 
			
		||||
@ -37,10 +41,10 @@ import { CoreCoursesHelper } from '../../services/courses-helper';
 | 
			
		||||
export class CoreCoursesCourseListItemComponent implements OnInit, OnDestroy, OnChanges {
 | 
			
		||||
 | 
			
		||||
    @Input() course!: CoreCourseListItem; // The course to render.
 | 
			
		||||
 | 
			
		||||
    @Input() showDownload = false; // If true, will show download button.
 | 
			
		||||
    @Input() layout: 'listwithenrol'|'summarycard'|'list'|'card' = 'listwithenrol';
 | 
			
		||||
 | 
			
		||||
    icons: CoreCoursesEnrolmentIcons[] = [];
 | 
			
		||||
    enrolmentIcons: CoreCoursesEnrolmentIcons[] = [];
 | 
			
		||||
    isEnrolled = false;
 | 
			
		||||
    prefetchCourseData: CorePrefetchStatusInfo = {
 | 
			
		||||
        icon: '',
 | 
			
		||||
@ -49,8 +53,16 @@ export class CoreCoursesCourseListItemComponent implements OnInit, OnDestroy, On
 | 
			
		||||
        loading: true,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    protected courseStatusObserver?: CoreEventObserver;
 | 
			
		||||
    showSpinner = false;
 | 
			
		||||
    downloadCourseEnabled = false;
 | 
			
		||||
    courseOptionMenuEnabled = false;
 | 
			
		||||
    progress = -1;
 | 
			
		||||
    completionUserTracked: boolean | undefined = false;
 | 
			
		||||
 | 
			
		||||
    protected courseStatus = CoreConstants.NOT_DOWNLOADED;
 | 
			
		||||
    protected isDestroyed = false;
 | 
			
		||||
    protected courseStatusObserver?: CoreEventObserver;
 | 
			
		||||
    protected siteUpdatedObserver?: CoreEventObserver;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
@ -58,48 +70,71 @@ export class CoreCoursesCourseListItemComponent implements OnInit, OnDestroy, On
 | 
			
		||||
    async ngOnInit(): Promise<void> {
 | 
			
		||||
        CoreCoursesHelper.loadCourseColorAndImage(this.course);
 | 
			
		||||
 | 
			
		||||
        this.isEnrolled = this.course.progress !== undefined;
 | 
			
		||||
        // Assume is enroled if mode is not listwithenrol.
 | 
			
		||||
        this.isEnrolled = this.layout != 'listwithenrol' || this.course.progress !== undefined;
 | 
			
		||||
 | 
			
		||||
        if (!this.isEnrolled) {
 | 
			
		||||
            try {
 | 
			
		||||
                const course = await CoreCourses.getUserCourse(this.course.id);
 | 
			
		||||
                this.course.progress = course.progress;
 | 
			
		||||
                this.course.completionusertracked = course.completionusertracked;
 | 
			
		||||
                this.course = Object.assign(this.course, course);
 | 
			
		||||
                this.updateCourseFields();
 | 
			
		||||
 | 
			
		||||
                this.isEnrolled = true;
 | 
			
		||||
 | 
			
		||||
                if (this.showDownload) {
 | 
			
		||||
                    this.initPrefetchCourse();
 | 
			
		||||
                }
 | 
			
		||||
            } catch {
 | 
			
		||||
                this.isEnrolled = false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!this.isEnrolled) {
 | 
			
		||||
            this.icons = [];
 | 
			
		||||
        if (this.isEnrolled) {
 | 
			
		||||
            if (this.showDownload) {
 | 
			
		||||
                this.initPrefetchCourse();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
 | 
			
		||||
 | 
			
		||||
            if (this.downloadCourseEnabled) {
 | 
			
		||||
                this.initPrefetchCourse();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // This field is only available from 3.6 onwards.
 | 
			
		||||
            this.courseOptionMenuEnabled = (this.layout != 'listwithenrol' && this.layout != 'summarycard') &&
 | 
			
		||||
                this.course.isfavourite !== undefined;
 | 
			
		||||
 | 
			
		||||
            // Refresh the enabled flag if site is updated.
 | 
			
		||||
            this.siteUpdatedObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
 | 
			
		||||
                const wasEnabled = this.downloadCourseEnabled;
 | 
			
		||||
 | 
			
		||||
                this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
 | 
			
		||||
 | 
			
		||||
                if (!wasEnabled && this.downloadCourseEnabled) {
 | 
			
		||||
                    // Download course is enabled now, initialize it.
 | 
			
		||||
                    this.initPrefetchCourse();
 | 
			
		||||
                }
 | 
			
		||||
            }, CoreSites.getCurrentSiteId());
 | 
			
		||||
        } else if ('enrollmentmethods' in this.course) {
 | 
			
		||||
            this.enrolmentIcons = [];
 | 
			
		||||
 | 
			
		||||
            this.course.enrollmentmethods.forEach((instance) => {
 | 
			
		||||
                if (instance === 'self') {
 | 
			
		||||
                    this.icons.push({
 | 
			
		||||
                    this.enrolmentIcons.push({
 | 
			
		||||
                        label: 'core.courses.selfenrolment',
 | 
			
		||||
                        icon: 'fas-key',
 | 
			
		||||
                    });
 | 
			
		||||
                } else if (instance === 'guest') {
 | 
			
		||||
                    this.icons.push({
 | 
			
		||||
                    this.enrolmentIcons.push({
 | 
			
		||||
                        label: 'core.courses.allowguests',
 | 
			
		||||
                        icon: 'fas-unlock',
 | 
			
		||||
                    });
 | 
			
		||||
                } else if (instance === 'paypal') {
 | 
			
		||||
                    this.icons.push({
 | 
			
		||||
                    this.enrolmentIcons.push({
 | 
			
		||||
                        label: 'core.courses.paypalaccepted',
 | 
			
		||||
                        icon: 'fab-paypal',
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (this.icons.length == 0) {
 | 
			
		||||
                this.icons.push({
 | 
			
		||||
            if (this.enrolmentIcons.length == 0) {
 | 
			
		||||
                this.enrolmentIcons.push({
 | 
			
		||||
                    label: 'core.courses.notenrollable',
 | 
			
		||||
                    icon: 'fas-lock',
 | 
			
		||||
                });
 | 
			
		||||
@ -114,6 +149,16 @@ export class CoreCoursesCourseListItemComponent implements OnInit, OnDestroy, On
 | 
			
		||||
        if (this.showDownload && this.isEnrolled) {
 | 
			
		||||
            this.initPrefetchCourse();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.updateCourseFields();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Helper function to update course fields.
 | 
			
		||||
     */
 | 
			
		||||
    protected updateCourseFields(): void {
 | 
			
		||||
        this.progress = 'progress' in this.course ? this.course.progress || -1 : -1;
 | 
			
		||||
        this.completionUserTracked = 'completionusertracked' in this.course && this.course.completionusertracked;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -179,6 +224,7 @@ export class CoreCoursesCourseListItemComponent implements OnInit, OnDestroy, On
 | 
			
		||||
    protected updateCourseStatus(status: string): void {
 | 
			
		||||
        const statusData = CoreCourseHelper.getCoursePrefetchStatusInfo(status);
 | 
			
		||||
 | 
			
		||||
        this.courseStatus = status;
 | 
			
		||||
        this.prefetchCourseData.status = statusData.status;
 | 
			
		||||
        this.prefetchCourseData.icon = statusData.icon;
 | 
			
		||||
        this.prefetchCourseData.statusTranslatable = statusData.statusTranslatable;
 | 
			
		||||
@ -188,11 +234,11 @@ export class CoreCoursesCourseListItemComponent implements OnInit, OnDestroy, On
 | 
			
		||||
    /**
 | 
			
		||||
     * Prefetch the course.
 | 
			
		||||
     *
 | 
			
		||||
     * @param e Click event.
 | 
			
		||||
     * @param event Click event.
 | 
			
		||||
     */
 | 
			
		||||
    async prefetchCourse(e?: Event): Promise<void> {
 | 
			
		||||
        e?.preventDefault();
 | 
			
		||||
        e?.stopPropagation();
 | 
			
		||||
    async prefetchCourse(event?: Event): Promise<void> {
 | 
			
		||||
        event?.preventDefault();
 | 
			
		||||
        event?.stopPropagation();
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await CoreCourseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course);
 | 
			
		||||
@ -203,12 +249,149 @@ export class CoreCoursesCourseListItemComponent implements OnInit, OnDestroy, On
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Delete course stored data.
 | 
			
		||||
     */
 | 
			
		||||
    async deleteCourseStoredData(): Promise<void> {
 | 
			
		||||
        try {
 | 
			
		||||
            await CoreDomUtils.showDeleteConfirm('core.course.confirmdeletestoreddata');
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (!CoreDomUtils.isCanceledError(error)) {
 | 
			
		||||
                throw error;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const modal = await CoreDomUtils.showModalLoading();
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await CoreCourseHelper.deleteCourseFiles(this.course.id);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            CoreDomUtils.showErrorModalDefault(error, Translate.instant('core.errordeletefile'));
 | 
			
		||||
        } finally {
 | 
			
		||||
            modal.dismiss();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show the context menu.
 | 
			
		||||
     *
 | 
			
		||||
     * @param event Click Event.
 | 
			
		||||
     */
 | 
			
		||||
    async showCourseOptionsMenu(event: Event): Promise<void> {
 | 
			
		||||
        event.preventDefault();
 | 
			
		||||
        event.stopPropagation();
 | 
			
		||||
 | 
			
		||||
        const popoverData = await CoreDomUtils.openPopover<string>({
 | 
			
		||||
            component: CoreCoursesCourseOptionsMenuComponent,
 | 
			
		||||
            componentProps: {
 | 
			
		||||
                course: this.course,
 | 
			
		||||
                prefetch: this.prefetchCourseData,
 | 
			
		||||
            },
 | 
			
		||||
            event: event,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        switch (popoverData) {
 | 
			
		||||
            case 'download':
 | 
			
		||||
                if (!this.prefetchCourseData.loading) {
 | 
			
		||||
                    this.prefetchCourse(event);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case 'delete':
 | 
			
		||||
                if (this.courseStatus == CoreConstants.DOWNLOADED || this.courseStatus == CoreConstants.OUTDATED) {
 | 
			
		||||
                    this.deleteCourseStoredData();
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case 'hide':
 | 
			
		||||
                this.setCourseHidden(true);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'show':
 | 
			
		||||
                this.setCourseHidden(false);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'favourite':
 | 
			
		||||
                this.setCourseFavourite(true);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'unfavourite':
 | 
			
		||||
                this.setCourseFavourite(false);
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Hide/Unhide the course from the course list.
 | 
			
		||||
     *
 | 
			
		||||
     * @param hide True to hide and false to show.
 | 
			
		||||
     */
 | 
			
		||||
    protected async setCourseHidden(hide: boolean): Promise<void> {
 | 
			
		||||
        this.showSpinner = true;
 | 
			
		||||
 | 
			
		||||
        // We should use null to unset the preference.
 | 
			
		||||
        try {
 | 
			
		||||
            await CoreUser.updateUserPreference(
 | 
			
		||||
                'block_myoverview_hidden_course_' + this.course.id,
 | 
			
		||||
                hide ? '1' : undefined,
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            this.course.hidden = hide;
 | 
			
		||||
 | 
			
		||||
            (<CoreEnrolledCourseDataWithExtraInfoAndOptions> this.course).hidden = hide;
 | 
			
		||||
            CoreEvents.trigger(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, {
 | 
			
		||||
                courseId: this.course.id,
 | 
			
		||||
                course: this.course,
 | 
			
		||||
                action: CoreCoursesProvider.ACTION_STATE_CHANGED,
 | 
			
		||||
                state: CoreCoursesProvider.STATE_HIDDEN,
 | 
			
		||||
                value: hide,
 | 
			
		||||
            }, CoreSites.getCurrentSiteId());
 | 
			
		||||
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (!this.isDestroyed) {
 | 
			
		||||
                CoreDomUtils.showErrorModalDefault(error, 'Error changing course visibility.');
 | 
			
		||||
            }
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.showSpinner = false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Favourite/Unfavourite the course from the course list.
 | 
			
		||||
     *
 | 
			
		||||
     * @param favourite True to favourite and false to unfavourite.
 | 
			
		||||
     */
 | 
			
		||||
    protected async setCourseFavourite(favourite: boolean): Promise<void> {
 | 
			
		||||
        this.showSpinner = true;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            await CoreCourses.setFavouriteCourse(this.course.id, favourite);
 | 
			
		||||
 | 
			
		||||
            this.course.isfavourite = favourite;
 | 
			
		||||
            CoreEvents.trigger(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, {
 | 
			
		||||
                courseId: this.course.id,
 | 
			
		||||
                course: this.course,
 | 
			
		||||
                action: CoreCoursesProvider.ACTION_STATE_CHANGED,
 | 
			
		||||
                state: CoreCoursesProvider.STATE_FAVOURITE,
 | 
			
		||||
                value: favourite,
 | 
			
		||||
            }, CoreSites.getCurrentSiteId());
 | 
			
		||||
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (!this.isDestroyed) {
 | 
			
		||||
                CoreDomUtils.showErrorModalDefault(error, 'Error changing course favourite attribute.');
 | 
			
		||||
            }
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.showSpinner = false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    ngOnDestroy(): void {
 | 
			
		||||
        this.isDestroyed = true;
 | 
			
		||||
        this.courseStatusObserver?.off();
 | 
			
		||||
        this.siteUpdatedObserver?.off();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -151,9 +151,3 @@
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
:host-context(body.version-3-1) {
 | 
			
		||||
    .core-course-thumb{
 | 
			
		||||
        display: none;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -35,6 +35,8 @@ import { CoreUser } from '@features/user/services/user';
 | 
			
		||||
 *
 | 
			
		||||
 * <core-courses-course-progress [course]="course">
 | 
			
		||||
 * </core-courses-course-progress>
 | 
			
		||||
 *
 | 
			
		||||
 * @deprecated since 4.0 Use core-courses-course-list-item instead.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'core-courses-course-progress',
 | 
			
		||||
@ -175,7 +177,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy, On
 | 
			
		||||
        try {
 | 
			
		||||
            await CoreDomUtils.showDeleteConfirm('core.course.confirmdeletestoreddata');
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            if (CoreDomUtils.isCanceledError(error)) {
 | 
			
		||||
            if (!CoreDomUtils.isCanceledError(error)) {
 | 
			
		||||
                throw error;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -221,7 +223,6 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy, On
 | 
			
		||||
            component: CoreCoursesCourseOptionsMenuComponent,
 | 
			
		||||
            componentProps: {
 | 
			
		||||
                course: this.course,
 | 
			
		||||
                courseStatus: this.courseStatus,
 | 
			
		||||
                prefetch: this.prefetchCourseData,
 | 
			
		||||
            },
 | 
			
		||||
            event: e,
 | 
			
		||||
@ -234,7 +235,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy, On
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case 'delete':
 | 
			
		||||
                if (this.courseStatus == 'downloaded' || this.courseStatus == 'outdated') {
 | 
			
		||||
                if (this.courseStatus == CoreConstants.DOWNLOADED || this.courseStatus == CoreConstants.OUTDATED) {
 | 
			
		||||
                    this.deleteCourse();
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
@ -60,7 +60,7 @@ const mainMenuHomeSiblingRoutes: Routes = [
 | 
			
		||||
const mainMenuTabRoutes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: CoreCoursesMyCoursesMainMenuHandlerService.PAGE_NAME,
 | 
			
		||||
        loadChildren: () => import('./pages/list/list.module').then(m => m.CoreCoursesListPageModule),
 | 
			
		||||
        loadChildren: () => import('./pages/my/my.module').then(m => m.CoreCoursesMyCoursesPageModule),
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -32,6 +32,7 @@
 | 
			
		||||
    "password": "Enrolment key",
 | 
			
		||||
    "paymentrequired": "This course requires a payment for entry.",
 | 
			
		||||
    "paypalaccepted": "PayPal payments accepted",
 | 
			
		||||
    "refreshcourses": "Refresh courses",
 | 
			
		||||
    "reload": "Reload",
 | 
			
		||||
    "removefromfavourites": "Unstar this course",
 | 
			
		||||
    "search": "Search",
 | 
			
		||||
 | 
			
		||||
@ -10,11 +10,10 @@
 | 
			
		||||
        <ion-buttons slot="end">
 | 
			
		||||
            <core-context-menu>
 | 
			
		||||
                <core-context-menu-item *ngIf="downloadCourseEnabled || downloadCoursesEnabled" [priority]="1000"
 | 
			
		||||
                    [content]="'core.settings.showdownloadoptions' | translate" (action)="toggleDownload()"
 | 
			
		||||
                    iconAction="toggle" [(toggle)]="downloadEnabled"></core-context-menu-item>
 | 
			
		||||
                <core-context-menu-item [priority]="900"
 | 
			
		||||
                    [content]="'core.courses.showonlyenrolled' | translate" (action)="filterEnrolled()"
 | 
			
		||||
                    iconAction="toggle" [(toggle)]="showOnlyEnrolled"></core-context-menu-item>   
 | 
			
		||||
                    [content]="'core.settings.showdownloadoptions' | translate" (action)="toggleDownload()" iconAction="toggle"
 | 
			
		||||
                    [(toggle)]="downloadEnabled"></core-context-menu-item>
 | 
			
		||||
                <core-context-menu-item [priority]="900" [content]="'core.courses.showonlyenrolled' | translate" (action)="filterEnrolled()"
 | 
			
		||||
                    iconAction="toggle" [(toggle)]="showOnlyEnrolled"></core-context-menu-item>
 | 
			
		||||
            </core-context-menu>
 | 
			
		||||
        </ion-buttons>
 | 
			
		||||
    </ion-toolbar>
 | 
			
		||||
@ -28,12 +27,12 @@
 | 
			
		||||
            <ion-icon name="fas-folder" slot="start" [attr.aria-label]="'core.category' | translate"></ion-icon>
 | 
			
		||||
            <ion-label>
 | 
			
		||||
                <p class="item-heading">
 | 
			
		||||
                    <core-format-text [text]="currentCategory.name" contextLevel="coursecat"
 | 
			
		||||
                    [contextInstanceId]="currentCategory.id"></core-format-text>
 | 
			
		||||
                    <core-format-text [text]="currentCategory.name" contextLevel="coursecat" [contextInstanceId]="currentCategory.id">
 | 
			
		||||
                    </core-format-text>
 | 
			
		||||
                </p>
 | 
			
		||||
                <p *ngIf="currentCategory.description">
 | 
			
		||||
                    <core-format-text [text]="currentCategory.description" maxHeight="60" contextLevel="coursecat"
 | 
			
		||||
                    [contextInstanceId]="currentCategory.id"></core-format-text>
 | 
			
		||||
                        [contextInstanceId]="currentCategory.id"></core-format-text>
 | 
			
		||||
                </p>
 | 
			
		||||
            </ion-label>
 | 
			
		||||
        </ion-item>
 | 
			
		||||
@ -45,8 +44,7 @@
 | 
			
		||||
                </ion-label>
 | 
			
		||||
            </ion-item-divider>
 | 
			
		||||
            <section *ngFor="let category of categories">
 | 
			
		||||
                <ion-item button class="ion-text-wrap" (click)="openCategory(category.id)" [attr.aria-label]="category.name"
 | 
			
		||||
                    detail="true">
 | 
			
		||||
                <ion-item button class="ion-text-wrap" (click)="openCategory(category.id)" [attr.aria-label]="category.name" detail="true">
 | 
			
		||||
                    <ion-icon name="fas-folder" slot="start" [attr.aria-label]="'core.category' | translate"></ion-icon>
 | 
			
		||||
                    <ion-label>
 | 
			
		||||
                        <h2>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										36
									
								
								src/core/features/courses/pages/my/my.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/core/features/courses/pages/my/my.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
			
		||||
<ion-header>
 | 
			
		||||
    <ion-toolbar>
 | 
			
		||||
        <ion-buttons slot="start">
 | 
			
		||||
            <ion-back-button [text]="'core.back' | translate"></ion-back-button>
 | 
			
		||||
        </ion-buttons>
 | 
			
		||||
        <h1>{{ 'core.courses.mycourses' | translate }}</h1>
 | 
			
		||||
        <ion-buttons slot="end">
 | 
			
		||||
            <ion-button *ngIf="searchEnabled" (click)="openSearch()" [attr.aria-label]="'core.courses.searchcourses' | translate">
 | 
			
		||||
                <ion-icon name="fas-search" slot="icon-only" aria-hidden="true"></ion-icon>
 | 
			
		||||
            </ion-button>
 | 
			
		||||
            <core-context-menu>
 | 
			
		||||
                <core-context-menu-item *ngIf="downloadCoursesEnabled && myOverviewBlock && myOverviewBlock.filteredCourses.length > 1"
 | 
			
		||||
                    [priority]="1000" [content]="myOverviewBlock?.prefetchCoursesData.statusTranslatable | translate"
 | 
			
		||||
                    (action)="myOverviewBlock?.prefetchCourses()"
 | 
			
		||||
                    [iconAction]="myOverviewBlock?.prefetchCoursesData.loading ? 'spinner' : myOverviewBlock?.prefetchCoursesData.icon"
 | 
			
		||||
                    [badge]="myOverviewBlock?.prefetchCoursesData.badge"
 | 
			
		||||
                    [badgeA11yText]="myOverviewBlock?.prefetchCoursesData.badgeA11yText">
 | 
			
		||||
                </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-user-menu-button></core-user-menu-button>
 | 
			
		||||
        </ion-buttons>
 | 
			
		||||
    </ion-toolbar>
 | 
			
		||||
</ion-header>
 | 
			
		||||
<ion-content>
 | 
			
		||||
    <ion-refresher slot="fixed" (ionRefresh)="refresh($event.target)">
 | 
			
		||||
        <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
			
		||||
    </ion-refresher>
 | 
			
		||||
    <ion-list>
 | 
			
		||||
        <!-- My courses blocks. -->
 | 
			
		||||
        <core-block [block]="{name: 'myoverview', visible: true}" contextLevel="user" [instanceId]="userId"
 | 
			
		||||
            [extraData]="{'downloadEnabled': true}">
 | 
			
		||||
        </core-block>
 | 
			
		||||
    </ion-list>
 | 
			
		||||
</ion-content>
 | 
			
		||||
							
								
								
									
										63
									
								
								src/core/features/courses/pages/my/my.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/core/features/courses/pages/my/my.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,63 @@
 | 
			
		||||
// (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 { Injector, NgModule } from '@angular/core';
 | 
			
		||||
import { RouterModule, ROUTES, Routes } from '@angular/router';
 | 
			
		||||
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { CoreBlockComponentsModule } from '@features/block/components/components.module';
 | 
			
		||||
 | 
			
		||||
import { CoreCoursesMyCoursesPage } from './my';
 | 
			
		||||
import { CoreMainMenuComponentsModule } from '@features/mainmenu/components/components.module';
 | 
			
		||||
import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module';
 | 
			
		||||
 | 
			
		||||
function buildRoutes(injector: Injector): Routes {
 | 
			
		||||
    return [
 | 
			
		||||
        {
 | 
			
		||||
            path: '',
 | 
			
		||||
            component: CoreCoursesMyCoursesPage,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            path: 'list',
 | 
			
		||||
            loadChildren: () =>
 | 
			
		||||
                import('../list/list.module')
 | 
			
		||||
                    .then(m => m.CoreCoursesListPageModule),
 | 
			
		||||
        },
 | 
			
		||||
        ...buildTabMainRoutes(injector, {
 | 
			
		||||
            redirectTo: '',
 | 
			
		||||
            pathMatch: 'full',
 | 
			
		||||
        }),
 | 
			
		||||
    ];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
        CoreBlockComponentsModule,
 | 
			
		||||
        CoreMainMenuComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    providers: [
 | 
			
		||||
        {
 | 
			
		||||
            provide: ROUTES,
 | 
			
		||||
            multi: true,
 | 
			
		||||
            deps: [Injector],
 | 
			
		||||
            useFactory: buildRoutes,
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    declarations: [
 | 
			
		||||
        CoreCoursesMyCoursesPage,
 | 
			
		||||
    ],
 | 
			
		||||
    exports: [RouterModule],
 | 
			
		||||
})
 | 
			
		||||
export class CoreCoursesMyCoursesPageModule { }
 | 
			
		||||
							
								
								
									
										3
									
								
								src/core/features/courses/pages/my/my.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/core/features/courses/pages/my/my.scss
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
:host ::ng-deep ion-item-divider {
 | 
			
		||||
    display: none !important;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										112
									
								
								src/core/features/courses/pages/my/my.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/core/features/courses/pages/my/my.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,112 @@
 | 
			
		||||
// (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 { AddonBlockMyOverviewComponent } from '@addons/block/myoverview/components/myoverview/myoverview';
 | 
			
		||||
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
 | 
			
		||||
import { CoreBlockComponent } from '@features/block/components/block/block';
 | 
			
		||||
import { IonRefresher } from '@ionic/angular';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
			
		||||
import { CoreCourses } from '../../services/courses';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Page that shows a my courses.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'page-core-courses-my',
 | 
			
		||||
    templateUrl: 'my.html',
 | 
			
		||||
    styleUrls: ['my.scss'],
 | 
			
		||||
})
 | 
			
		||||
export class CoreCoursesMyCoursesPage implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
    @ViewChild(CoreBlockComponent) block!: CoreBlockComponent;
 | 
			
		||||
 | 
			
		||||
    searchEnabled = false;
 | 
			
		||||
    downloadCoursesEnabled = false;
 | 
			
		||||
    userId: number;
 | 
			
		||||
    myOverviewBlock?: AddonBlockMyOverviewComponent;
 | 
			
		||||
 | 
			
		||||
    protected updateSiteObserver: CoreEventObserver;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        // Refresh the enabled flags if site is updated.
 | 
			
		||||
        this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
 | 
			
		||||
            this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite();
 | 
			
		||||
            this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
 | 
			
		||||
        }, CoreSites.getCurrentSiteId());
 | 
			
		||||
 | 
			
		||||
        this.userId = CoreSites.getCurrentSiteUserId();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    ngOnInit(): void {
 | 
			
		||||
        this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite();
 | 
			
		||||
        this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
 | 
			
		||||
 | 
			
		||||
        this.loadBlock();
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load my overview block instance.
 | 
			
		||||
     */
 | 
			
		||||
    protected loadBlock(): void {
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            if (!this.block) {
 | 
			
		||||
                return this.loadBlock();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.myOverviewBlock = this.block?.dynamicComponent?.instance as AddonBlockMyOverviewComponent;
 | 
			
		||||
        }, 500);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Open page to manage courses storage.
 | 
			
		||||
     */
 | 
			
		||||
    manageCoursesStorage(): void {
 | 
			
		||||
        CoreNavigator.navigateToSitePath('/storage');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Go to search courses.
 | 
			
		||||
     */
 | 
			
		||||
    async openSearch(): Promise<void> {
 | 
			
		||||
        CoreNavigator.navigateToSitePath('/list', { params : { mode: 'search' } });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Refresh the data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresher Refresher.
 | 
			
		||||
     */
 | 
			
		||||
    async refresh(refresher?: IonRefresher): Promise<void> {
 | 
			
		||||
        if (this.block) {
 | 
			
		||||
            await CoreUtils.ignoreErrors(this.block.doRefresh());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        refresher?.complete();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    ngOnDestroy(): void {
 | 
			
		||||
        this.updateSiteObserver?.off();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -32,6 +32,8 @@ import { AddonCourseCompletion } from '@/addons/coursecompletion/services/course
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class CoreCoursesHelperProvider {
 | 
			
		||||
 | 
			
		||||
    protected courseSiteColors: Record<string, (string | undefined)[]> = {};
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the courses to display the course picker popover. If a courseId is specified, it will also return its categoryId.
 | 
			
		||||
     *
 | 
			
		||||
@ -72,11 +74,10 @@ export class CoreCoursesHelperProvider {
 | 
			
		||||
     * @param courseByField Course returned by core_course_get_courses_by_field.
 | 
			
		||||
     * @param addCategoryName Whether add category name or not.
 | 
			
		||||
     */
 | 
			
		||||
    loadCourseExtraInfo(
 | 
			
		||||
    protected loadCourseExtraInfo(
 | 
			
		||||
        course: CoreEnrolledCourseDataWithExtraInfo,
 | 
			
		||||
        courseByField: CoreCourseSearchedData,
 | 
			
		||||
        addCategoryName: boolean = false,
 | 
			
		||||
        colors?: (string | undefined)[],
 | 
			
		||||
    ): void {
 | 
			
		||||
        if (courseByField) {
 | 
			
		||||
            course.displayname = courseByField.displayname;
 | 
			
		||||
@ -85,18 +86,8 @@ export class CoreCoursesHelperProvider {
 | 
			
		||||
        } else {
 | 
			
		||||
            delete course.displayname;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.loadCourseColorAndImage(course, colors);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Given a list of courses returned by core_enrol_get_users_courses, load some extra data using the WebService
 | 
			
		||||
     * core_course_get_courses_by_field if available.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courses List of courses.
 | 
			
		||||
     * @param loadCategoryNames Whether load category names or not.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    /**
 | 
			
		||||
     * Loads the color of courses or the thumb image.
 | 
			
		||||
     *
 | 
			
		||||
@ -107,11 +98,8 @@ export class CoreCoursesHelperProvider {
 | 
			
		||||
        if (!courses.length) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        const colors = await this.loadCourseSiteColors();
 | 
			
		||||
 | 
			
		||||
        courses.forEach((course) => {
 | 
			
		||||
            this.loadCourseColorAndImage(course, colors);
 | 
			
		||||
        });
 | 
			
		||||
        await Promise.all(courses.map((course) => this.loadCourseColorAndImage(course)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -131,32 +119,19 @@ export class CoreCoursesHelperProvider {
 | 
			
		||||
        let coursesInfo = {};
 | 
			
		||||
        let courseInfoAvailable = false;
 | 
			
		||||
 | 
			
		||||
        const promises: Promise<void>[] = [];
 | 
			
		||||
        let colors: (string | undefined)[] = [];
 | 
			
		||||
 | 
			
		||||
        promises.push(this.loadCourseSiteColors().then((loadedColors) => {
 | 
			
		||||
            colors = loadedColors;
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        if (loadCategoryNames || (courses[0].overviewfiles === undefined && courses[0].displayname === undefined)) {
 | 
			
		||||
            const courseIds = courses.map((course) => course.id).join(',');
 | 
			
		||||
 | 
			
		||||
            courseInfoAvailable = true;
 | 
			
		||||
 | 
			
		||||
            // Get the extra data for the courses.
 | 
			
		||||
            promises.push(CoreCourses.getCoursesByField('ids', courseIds).then((coursesInfos) => {
 | 
			
		||||
                coursesInfo = CoreUtils.arrayToObject(coursesInfos, 'id');
 | 
			
		||||
            const coursesInfosArray = await CoreCourses.getCoursesByField('ids', courseIds);
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }));
 | 
			
		||||
            coursesInfo = CoreUtils.arrayToObject(coursesInfosArray, 'id');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await Promise.all(promises);
 | 
			
		||||
 | 
			
		||||
        courses.forEach((course) => {
 | 
			
		||||
            this.loadCourseExtraInfo(course, courseInfoAvailable ? coursesInfo[course.id] : course, loadCategoryNames, colors);
 | 
			
		||||
            this.loadCourseExtraInfo(course, courseInfoAvailable ? coursesInfo[course.id] : course, loadCategoryNames);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -166,18 +141,30 @@ export class CoreCoursesHelperProvider {
 | 
			
		||||
     * @return course colors RGB.
 | 
			
		||||
     */
 | 
			
		||||
    protected async loadCourseSiteColors(): Promise<(string | undefined)[]> {
 | 
			
		||||
        const site = CoreSites.getCurrentSite();
 | 
			
		||||
        const site = CoreSites.getRequiredCurrentSite();
 | 
			
		||||
        const siteId = site.getId();
 | 
			
		||||
 | 
			
		||||
        if (this.courseSiteColors[siteId] !== undefined) {
 | 
			
		||||
            return this.courseSiteColors[siteId];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!site.isVersionGreaterEqualThan('3.8')) {
 | 
			
		||||
            this.courseSiteColors[siteId] = [];
 | 
			
		||||
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const colors: (string | undefined)[] = [];
 | 
			
		||||
 | 
			
		||||
        if (site?.isVersionGreaterEqualThan('3.8')) {
 | 
			
		||||
            try {
 | 
			
		||||
                const configs = await site.getConfig();
 | 
			
		||||
                for (let x = 0; x < 10; x++) {
 | 
			
		||||
                    colors[x] = configs['core_admin_coursecolor' + (x + 1)] || undefined;
 | 
			
		||||
                }
 | 
			
		||||
            } catch {
 | 
			
		||||
                // Ignore errors.
 | 
			
		||||
        try {
 | 
			
		||||
            const configs = await site.getConfig();
 | 
			
		||||
            for (let x = 0; x < 10; x++) {
 | 
			
		||||
                colors[x] = configs['core_admin_coursecolor' + (x + 1)] || undefined;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.courseSiteColors[siteId] = colors;
 | 
			
		||||
        } catch {
 | 
			
		||||
            // Ignore errors.
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return colors;
 | 
			
		||||
@ -187,19 +174,18 @@ export class CoreCoursesHelperProvider {
 | 
			
		||||
     * Loads the color of the course or the thumb image.
 | 
			
		||||
     *
 | 
			
		||||
     * @param course Course data.
 | 
			
		||||
     * @param colors Colors loaded.
 | 
			
		||||
     */
 | 
			
		||||
    async loadCourseColorAndImage(course: CoreCourseWithImageAndColor, colors?: (string | undefined)[]): Promise<void> {
 | 
			
		||||
        if (!colors) {
 | 
			
		||||
            colors = await this.loadCourseSiteColors();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    async loadCourseColorAndImage(course: CoreCourseWithImageAndColor): Promise<void> {
 | 
			
		||||
        if (course.overviewfiles && course.overviewfiles[0]) {
 | 
			
		||||
            course.courseImage = course.overviewfiles[0].fileurl;
 | 
			
		||||
        } else {
 | 
			
		||||
            course.colorNumber = course.id % 10;
 | 
			
		||||
            course.color = colors.length ? colors[course.colorNumber] : undefined;
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const colors = await this.loadCourseSiteColors();
 | 
			
		||||
 | 
			
		||||
        course.colorNumber = course.id % 10;
 | 
			
		||||
        course.color = colors.length ? colors[course.colorNumber] : undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,7 @@ import { makeSingleton } from '@singletons';
 | 
			
		||||
import { CoreStatusWithWarningsWSResponse, CoreWarningsWSResponse, CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
 | 
			
		||||
import { CoreEvents } from '@singletons/events';
 | 
			
		||||
import { CoreWSError } from '@classes/errors/wserror';
 | 
			
		||||
import { CoreCourseWithImageAndColor } from './courses-helper';
 | 
			
		||||
import { CoreCourseAnyCourseDataWithExtraInfoAndOptions, CoreCourseWithImageAndColor } from './courses-helper';
 | 
			
		||||
 | 
			
		||||
const ROOT_CACHE_KEY = 'mmCourses:';
 | 
			
		||||
 | 
			
		||||
@ -1384,7 +1384,10 @@ export type CoreCourseSearchedData = CoreCourseBasicSearchedData & {
 | 
			
		||||
/**
 | 
			
		||||
 * Course to render as list item.
 | 
			
		||||
 */
 | 
			
		||||
export type CoreCourseListItem = CoreCourseSearchedData & CoreCourseWithImageAndColor & {
 | 
			
		||||
export type CoreCourseListItem = ((CoreCourseSearchedData & CoreCourseWithImageAndColor) |
 | 
			
		||||
CoreCourseAnyCourseDataWithExtraInfoAndOptions) & {
 | 
			
		||||
    isfavourite?: boolean; // If the user marked this course a favourite.
 | 
			
		||||
    hidden?: boolean; // If the user hide the course from the dashboard.
 | 
			
		||||
    completionusertracked?: boolean; // If the user is completion tracked.
 | 
			
		||||
    progress?: number | null; // Progress percentage.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -26,7 +26,7 @@ import { CoreDashboardHomeHandler } from './dashboard-home';
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class CoreCoursesMyCoursesMainMenuHandlerService implements CoreMainMenuHandler {
 | 
			
		||||
 | 
			
		||||
    static readonly PAGE_NAME = 'courses';
 | 
			
		||||
    static readonly PAGE_NAME = 'my';
 | 
			
		||||
 | 
			
		||||
    name = 'CoreCoursesMyCourses';
 | 
			
		||||
    priority = 900;
 | 
			
		||||
@ -35,13 +35,20 @@ export class CoreCoursesMyCoursesMainMenuHandlerService implements CoreMainMenuH
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async isEnabled(): Promise<boolean> {
 | 
			
		||||
        const siteId = CoreSites.getCurrentSiteId();
 | 
			
		||||
        const site = CoreSites.getRequiredCurrentSite();
 | 
			
		||||
 | 
			
		||||
        const siteId = site.getId();
 | 
			
		||||
        const disabled = await CoreCourses.isMyCoursesDisabled(siteId);
 | 
			
		||||
 | 
			
		||||
        if (disabled) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (site.isVersionGreaterEqualThan('4.0')) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Dashboard cannot be disabled on 3.5 or 3.6 so it will never show this tab.
 | 
			
		||||
        const dashboardEnabled = await CoreDashboardHomeHandler.isEnabledForSite(siteId);
 | 
			
		||||
        const siteHomeEnabled = await CoreSiteHomeHomeHandler.isEnabledForSite(siteId);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@
 | 
			
		||||
    "all": "All",
 | 
			
		||||
    "allgroups": "All groups",
 | 
			
		||||
    "allparticipants": "All participants",
 | 
			
		||||
    "applyfilters": "Apply filters",
 | 
			
		||||
    "answer": "Answer",
 | 
			
		||||
    "answered": "Answered",
 | 
			
		||||
    "areyousure": "Are you sure?",
 | 
			
		||||
 | 
			
		||||
@ -1163,3 +1163,7 @@ iframe {
 | 
			
		||||
        display: none !important;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ion-grid.core-no-grid > ion-row {
 | 
			
		||||
    display: block;
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user