forked from EVOgeek/Vmeda.Online
		
	
						commit
						6504f29124
					
				@ -40,10 +40,10 @@
 | 
				
			|||||||
  "addon.block_learningplans.pluginname": "block_lp",
 | 
					  "addon.block_learningplans.pluginname": "block_lp",
 | 
				
			||||||
  "addon.block_myoverview.all": "block_myoverview",
 | 
					  "addon.block_myoverview.all": "block_myoverview",
 | 
				
			||||||
  "addon.block_myoverview.allincludinghidden": "block_myoverview",
 | 
					  "addon.block_myoverview.allincludinghidden": "block_myoverview",
 | 
				
			||||||
  "addon.block_myoverview.aria:favourites": "block_myoverview",
 | 
					 | 
				
			||||||
  "addon.block_myoverview.aria:hiddencourses": "block_myoverview",
 | 
					 | 
				
			||||||
  "addon.block_myoverview.card": "block_myoverview",
 | 
					  "addon.block_myoverview.card": "block_myoverview",
 | 
				
			||||||
 | 
					  "addon.block_myoverview.favourites": "block_myoverview",
 | 
				
			||||||
  "addon.block_myoverview.future": "block_myoverview",
 | 
					  "addon.block_myoverview.future": "block_myoverview",
 | 
				
			||||||
 | 
					  "addon.block_myoverview.hiddencourses": "block_myoverview",
 | 
				
			||||||
  "addon.block_myoverview.inprogress": "block_myoverview",
 | 
					  "addon.block_myoverview.inprogress": "block_myoverview",
 | 
				
			||||||
  "addon.block_myoverview.lastaccessed": "block_myoverview",
 | 
					  "addon.block_myoverview.lastaccessed": "block_myoverview",
 | 
				
			||||||
  "addon.block_myoverview.list": "block_myoverview",
 | 
					  "addon.block_myoverview.list": "block_myoverview",
 | 
				
			||||||
@ -1451,7 +1451,6 @@
 | 
				
			|||||||
  "core.allparticipants": "moodle",
 | 
					  "core.allparticipants": "moodle",
 | 
				
			||||||
  "core.answer": "moodle",
 | 
					  "core.answer": "moodle",
 | 
				
			||||||
  "core.answered": "quiz",
 | 
					  "core.answered": "quiz",
 | 
				
			||||||
  "core.applyfilters": "user",
 | 
					 | 
				
			||||||
  "core.areyousure": "moodle",
 | 
					  "core.areyousure": "moodle",
 | 
				
			||||||
  "core.back": "moodle",
 | 
					  "core.back": "moodle",
 | 
				
			||||||
  "core.block.blocks": "moodle",
 | 
					  "core.block.blocks": "moodle",
 | 
				
			||||||
 | 
				
			|||||||
@ -17,12 +17,10 @@ import { NgModule } from '@angular/core';
 | 
				
			|||||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
					import { CoreSharedModule } from '@/core/shared.module';
 | 
				
			||||||
import { CoreCoursesComponentsModule } from '@features/courses/components/components.module';
 | 
					import { CoreCoursesComponentsModule } from '@features/courses/components/components.module';
 | 
				
			||||||
import { AddonBlockMyOverviewComponent } from './myoverview/myoverview';
 | 
					import { AddonBlockMyOverviewComponent } from './myoverview/myoverview';
 | 
				
			||||||
import { AddonBlockMyOverviewFilterOptionsComponent } from './filteroptions/filteroptions';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@NgModule({
 | 
					@NgModule({
 | 
				
			||||||
    declarations: [
 | 
					    declarations: [
 | 
				
			||||||
        AddonBlockMyOverviewComponent,
 | 
					        AddonBlockMyOverviewComponent,
 | 
				
			||||||
        AddonBlockMyOverviewFilterOptionsComponent,
 | 
					 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    imports: [
 | 
					    imports: [
 | 
				
			||||||
        CoreSharedModule,
 | 
					        CoreSharedModule,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,58 +0,0 @@
 | 
				
			|||||||
<ion-header>
 | 
					 | 
				
			||||||
    <ion-toolbar>
 | 
					 | 
				
			||||||
        <ion-title>
 | 
					 | 
				
			||||||
            <h1>{{ 'core.courses.filtermycourses' | translate }}</h1>
 | 
					 | 
				
			||||||
        </ion-title>
 | 
					 | 
				
			||||||
        <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.all' | 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.aria:favourites' | 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>
 | 
					 | 
				
			||||||
@ -1,44 +0,0 @@
 | 
				
			|||||||
// (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();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -22,16 +22,55 @@
 | 
				
			|||||||
</ion-item-divider>
 | 
					</ion-item-divider>
 | 
				
			||||||
<core-loading [hideUntil]="loaded">
 | 
					<core-loading [hideUntil]="loaded">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <ion-row class="ion-hide-md-up addon-block-myoverview-filter" *ngIf="hasCourses">
 | 
				
			||||||
 | 
					        <ion-col>
 | 
				
			||||||
 | 
					            <!-- Filter courses. -->
 | 
				
			||||||
 | 
					            <ion-searchbar [(ngModel)]="textFilter" (ionInput)="filterTextChanged($event.target)"
 | 
				
			||||||
 | 
					                (ionCancel)="filterTextChanged($event.target)" [placeholder]="'core.courses.filtermycourses' | translate">
 | 
				
			||||||
 | 
					            </ion-searchbar>
 | 
				
			||||||
 | 
					        </ion-col>
 | 
				
			||||||
 | 
					    </ion-row>
 | 
				
			||||||
    <ion-row class="ion-justify-content-between ion-align-items-center addon-block-myoverview-filter" *ngIf="hasCourses">
 | 
					    <ion-row class="ion-justify-content-between ion-align-items-center addon-block-myoverview-filter" *ngIf="hasCourses">
 | 
				
			||||||
        <ion-col size="auto" *ngIf="filters.enabled">
 | 
					        <ion-col size="auto" *ngIf="filters.enabled">
 | 
				
			||||||
            <core-combobox interface="modal" [label]="'core.courses.filtermycourses' | translate" (onChange)="filterOptionsChanged($event)"
 | 
					            <core-combobox [label]="'core.courses.filtermycourses' | translate" [selection]="filters.timeFilterSelected"
 | 
				
			||||||
                icon="fas-filter" [badge]="filters.count" [modalOptions]="filterModalOptions">
 | 
					                (onChange)="filterOptionsChanged($event)">
 | 
				
			||||||
 | 
					                <ion-select-option class="ion-text-wrap core-select-option-border-bottom" value="allincludinghidden"
 | 
				
			||||||
 | 
					                    *ngIf="filters.show.allincludinghidden">
 | 
				
			||||||
 | 
					                    {{ 'addon.block_myoverview.allincludinghidden' | translate }}
 | 
				
			||||||
 | 
					                </ion-select-option>
 | 
				
			||||||
 | 
					                <ion-select-option class="ion-text-wrap core-select-option-border-bottom" value="all" *ngIf="filters.show.all">
 | 
				
			||||||
 | 
					                    {{ 'addon.block_myoverview.all' | translate }}
 | 
				
			||||||
 | 
					                </ion-select-option>
 | 
				
			||||||
 | 
					                <ion-select-option class="ion-text-wrap"
 | 
				
			||||||
 | 
					                    [class.core-select-option-border-bottom]="!filters.show.past && !filters.show.future" value="inprogress"
 | 
				
			||||||
 | 
					                    *ngIf="filters.show.inprogress">
 | 
				
			||||||
 | 
					                    {{ 'addon.block_myoverview.inprogress' | translate }}
 | 
				
			||||||
 | 
					                </ion-select-option>
 | 
				
			||||||
 | 
					                <ion-select-option class="ion-text-wrap" [class.core-select-option-border-bottom]="!filters.show.past" value="future"
 | 
				
			||||||
 | 
					                    *ngIf="filters.show.future">
 | 
				
			||||||
 | 
					                    {{ 'addon.block_myoverview.future' | translate }}
 | 
				
			||||||
 | 
					                </ion-select-option>
 | 
				
			||||||
 | 
					                <ion-select-option class="ion-text-wrap core-select-option-border-bottom" value="past" *ngIf="filters.show.past">
 | 
				
			||||||
 | 
					                    {{ 'addon.block_myoverview.past' | translate }}
 | 
				
			||||||
 | 
					                </ion-select-option>
 | 
				
			||||||
 | 
					                <ng-container *ngIf="filters.show.custom">
 | 
				
			||||||
 | 
					                    <ng-container *ngFor="let customOption of filters.customFilters; let index = index; let last = last">
 | 
				
			||||||
 | 
					                        <ion-select-option class="ion-text-wrap" value="custom-{{index}}" [class.core-select-option-border-bottom]="last">
 | 
				
			||||||
 | 
					                            {{ customOption.name }}</ion-select-option>
 | 
				
			||||||
 | 
					                    </ng-container>
 | 
				
			||||||
 | 
					                </ng-container>
 | 
				
			||||||
 | 
					                <ion-select-option class="ion-text-wrap core-select-option-border-bottom" value="favourite" *ngIf="filters.show.favourite">
 | 
				
			||||||
 | 
					                    {{ 'addon.block_myoverview.favourites' | translate }}
 | 
				
			||||||
 | 
					                </ion-select-option>
 | 
				
			||||||
 | 
					                <ion-select-option class="ion-text-wrap" value="hidden" *ngIf="filters.show.hidden">
 | 
				
			||||||
 | 
					                    {{ 'addon.block_myoverview.hiddencourses' | translate }}
 | 
				
			||||||
 | 
					                </ion-select-option>
 | 
				
			||||||
            </core-combobox>
 | 
					            </core-combobox>
 | 
				
			||||||
        </ion-col>
 | 
					        </ion-col>
 | 
				
			||||||
        <ion-col>
 | 
					        <ion-col>
 | 
				
			||||||
            <!-- Filter courses. -->
 | 
					            <!-- Filter courses. -->
 | 
				
			||||||
            <ion-searchbar class="ion-hide-md-down" [(ngModel)]="textFilter" (ionInput)="filterTextChanged($event.target)"
 | 
					            <ion-searchbar class="ion-hide-md-down" [(ngModel)]="textFilter" (ionInput)="filterTextChanged($event.target)"
 | 
				
			||||||
                (ionCancel)="filterTextChanged($event.target)" [placeholder]="'core.filter' | translate">
 | 
					                (ionCancel)="filterTextChanged($event.target)" [placeholder]="'core.courses.filtermycourses' | translate">
 | 
				
			||||||
            </ion-searchbar>
 | 
					            </ion-searchbar>
 | 
				
			||||||
        </ion-col>
 | 
					        </ion-col>
 | 
				
			||||||
        <ion-col size="auto" *ngIf="sort.enabled">
 | 
					        <ion-col size="auto" *ngIf="sort.enabled">
 | 
				
			||||||
@ -59,17 +98,14 @@
 | 
				
			|||||||
            </ion-button>
 | 
					            </ion-button>
 | 
				
			||||||
        </ion-col>
 | 
					        </ion-col>
 | 
				
			||||||
    </ion-row>
 | 
					    </ion-row>
 | 
				
			||||||
    <ion-row class="ion-hide-md-up addon-block-myoverview-filter" *ngIf="hasCourses">
 | 
					 | 
				
			||||||
        <ion-col>
 | 
					 | 
				
			||||||
            <!-- 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"
 | 
					    <core-empty-box *ngIf="filteredCourses.length == 0" image="assets/img/icons/courses.svg"
 | 
				
			||||||
        [message]="'addon.block_myoverview.nocourses' | translate">
 | 
					        [message]="'addon.block_myoverview.nocourses' | translate">
 | 
				
			||||||
 | 
					        <ion-button *ngIf="searchEnabled" (click)="openSearch()" fill="outline">
 | 
				
			||||||
 | 
					            <ion-icon name="fas-search" slot="start" aria-hidden="true">
 | 
				
			||||||
 | 
					            </ion-icon>
 | 
				
			||||||
 | 
					            {{'core.courses.searchcourses' | translate}}
 | 
				
			||||||
 | 
					        </ion-button>
 | 
				
			||||||
    </core-empty-box>
 | 
					    </core-empty-box>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <!-- List of courses. -->
 | 
					    <!-- List of courses. -->
 | 
				
			||||||
 | 
				
			|||||||
@ -1,21 +1,35 @@
 | 
				
			|||||||
:host {
 | 
					:host {
 | 
				
			||||||
    ion-row.addon-block-myoverview-filter {
 | 
					    ion-row.addon-block-myoverview-filter {
 | 
				
			||||||
        padding: 4px 8px 0px 8px;
 | 
					        margin: 8px;
 | 
				
			||||||
 | 
					        padding: 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ion-col {
 | 
					        ion-col {
 | 
				
			||||||
            padding: 0 2px;
 | 
					            padding: 0;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ion-button,
 | 
					        ion-button,
 | 
				
			||||||
        core-combobox ::ng-deep ion-button {
 | 
					        core-combobox ::ng-deep ion-button {
 | 
				
			||||||
 | 
					            --border-width: 0;
 | 
				
			||||||
 | 
					            --a11y-min-target-size: 40px;
 | 
				
			||||||
            margin: 0;
 | 
					            margin: 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            .select-icon {
 | 
				
			||||||
 | 
					                display: none;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            ion-icon {
 | 
					            ion-icon {
 | 
				
			||||||
                font-size: 20px;
 | 
					                font-size: 20px;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ::ng-deep ion-searchbar {
 | 
					        core-combobox ::ng-deep ion-select {
 | 
				
			||||||
 | 
					            margin: 0;
 | 
				
			||||||
 | 
					            --a11y-min-target-size: 40px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       ion-searchbar {
 | 
				
			||||||
            padding: 0;
 | 
					            padding: 0;
 | 
				
			||||||
 | 
					            --height: 40px;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,6 @@
 | 
				
			|||||||
// limitations under the License.
 | 
					// limitations under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Component, OnInit, OnDestroy } from '@angular/core';
 | 
					import { Component, OnInit, OnDestroy } from '@angular/core';
 | 
				
			||||||
import { ModalOptions } from '@ionic/core';
 | 
					 | 
				
			||||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
					import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
				
			||||||
import { CoreTimeUtils } from '@services/utils/time';
 | 
					import { CoreTimeUtils } from '@services/utils/time';
 | 
				
			||||||
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
					import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
				
			||||||
@ -27,11 +26,12 @@ import { CoreUtils } from '@services/utils/utils';
 | 
				
			|||||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
					import { CoreDomUtils } from '@services/utils/dom';
 | 
				
			||||||
import { CoreTextUtils } from '@services/utils/text';
 | 
					import { CoreTextUtils } from '@services/utils/text';
 | 
				
			||||||
import { AddonCourseCompletion } from '@addons/coursecompletion/services/coursecompletion';
 | 
					import { AddonCourseCompletion } from '@addons/coursecompletion/services/coursecompletion';
 | 
				
			||||||
import { AddonBlockMyOverviewFilterOptionsComponent } from '../filteroptions/filteroptions';
 | 
					 | 
				
			||||||
import { IonSearchbar } from '@ionic/angular';
 | 
					import { IonSearchbar } from '@ionic/angular';
 | 
				
			||||||
import moment from 'moment';
 | 
					import moment from 'moment';
 | 
				
			||||||
 | 
					import { CoreNavigator } from '@services/navigator';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const FILTER_PRIORITY: AddonBlockMyOverviewTimeFilters[] = ['all', 'inprogress', 'future', 'past'];
 | 
					const FILTER_PRIORITY: AddonBlockMyOverviewTimeFilters[] =
 | 
				
			||||||
 | 
					    ['all', 'inprogress', 'future', 'past', 'favourite', 'allincludinghidden', 'hidden'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Component to render a my overview block.
 | 
					 * Component to render a my overview block.
 | 
				
			||||||
@ -58,6 +58,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
 | 
				
			|||||||
    filters: AddonBlockMyOverviewFilterOptions = {
 | 
					    filters: AddonBlockMyOverviewFilterOptions = {
 | 
				
			||||||
        enabled: false,
 | 
					        enabled: false,
 | 
				
			||||||
        show: { // Options are visible, disabled, hidden.
 | 
					        show: { // Options are visible, disabled, hidden.
 | 
				
			||||||
 | 
					            allincludinghidden: true,
 | 
				
			||||||
            all: true,
 | 
					            all: true,
 | 
				
			||||||
            past: true,
 | 
					            past: true,
 | 
				
			||||||
            inprogress: true,
 | 
					            inprogress: true,
 | 
				
			||||||
@ -67,14 +68,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
 | 
				
			|||||||
            custom: false,
 | 
					            custom: false,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        timeFilterSelected: 'inprogress',
 | 
					        timeFilterSelected: 'inprogress',
 | 
				
			||||||
        favouriteSelected: false,
 | 
					 | 
				
			||||||
        hiddenSelected: false,
 | 
					 | 
				
			||||||
        customFilters: [],
 | 
					        customFilters: [],
 | 
				
			||||||
        count: 0,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    filterModalOptions: ModalOptions = {
 | 
					 | 
				
			||||||
        component: AddonBlockMyOverviewFilterOptionsComponent,
 | 
					 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    isLayoutSwitcherAvailable = false;
 | 
					    isLayoutSwitcherAvailable = false;
 | 
				
			||||||
@ -88,6 +82,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    textFilter = '';
 | 
					    textFilter = '';
 | 
				
			||||||
    hasCourses = false;
 | 
					    hasCourses = false;
 | 
				
			||||||
 | 
					    searchEnabled = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected currentSite!: CoreSite;
 | 
					    protected currentSite!: CoreSite;
 | 
				
			||||||
    protected allCourses: CoreEnrolledCourseDataWithOptions[] = [];
 | 
					    protected allCourses: CoreEnrolledCourseDataWithOptions[] = [];
 | 
				
			||||||
@ -98,6 +93,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
 | 
				
			|||||||
    protected fetchContentDefaultError = 'Error getting my overview data.';
 | 
					    protected fetchContentDefaultError = 'Error getting my overview data.';
 | 
				
			||||||
    protected gradePeriodAfter = 0;
 | 
					    protected gradePeriodAfter = 0;
 | 
				
			||||||
    protected gradePeriodBefore = 0;
 | 
					    protected gradePeriodBefore = 0;
 | 
				
			||||||
 | 
					    protected today = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor() {
 | 
					    constructor() {
 | 
				
			||||||
        super('AddonBlockMyOverviewComponent');
 | 
					        super('AddonBlockMyOverviewComponent');
 | 
				
			||||||
@ -110,12 +106,13 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
 | 
				
			|||||||
        // Refresh the enabled flags if enabled.
 | 
					        // Refresh the enabled flags if enabled.
 | 
				
			||||||
        this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
 | 
					        this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
 | 
				
			||||||
        this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
 | 
					        this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
 | 
				
			||||||
 | 
					        this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Refresh the enabled flags if site is updated.
 | 
					        // Refresh the enabled flags if site is updated.
 | 
				
			||||||
        this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
 | 
					        this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
 | 
				
			||||||
            this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
 | 
					            this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
 | 
				
			||||||
            this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
 | 
					            this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
 | 
				
			||||||
 | 
					            this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite();
 | 
				
			||||||
        }, CoreSites.getCurrentSiteId());
 | 
					        }, CoreSites.getCurrentSiteId());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.coursesObserver = CoreEvents.on(
 | 
					        this.coursesObserver = CoreEvents.on(
 | 
				
			||||||
@ -148,43 +145,11 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
 | 
				
			|||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }));
 | 
					        }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Wait for the migration.
 | 
					        promises.push(this.currentSite.getLocalSiteConfig(
 | 
				
			||||||
        await this.currentSite.getLocalSiteConfig<string>(
 | 
					 | 
				
			||||||
            'AddonBlockMyOverviewFilter',
 | 
					            'AddonBlockMyOverviewFilter',
 | 
				
			||||||
            this.filters.timeFilterSelected,
 | 
					            this.filters.timeFilterSelected,
 | 
				
			||||||
        ).then(async (value) => {
 | 
					 | 
				
			||||||
            if (FILTER_PRIORITY.includes(value as AddonBlockMyOverviewTimeFilters)) {
 | 
					 | 
				
			||||||
                this.filters.timeFilterSelected = value as AddonBlockMyOverviewTimeFilters;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Migrate setting.
 | 
					 | 
				
			||||||
            this.filters.hiddenSelected = value == 'allincludinghidden' || value == 'hidden';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (value == 'favourite') {
 | 
					 | 
				
			||||||
                this.filters.favouriteSelected = true;
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                this.filters.favouriteSelected = false;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return await this.saveFilters('all');
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        promises.push(this.currentSite.getLocalSiteConfig(
 | 
					 | 
				
			||||||
            'AddonBlockMyOverviewFavouriteFilter',
 | 
					 | 
				
			||||||
            this.filters.favouriteSelected ? 1 : 0,
 | 
					 | 
				
			||||||
        ).then((value) => {
 | 
					        ).then((value) => {
 | 
				
			||||||
            this.filters.favouriteSelected = value == 1;
 | 
					            this.filters.timeFilterSelected = value;
 | 
				
			||||||
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        promises.push(this.currentSite.getLocalSiteConfig(
 | 
					 | 
				
			||||||
            'AddonBlockMyOverviewHiddenFilter',
 | 
					 | 
				
			||||||
            this.filters.hiddenSelected ? 1 : 0,
 | 
					 | 
				
			||||||
        ).then((value) => {
 | 
					 | 
				
			||||||
            this.filters.hiddenSelected = value == 1;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }));
 | 
					        }));
 | 
				
			||||||
@ -312,6 +277,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
 | 
				
			|||||||
            config?.displaygroupingallincludinghidden?.value == '1' ||
 | 
					            config?.displaygroupingallincludinghidden?.value == '1' ||
 | 
				
			||||||
            sampleCourse.hidden !== undefined && (!config || config.displaygroupinghidden?.value == '1');
 | 
					            sampleCourse.hidden !== undefined && (!config || config.displaygroupinghidden?.value == '1');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.filters.show.allincludinghidden =  !config || config.displaygroupingallincludinghidden?.value == '1';
 | 
				
			||||||
        this.filters.show.all =  !config || config.displaygroupingall?.value == '1';
 | 
					        this.filters.show.all =  !config || config.displaygroupingall?.value == '1';
 | 
				
			||||||
        this.filters.show.inprogress = !config || config.displaygroupinginprogress?.value == '1';
 | 
					        this.filters.show.inprogress = !config || config.displaygroupinginprogress?.value == '1';
 | 
				
			||||||
        this.filters.show.past = !config || config.displaygroupingpast?.value == '1';
 | 
					        this.filters.show.past = !config || config.displaygroupingpast?.value == '1';
 | 
				
			||||||
@ -335,10 +301,6 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
 | 
				
			|||||||
            this.saveFilters('all');
 | 
					            this.saveFilters('all');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.filterModalOptions.componentProps = {
 | 
					 | 
				
			||||||
            options: Object.assign({}, this.filters),
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.filterCourses();
 | 
					        this.filterCourses();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -472,81 +434,66 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
 | 
				
			|||||||
     * Set selected courses filter.
 | 
					     * Set selected courses filter.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected async filterCourses(): Promise<void> {
 | 
					    protected async filterCourses(): Promise<void> {
 | 
				
			||||||
        this.filters.count = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let timeFilter = this.filters.timeFilterSelected;
 | 
					        let timeFilter = this.filters.timeFilterSelected;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Filter is not active, take the first active or all.
 | 
					 | 
				
			||||||
        if (!this.filters.show[timeFilter]) {
 | 
					 | 
				
			||||||
            timeFilter = FILTER_PRIORITY.find((name) => this.filters.show[name]) || 'all';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            this.saveFilters(timeFilter);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (timeFilter !== 'all') {
 | 
					 | 
				
			||||||
            this.filters.count++;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.filteredCourses = this.allCourses;
 | 
					        this.filteredCourses = this.allCourses;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const customFilterName = this.block.configsRecord?.customfiltergrouping.value;
 | 
					        if (this.filters.show.custom && timeFilter.startsWith('custom-')) {
 | 
				
			||||||
        const customFilterValue = this.filters.customSelected;
 | 
					            // Custom filter.
 | 
				
			||||||
        if (customFilterName && this.filters.show.custom && customFilterValue !== undefined) {
 | 
					            const customFilterName = this.block.configsRecord?.customfiltergrouping.value;
 | 
				
			||||||
            this.filters.count++;
 | 
					            const customFilterValue = this.filters.customFilters[timeFilter.substring(7)]?.value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.loaded = false;
 | 
					            if (customFilterName !== undefined && customFilterValue !== undefined) {
 | 
				
			||||||
            try {
 | 
					                this.loaded = false;
 | 
				
			||||||
                const courses = await CoreCourses.getEnrolledCoursesByCustomField(customFilterName, customFilterValue);
 | 
					                try {
 | 
				
			||||||
 | 
					                    const courses = await CoreCourses.getEnrolledCoursesByCustomField(customFilterName, customFilterValue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const courseIds = courses.map((course) => course.id);
 | 
					                    // Get the courses information from allincludinghidden to get the max info about the course.
 | 
				
			||||||
 | 
					                    const courseIds = courses.map((course) => course.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                this.filteredCourses = this.filteredCourses.filter((course) => courseIds.includes(course.id));
 | 
					                    this.filteredCourses = this.filteredCourses.filter((course) => courseIds.includes(course.id));
 | 
				
			||||||
            } catch (error) {
 | 
					                } catch (error) {
 | 
				
			||||||
                CoreDomUtils.showErrorModalDefault(error, this.fetchContentDefaultError);
 | 
					                    CoreDomUtils.showErrorModalDefault(error, this.fetchContentDefaultError);
 | 
				
			||||||
            } finally {
 | 
					                } finally {
 | 
				
			||||||
                this.loaded = true;
 | 
					                    this.loaded = true;
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const onlyFavourite = this.filters.show.favourite && this.filters.favouriteSelected;
 | 
					 | 
				
			||||||
        if (onlyFavourite) {
 | 
					 | 
				
			||||||
            this.filters.count++;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const showHidden = this.filters.show.hidden && this.filters.hiddenSelected;
 | 
					 | 
				
			||||||
        if (showHidden) {
 | 
					 | 
				
			||||||
            this.filters.count++;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Time filter, favourite and hidden.
 | 
					 | 
				
			||||||
        const today = CoreTimeUtils.timestamp();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.filteredCourses = this.filteredCourses.filter((course) => {
 | 
					 | 
				
			||||||
            let include = timeFilter == 'all';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!include) {
 | 
					 | 
				
			||||||
                if ((course.enddate && this.courseClassifyEndDate(course.enddate) < today) || course.completed) {
 | 
					 | 
				
			||||||
                    // Courses that have already ended.
 | 
					 | 
				
			||||||
                    include = timeFilter == 'past';
 | 
					 | 
				
			||||||
                } else if (course.startdate && this.courseClassifyStartDate(course.startdate) > today) {
 | 
					 | 
				
			||||||
                    // Courses that have not started yet.
 | 
					 | 
				
			||||||
                    include = timeFilter == 'future';
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    // Courses still in progress.
 | 
					 | 
				
			||||||
                    include = timeFilter == 'inprogress';
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
            if (onlyFavourite) {
 | 
					            // Filter is not active, take the first active or all. Custom is never saved.
 | 
				
			||||||
                include = include && !!course.isfavourite;
 | 
					            if (!this.filters.show[timeFilter]) {
 | 
				
			||||||
 | 
					                timeFilter = FILTER_PRIORITY.find((name) => this.filters.show[name]) || 'all';
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            this.saveFilters(timeFilter);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!showHidden) {
 | 
					            // Update today date.
 | 
				
			||||||
                include = include && !course.hidden;
 | 
					            this.today = Date.now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Apply filters.
 | 
				
			||||||
 | 
					            switch(timeFilter) {
 | 
				
			||||||
 | 
					                case 'allincludinghidden':
 | 
				
			||||||
 | 
					                    // No nothing, it's all courses.
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                case 'all':
 | 
				
			||||||
 | 
					                    this.filteredCourses = this.filteredCourses.filter((course) => !course.hidden);
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                case 'inprogress':
 | 
				
			||||||
 | 
					                    this.filteredCourses = this.filteredCourses.filter((course) =>
 | 
				
			||||||
 | 
					                        !course.hidden && !this.isPastCourse(course) && !this.isFutureCourse(course));
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                case 'future':
 | 
				
			||||||
 | 
					                    this.filteredCourses = this.filteredCourses.filter((course) => !course.hidden && this.isFutureCourse(course));
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                case 'past':
 | 
				
			||||||
 | 
					                    this.filteredCourses = this.filteredCourses.filter((course) => !course.hidden && this.isPastCourse(course));
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                case 'favourite':
 | 
				
			||||||
 | 
					                    this.filteredCourses = this.filteredCourses.filter((course) => !course.hidden && course.isfavourite);
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                case 'hidden':
 | 
				
			||||||
 | 
					                    this.filteredCourses = this.filteredCourses.filter((course) => course.hidden);
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
            return include;
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Text filter.
 | 
					        // Text filter.
 | 
				
			||||||
        const value = this.textFilter.trim().toLowerCase();
 | 
					        const value = this.textFilter.trim().toLowerCase();
 | 
				
			||||||
@ -569,23 +516,41 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * This function calculates the end date to use for display classification purposes, incorporating the grace period, if any.
 | 
					     * Calculates if course date is past.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param endDate Course end date.
 | 
					     * @param course Course Object.
 | 
				
			||||||
     * @return The new enddate.
 | 
					     * @return Wether the course is past.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected courseClassifyEndDate(endDate: number): number {
 | 
					    protected isPastCourse(course: CoreEnrolledCourseDataWithOptions): boolean {
 | 
				
			||||||
        return moment(endDate).add(this.gradePeriodAfter, 'days').valueOf();
 | 
					        if (course.completed) {
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!course.enddate) {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Calculate the end date to use for display classification purposes, incorporating the grace period, if any.
 | 
				
			||||||
 | 
					        const endDate = moment(course.enddate * 1000).add(this.gradePeriodAfter, 'days').valueOf();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return endDate < this.today;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * This function calculates the start date to use for display classification purposes, incorporating the grace period, if any.
 | 
					     * Calculates if course date is future.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param startDate Course start date.
 | 
					     * @param course Course Object.
 | 
				
			||||||
     * @return The new startdate.
 | 
					     * @return Wether the course is future.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected courseClassifyStartDate(startDate: number): number {
 | 
					    protected isFutureCourse(course: CoreEnrolledCourseDataWithOptions): boolean {
 | 
				
			||||||
        return moment(startDate).subtract(this.gradePeriodBefore, 'days').valueOf();
 | 
					        if (this.isPastCourse(course) || !course.startdate) {
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Calculate the start date to use for display classification purposes, incorporating the grace period, if any.
 | 
				
			||||||
 | 
					        const startDate = moment(course.startdate * 1000).subtract(this.gradePeriodBefore, 'days').valueOf();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return startDate > this.today;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -627,18 +592,9 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
 | 
				
			|||||||
     * @param timeFilter New time filter.
 | 
					     * @param timeFilter New time filter.
 | 
				
			||||||
     * @return Promise resolved when done.
 | 
					     * @return Promise resolved when done.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async saveFilters(timeFilter: AddonBlockMyOverviewTimeFilters): Promise<void> {
 | 
					    async saveFilters(timeFilter: string): Promise<void> {
 | 
				
			||||||
        this.filters.timeFilterSelected = timeFilter;
 | 
					        this.filters.timeFilterSelected = timeFilter;
 | 
				
			||||||
 | 
					        await this.currentSite.setLocalSiteConfig('AddonBlockMyOverviewFilter', timeFilter);
 | 
				
			||||||
        this.filterModalOptions.componentProps = {
 | 
					 | 
				
			||||||
            options: Object.assign({}, this.filters),
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await Promise.all([
 | 
					 | 
				
			||||||
            this.currentSite.setLocalSiteConfig('AddonBlockMyOverviewFilter', this.filters.timeFilterSelected),
 | 
					 | 
				
			||||||
            this.currentSite.setLocalSiteConfig('AddonBlockMyOverviewFavouriteFilter', this.filters.favouriteSelected ? 1 : 0),
 | 
					 | 
				
			||||||
            this.currentSite.setLocalSiteConfig('AddonBlockMyOverviewHiddenFilter', this.filters.hiddenSelected ? 1 : 0),
 | 
					 | 
				
			||||||
        ]);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -666,16 +622,23 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Opens display Options modal.
 | 
					     * Option selected save and apply filter.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
 | 
					     * @param selected Option selected.
 | 
				
			||||||
     * @return Promise resolved when done.
 | 
					     * @return Promise resolved when done.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    filterOptionsChanged(modalData: AddonBlockMyOverviewFilterOptions): void {
 | 
					    async filterOptionsChanged(selected: AddonBlockMyOverviewTimeFilters): Promise<void> {
 | 
				
			||||||
        this.filters = modalData;
 | 
					        this.filters.timeFilterSelected = selected;
 | 
				
			||||||
        this.saveFilters(this.filters.timeFilterSelected);
 | 
					 | 
				
			||||||
        this.filterCourses();
 | 
					        this.filterCourses();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Go to search courses.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    async openSearch(): Promise<void> {
 | 
				
			||||||
 | 
					        CoreNavigator.navigateToSitePath('courses/list', { params : { mode: 'search' } });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @inheritdoc
 | 
					     * @inheritdoc
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
@ -688,11 +651,12 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type AddonBlockMyOverviewLayouts = 'card'|'list';
 | 
					type AddonBlockMyOverviewLayouts = 'card'|'list';
 | 
				
			||||||
type AddonBlockMyOverviewTimeFilters = 'all'|'inprogress'|'future'|'past';
 | 
					type AddonBlockMyOverviewTimeFilters = 'allincludinghidden'|'all'|'inprogress'|'future'|'past'|'favourite'|'hidden';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type AddonBlockMyOverviewFilterOptions = {
 | 
					export type AddonBlockMyOverviewFilterOptions = {
 | 
				
			||||||
    enabled: boolean;
 | 
					    enabled: boolean;
 | 
				
			||||||
    show: {
 | 
					    show: {
 | 
				
			||||||
 | 
					        allincludinghidden: boolean;
 | 
				
			||||||
        all: boolean;
 | 
					        all: boolean;
 | 
				
			||||||
        inprogress: boolean;
 | 
					        inprogress: boolean;
 | 
				
			||||||
        future: boolean;
 | 
					        future: boolean;
 | 
				
			||||||
@ -701,15 +665,11 @@ export type AddonBlockMyOverviewFilterOptions = {
 | 
				
			|||||||
        hidden: boolean;
 | 
					        hidden: boolean;
 | 
				
			||||||
        custom: boolean;
 | 
					        custom: boolean;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    timeFilterSelected: AddonBlockMyOverviewTimeFilters;
 | 
					    timeFilterSelected: string;
 | 
				
			||||||
    favouriteSelected: boolean;
 | 
					 | 
				
			||||||
    hiddenSelected: boolean;
 | 
					 | 
				
			||||||
    customFilters: {
 | 
					    customFilters: {
 | 
				
			||||||
        name: string;
 | 
					        name: string;
 | 
				
			||||||
        value: string;
 | 
					        value: string;
 | 
				
			||||||
    }[];
 | 
					    }[];
 | 
				
			||||||
    customSelected?: string;
 | 
					 | 
				
			||||||
    count: number;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type AddonBlockMyOverviewSortOptions = {
 | 
					type AddonBlockMyOverviewSortOptions = {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,10 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    "all": "All",
 | 
					    "all": "All",
 | 
				
			||||||
    "allincludinghidden": "All (including archived)",
 | 
					    "allincludinghidden": "All (including archived)",
 | 
				
			||||||
    "aria:favourites": "Show starred courses only",
 | 
					 | 
				
			||||||
    "aria:hiddencourses": "Show archived courses",
 | 
					 | 
				
			||||||
    "card": "Card",
 | 
					    "card": "Card",
 | 
				
			||||||
 | 
					    "favourites": "Starred",
 | 
				
			||||||
    "future": "Future",
 | 
					    "future": "Future",
 | 
				
			||||||
 | 
					    "hiddencourses": "Archived",
 | 
				
			||||||
    "inprogress": "In progress",
 | 
					    "inprogress": "In progress",
 | 
				
			||||||
    "lastaccessed": "Last accessed",
 | 
					    "lastaccessed": "Last accessed",
 | 
				
			||||||
    "list": "List",
 | 
					    "list": "List",
 | 
				
			||||||
 | 
				
			|||||||
@ -24,7 +24,7 @@
 | 
				
			|||||||
                        <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
					                        <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
				
			||||||
                    </ion-refresher>
 | 
					                    </ion-refresher>
 | 
				
			||||||
                    <core-loading [hideUntil]="confirmedLoaded">
 | 
					                    <core-loading [hideUntil]="confirmedLoaded">
 | 
				
			||||||
                        <ion-list class="ion-no-margin">
 | 
					                        <ion-list class="ion-no-margin" *ngIf="confirmedContacts.length">
 | 
				
			||||||
                            <ion-item class="ion-text-wrap addon-messages-conversation-item" (click)="selectUser(contact.id)" button
 | 
					                            <ion-item class="ion-text-wrap addon-messages-conversation-item" (click)="selectUser(contact.id)" button
 | 
				
			||||||
                                *ngFor="let contact of confirmedContacts" [attr.aria-label]="contact.fullname" detail="true"
 | 
					                                *ngFor="let contact of confirmedContacts" [attr.aria-label]="contact.fullname" detail="true"
 | 
				
			||||||
                                [attr.aria-current]="contact.id == selectedUserId ? 'page' : 'false'">
 | 
					                                [attr.aria-current]="contact.id == selectedUserId ? 'page' : 'false'">
 | 
				
			||||||
@ -62,7 +62,7 @@
 | 
				
			|||||||
                        <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
					                        <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
				
			||||||
                    </ion-refresher>
 | 
					                    </ion-refresher>
 | 
				
			||||||
                    <core-loading [hideUntil]="requestsLoaded">
 | 
					                    <core-loading [hideUntil]="requestsLoaded">
 | 
				
			||||||
                        <ion-list class="ion-no-margin">
 | 
					                        <ion-list class="ion-no-margin" *ngIf="requests.length">
 | 
				
			||||||
                            <ion-item class="ion-text-wrap addon-messages-conversation-item" *ngFor="let request of requests"
 | 
					                            <ion-item class="ion-text-wrap addon-messages-conversation-item" *ngFor="let request of requests"
 | 
				
			||||||
                                [attr.aria-label]="request.fullname" (click)="selectUser(request.id)" button
 | 
					                                [attr.aria-label]="request.fullname" (click)="selectUser(request.id)" button
 | 
				
			||||||
                                [attr.aria-current]="request.id == selectedUserId ? 'page' : 'false'" detail="true">
 | 
					                                [attr.aria-current]="request.id == selectedUserId ? 'page' : 'false'" detail="true">
 | 
				
			||||||
 | 
				
			|||||||
@ -27,24 +27,28 @@
 | 
				
			|||||||
        @include position(0, 0, null, null);
 | 
					        @include position(0, 0, null, null);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ion-header ion-toolbar h1 {
 | 
					    ion-header ion-toolbar ion-title {
 | 
				
			||||||
        display: flex;
 | 
					 | 
				
			||||||
        align-items: center;
 | 
					 | 
				
			||||||
        padding: 0;
 | 
					        padding: 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .core-bar-button-image {
 | 
					        h1 {
 | 
				
			||||||
            @include margin-horizontal(null, 6px);
 | 
					            display: flex;
 | 
				
			||||||
        }
 | 
					            align-items: center;
 | 
				
			||||||
 | 
					            padding: 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        core-format-text {
 | 
					            .core-bar-button-image {
 | 
				
			||||||
            overflow: hidden;
 | 
					                @include margin-horizontal(null, 6px);
 | 
				
			||||||
            text-overflow: ellipsis;
 | 
					            }
 | 
				
			||||||
            white-space: nowrap;
 | 
					 | 
				
			||||||
            flex-shrink: 1;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ion-icon {
 | 
					            core-format-text {
 | 
				
			||||||
            @include margin-horizontal(6px, null);
 | 
					                overflow: hidden;
 | 
				
			||||||
 | 
					                text-overflow: ellipsis;
 | 
				
			||||||
 | 
					                white-space: nowrap;
 | 
				
			||||||
 | 
					                flex-shrink: 1;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ion-icon {
 | 
				
			||||||
 | 
					                @include margin-horizontal(6px, null);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -24,7 +24,7 @@
 | 
				
			|||||||
        <core-iframe [src]="src"></core-iframe>
 | 
					        <core-iframe [src]="src"></core-iframe>
 | 
				
			||||||
    </ng-container>
 | 
					    </ng-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div *ngIf="mode == 'embedded'">
 | 
					    <div *ngIf="mode == 'embedded'" class="ion-padding">
 | 
				
			||||||
        <core-format-text [text]="contentText" [filter]="false"></core-format-text>
 | 
					        <core-format-text [text]="contentText" [filter]="false"></core-format-text>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -53,7 +53,6 @@ export class CoreComboboxComponent {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // Additional options when interface modal is selected.
 | 
					    // Additional options when interface modal is selected.
 | 
				
			||||||
    @Input() icon?: string; // Icon for modal interface.
 | 
					    @Input() icon?: string; // Icon for modal interface.
 | 
				
			||||||
    @Input() badge?: number; // Badge number to show near the icon.
 | 
					 | 
				
			||||||
    @Input() modalOptions?: ModalOptions; // Will emit an event the value changed.
 | 
					    @Input() modalOptions?: ModalOptions; // Will emit an event the value changed.
 | 
				
			||||||
    @Input() listboxId = '';
 | 
					    @Input() listboxId = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,6 @@
 | 
				
			|||||||
<ion-button (click)="openSelect($event)" *ngIf="interface != 'modal' && icon" [disabled]="disabled">
 | 
					<ion-button (click)="openSelect($event)" *ngIf="interface != 'modal' && icon" [disabled]="disabled">
 | 
				
			||||||
    <ion-icon [name]="icon" [attr.aria-label]="label" slot="start">
 | 
					    <ion-icon [name]="icon" [attr.aria-label]="label" slot="start">
 | 
				
			||||||
    </ion-icon>
 | 
					    </ion-icon>
 | 
				
			||||||
    <ion-badge *ngIf="badge && badge > 0" slot="start">{{badge}}</ion-badge>
 | 
					 | 
				
			||||||
    <div class="select-icon" role="presentation" aria-hidden="true">
 | 
					    <div class="select-icon" role="presentation" aria-hidden="true">
 | 
				
			||||||
        <div class="select-icon-inner"></div>
 | 
					        <div class="select-icon-inner"></div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
@ -14,7 +13,6 @@
 | 
				
			|||||||
<ion-button *ngIf="interface == 'modal'" aria-haspopup="listbox" [attr.aria-controls]="listboxId" [attr.aria-owns]="listboxId"
 | 
					<ion-button *ngIf="interface == 'modal'" aria-haspopup="listbox" [attr.aria-controls]="listboxId" [attr.aria-owns]="listboxId"
 | 
				
			||||||
    [attr.aria-expanded]="expanded" (click)="openSelect()" [disabled]="disabled" expand="block" role="combobox">
 | 
					    [attr.aria-expanded]="expanded" (click)="openSelect()" [disabled]="disabled" expand="block" role="combobox">
 | 
				
			||||||
    <ion-icon *ngIf="icon" [name]="icon" slot="start" aria-hidden="true"></ion-icon>
 | 
					    <ion-icon *ngIf="icon" [name]="icon" slot="start" aria-hidden="true"></ion-icon>
 | 
				
			||||||
    <ion-badge *ngIf="badge && badge > 0" slot="start">{{badge}}</ion-badge>
 | 
					 | 
				
			||||||
    <span class="sr-only" *ngIf="label">{{ label }}:</span>
 | 
					    <span class="sr-only" *ngIf="label">{{ label }}:</span>
 | 
				
			||||||
    <div class="select-text">
 | 
					    <div class="select-text">
 | 
				
			||||||
        <slot name="text">{{selection}}</slot>
 | 
					        <slot name="text">{{selection}}</slot>
 | 
				
			||||||
 | 
				
			|||||||
@ -13,6 +13,8 @@
 | 
				
			|||||||
    padding: 16px;
 | 
					    padding: 16px;
 | 
				
			||||||
    --image-size: 120px;
 | 
					    --image-size: 120px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ion-icon {
 | 
					    ion-icon {
 | 
				
			||||||
        font-size: var(--image-size);
 | 
					        font-size: var(--image-size);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -22,8 +22,9 @@ import { Component } from '@angular/core';
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
    selector: 'core-spacer',
 | 
					    selector: 'core-spacer',
 | 
				
			||||||
    template: '<ion-item-divider><ion-label></ion-label></ion-item-divider>',
 | 
					    template: '',
 | 
				
			||||||
    styles: [':host {--item-divider-min-height: 30px;}'],
 | 
					    styles: [':host { display: block; margin: var(--spacer-vertical) var(--spacer-horizontal); \
 | 
				
			||||||
 | 
					        border-bottom: 1px solid var(--spacer-color);}'],
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
export class CoreSpacerComponent {
 | 
					export class CoreSpacerComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -50,7 +50,8 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
 | 
				
			|||||||
    protected endContentScrollListener?: EventListener;
 | 
					    protected endContentScrollListener?: EventListener;
 | 
				
			||||||
    protected resizeListener?: CoreEventObserver;
 | 
					    protected resizeListener?: CoreEventObserver;
 | 
				
			||||||
    protected slotPromise?: CoreCancellablePromise<void>;
 | 
					    protected slotPromise?: CoreCancellablePromise<void>;
 | 
				
			||||||
    protected calcPending = false;
 | 
					    protected viewportPromise?: CoreCancellablePromise<void>;
 | 
				
			||||||
 | 
					    protected loadingHeight = false;
 | 
				
			||||||
    protected pageDidEnterListener?: EventListener;
 | 
					    protected pageDidEnterListener?: EventListener;
 | 
				
			||||||
    protected page?: HTMLElement;
 | 
					    protected page?: HTMLElement;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -85,13 +86,14 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
 | 
				
			|||||||
     * Calculate the height of the footer.
 | 
					     * Calculate the height of the footer.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected async calculateHeight(): Promise<void> {
 | 
					    protected async calculateHeight(): Promise<void> {
 | 
				
			||||||
        if (!CoreDom.isElementVisible(this.element)) {
 | 
					        if (this.loadingHeight) {
 | 
				
			||||||
            this.calcPending = true;
 | 
					            // Already calculating, return.
 | 
				
			||||||
 | 
					 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        this.loadingHeight = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.calcPending = false;
 | 
					        this.viewportPromise = CoreDom.waitToBeInViewport(this.element);
 | 
				
			||||||
 | 
					        await this.viewportPromise;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.element.classList.remove('is-active');
 | 
					        this.element.classList.remove('is-active');
 | 
				
			||||||
        await CoreUtils.nextTick();
 | 
					        await CoreUtils.nextTick();
 | 
				
			||||||
@ -110,6 +112,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
 | 
				
			|||||||
        this.element.classList.add('is-active');
 | 
					        this.element.classList.add('is-active');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.setBarHeight(this.initialHeight);
 | 
					        this.setBarHeight(this.initialHeight);
 | 
				
			||||||
 | 
					        this.loadingHeight = false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -175,9 +178,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
 | 
				
			|||||||
        this.page?.addEventListener(
 | 
					        this.page?.addEventListener(
 | 
				
			||||||
            'ionViewDidEnter',
 | 
					            'ionViewDidEnter',
 | 
				
			||||||
            this.pageDidEnterListener = () => {
 | 
					            this.pageDidEnterListener = () => {
 | 
				
			||||||
                if (this.calcPending) {
 | 
					                this.calculateHeight();
 | 
				
			||||||
                    this.calculateHeight();
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -255,6 +256,7 @@ export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        this.resizeListener?.off();
 | 
					        this.resizeListener?.off();
 | 
				
			||||||
        this.slotPromise?.cancel();
 | 
					        this.slotPromise?.cancel();
 | 
				
			||||||
 | 
					        this.viewportPromise?.cancel();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,7 @@
 | 
				
			|||||||
// limitations under the License.
 | 
					// limitations under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Directive, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChange } from '@angular/core';
 | 
					import { Directive, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChange } from '@angular/core';
 | 
				
			||||||
import { CorePromisedValue } from '@classes/promised-value';
 | 
					import { CoreCancellablePromise } from '@classes/cancellable-promise';
 | 
				
			||||||
import { CoreLoadingComponent } from '@components/loading/loading';
 | 
					import { CoreLoadingComponent } from '@components/loading/loading';
 | 
				
			||||||
import { CoreTabsOutletComponent } from '@components/tabs-outlet/tabs-outlet';
 | 
					import { CoreTabsOutletComponent } from '@components/tabs-outlet/tabs-outlet';
 | 
				
			||||||
import { CoreTabsComponent } from '@components/tabs/tabs';
 | 
					import { CoreTabsComponent } from '@components/tabs/tabs';
 | 
				
			||||||
@ -69,17 +69,15 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
 | 
				
			|||||||
    protected content?: HTMLIonContentElement;
 | 
					    protected content?: HTMLIonContentElement;
 | 
				
			||||||
    protected contentScrollListener?: EventListener;
 | 
					    protected contentScrollListener?: EventListener;
 | 
				
			||||||
    protected endContentScrollListener?: EventListener;
 | 
					    protected endContentScrollListener?: EventListener;
 | 
				
			||||||
    protected pageDidEnterListener?: EventListener;
 | 
					 | 
				
			||||||
    protected resizeListener?: CoreEventObserver;
 | 
					    protected resizeListener?: CoreEventObserver;
 | 
				
			||||||
    protected floatingTitle?: HTMLHeadingElement;
 | 
					    protected floatingTitle?: HTMLHeadingElement;
 | 
				
			||||||
    protected scrollingHeight?: number;
 | 
					    protected scrollingHeight?: number;
 | 
				
			||||||
    protected subscriptions: Subscription[] = [];
 | 
					    protected subscriptions: Subscription[] = [];
 | 
				
			||||||
    protected enabled = true;
 | 
					    protected enabled = true;
 | 
				
			||||||
    protected isWithinContent = false;
 | 
					    protected isWithinContent = false;
 | 
				
			||||||
    protected enteredPromise = new CorePromisedValue<void>();
 | 
					 | 
				
			||||||
    protected mutationObserver?: MutationObserver;
 | 
					    protected mutationObserver?: MutationObserver;
 | 
				
			||||||
    protected firstEnter = true;
 | 
					    protected loadingFloatingTitle = false;
 | 
				
			||||||
    protected initPending = false;
 | 
					    protected visiblePromise?: CoreCancellablePromise<void>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(el: ElementRef) {
 | 
					    constructor(el: ElementRef) {
 | 
				
			||||||
        this.collapsedHeader = el.nativeElement;
 | 
					        this.collapsedHeader = el.nativeElement;
 | 
				
			||||||
@ -89,6 +87,7 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
 | 
				
			|||||||
     * @inheritdoc
 | 
					     * @inheritdoc
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    ngOnInit(): void {
 | 
					    ngOnInit(): void {
 | 
				
			||||||
 | 
					        this.collapsible = !CoreUtils.isFalseOrZero(this.collapsible);
 | 
				
			||||||
        this.init();
 | 
					        this.init();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -105,10 +104,9 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
 | 
				
			|||||||
        await Promise.all([
 | 
					        await Promise.all([
 | 
				
			||||||
            this.initializeCollapsedHeader(),
 | 
					            this.initializeCollapsedHeader(),
 | 
				
			||||||
            this.initializeExpandedHeader(),
 | 
					            this.initializeExpandedHeader(),
 | 
				
			||||||
            await this.enteredPromise,
 | 
					 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.initializeFloatingTitle();
 | 
					        await this.initializeFloatingTitle();
 | 
				
			||||||
        this.initializeContent();
 | 
					        this.initializeContent();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -116,8 +114,9 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
 | 
				
			|||||||
     * @inheritdoc
 | 
					     * @inheritdoc
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async ngOnChanges(changes: {[name: string]: SimpleChange}): Promise<void> {
 | 
					    async ngOnChanges(changes: {[name: string]: SimpleChange}): Promise<void> {
 | 
				
			||||||
        if (changes.collapsible) {
 | 
					        if (changes.collapsible && !changes.collapsible.firstChange) {
 | 
				
			||||||
            this.enabled = !CoreUtils.isFalseOrZero(changes.collapsible.currentValue);
 | 
					            this.collapsible = !CoreUtils.isFalseOrZero(changes.collapsible.currentValue);
 | 
				
			||||||
 | 
					            this.enabled = this.collapsible;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await this.init();
 | 
					            await this.init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -139,47 +138,16 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
 | 
				
			|||||||
        if (this.content && this.endContentScrollListener) {
 | 
					        if (this.content && this.endContentScrollListener) {
 | 
				
			||||||
            this.content.removeEventListener('ionScrollEnd', this.endContentScrollListener);
 | 
					            this.content.removeEventListener('ionScrollEnd', this.endContentScrollListener);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (this.page && this.pageDidEnterListener) {
 | 
					 | 
				
			||||||
            this.page.removeEventListener('ionViewDidEnter', this.pageDidEnterListener);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.resizeListener?.off();
 | 
					        this.resizeListener?.off();
 | 
				
			||||||
        this.mutationObserver?.disconnect();
 | 
					        this.mutationObserver?.disconnect();
 | 
				
			||||||
 | 
					        this.visiblePromise?.cancel();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Search the page element, initialize it, and wait until it's ready for the transition to trigger on scroll.
 | 
					     * Listen to changing events.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected initializePage(): void {
 | 
					    protected listenEvents(): void {
 | 
				
			||||||
        if (!this.collapsedHeader.parentElement) {
 | 
					 | 
				
			||||||
            throw new Error('[collapsible-header] Couldn\'t get page');
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Find element and prepare classes.
 | 
					 | 
				
			||||||
        this.page = this.collapsedHeader.parentElement;
 | 
					 | 
				
			||||||
        this.page.classList.add('collapsible-header-page');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.page.addEventListener(
 | 
					 | 
				
			||||||
            'ionViewDidEnter',
 | 
					 | 
				
			||||||
            this.pageDidEnterListener = () => {
 | 
					 | 
				
			||||||
                if (this.firstEnter) {
 | 
					 | 
				
			||||||
                    this.firstEnter = false;
 | 
					 | 
				
			||||||
                    clearTimeout(timeout);
 | 
					 | 
				
			||||||
                    this.enteredPromise.resolve();
 | 
					 | 
				
			||||||
                } else if (this.initPending) {
 | 
					 | 
				
			||||||
                    this.initializeFloatingTitle();
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Timeout in case event is never fired.
 | 
					 | 
				
			||||||
        const timeout = window.setTimeout(() => {
 | 
					 | 
				
			||||||
            if (this.firstEnter) {
 | 
					 | 
				
			||||||
                this.firstEnter = false;
 | 
					 | 
				
			||||||
                this.enteredPromise.reject(new Error('[collapsible-header] Waiting for ionViewDidEnter timeout reached'));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }, 5000);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.resizeListener = CoreDom.onWindowResize(() => {
 | 
					        this.resizeListener = CoreDom.onWindowResize(() => {
 | 
				
			||||||
            this.initializeFloatingTitle();
 | 
					            this.initializeFloatingTitle();
 | 
				
			||||||
        }, 50);
 | 
					        }, 50);
 | 
				
			||||||
@ -214,6 +182,19 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Search the page element, initialize it, and wait until it's ready for the transition to trigger on scroll.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected initializePage(): void {
 | 
				
			||||||
 | 
					        if (!this.collapsedHeader.parentElement) {
 | 
				
			||||||
 | 
					            throw new Error('[collapsible-header] Couldn\'t get page');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Find element and prepare classes.
 | 
				
			||||||
 | 
					        this.page = this.collapsedHeader.parentElement;
 | 
				
			||||||
 | 
					        this.page.classList.add('collapsible-header-page');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Search the collapsed header element, initialize it, and wait until it's ready for the transition to trigger on scroll.
 | 
					     * Search the collapsed header element, initialize it, and wait until it's ready for the transition to trigger on scroll.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
@ -251,6 +232,8 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
 | 
				
			|||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.listenEvents();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Initialize from tabs.
 | 
					        // Initialize from tabs.
 | 
				
			||||||
        const tabs = CoreComponentsRegistry.resolve(this.page.querySelector('core-tabs-outlet'), CoreTabsOutletComponent);
 | 
					        const tabs = CoreComponentsRegistry.resolve(this.page.querySelector('core-tabs-outlet'), CoreTabsOutletComponent);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -282,21 +265,22 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Initialize a floating title to mimic transitioning the title from one state to the other.
 | 
					     * Initialize a floating title to mimic transitioning the title from one state to the other.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected initializeFloatingTitle(): void {
 | 
					    protected async initializeFloatingTitle(): Promise<void> {
 | 
				
			||||||
        if (!this.page || !this.expandedHeader) {
 | 
					        if (!this.page || !this.expandedHeader) {
 | 
				
			||||||
            throw new Error('[collapsible-header] Couldn\'t create floating title');
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (!CoreDom.isElementVisible(this.expandedHeader)) {
 | 
					 | 
				
			||||||
            this.initPending = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.initPending = false;
 | 
					        if (this.loadingFloatingTitle) {
 | 
				
			||||||
 | 
					            // Already calculating, return.
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.loadingFloatingTitle = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.visiblePromise = CoreDom.waitToBeVisible(this.expandedHeader);
 | 
				
			||||||
 | 
					        await this.visiblePromise;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.page.classList.remove('collapsible-header-page-is-active');
 | 
					        this.page.classList.remove('collapsible-header-page-is-active');
 | 
				
			||||||
        CoreUtils.nextTick();
 | 
					        await CoreUtils.nextTick();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Add floating title and measure initial position.
 | 
					        // Add floating title and measure initial position.
 | 
				
			||||||
        const collapsedHeaderTitle = this.collapsedHeader.querySelector('h1') as HTMLHeadingElement;
 | 
					        const collapsedHeaderTitle = this.collapsedHeader.querySelector('h1') as HTMLHeadingElement;
 | 
				
			||||||
@ -368,6 +352,8 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
 | 
				
			|||||||
        this.collapsedFontStyles = collapsedFontStyles;
 | 
					        this.collapsedFontStyles = collapsedFontStyles;
 | 
				
			||||||
        this.expandedFontStyles = expandedFontStyles;
 | 
					        this.expandedFontStyles = expandedFontStyles;
 | 
				
			||||||
        this.expandedHeaderHeight = expandedHeaderHeight;
 | 
					        this.expandedHeaderHeight = expandedHeaderHeight;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.loadingFloatingTitle = false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 | 
				
			|||||||
@ -55,8 +55,9 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
 | 
				
			|||||||
    protected resizeListener?: CoreEventObserver;
 | 
					    protected resizeListener?: CoreEventObserver;
 | 
				
			||||||
    protected darkModeListener?: Subscription;
 | 
					    protected darkModeListener?: Subscription;
 | 
				
			||||||
    protected domPromise?: CoreCancellablePromise<void>;
 | 
					    protected domPromise?: CoreCancellablePromise<void>;
 | 
				
			||||||
 | 
					    protected visiblePromise?: CoreCancellablePromise<void>;
 | 
				
			||||||
    protected uniqueId: string;
 | 
					    protected uniqueId: string;
 | 
				
			||||||
    protected calcPending = false;
 | 
					    protected loadingHeight = false;
 | 
				
			||||||
    protected pageDidEnterListener?: EventListener;
 | 
					    protected pageDidEnterListener?: EventListener;
 | 
				
			||||||
    protected page?: HTMLElement;
 | 
					    protected page?: HTMLElement;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -99,9 +100,7 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
 | 
				
			|||||||
        this.page?.addEventListener(
 | 
					        this.page?.addEventListener(
 | 
				
			||||||
            'ionViewDidEnter',
 | 
					            'ionViewDidEnter',
 | 
				
			||||||
            this.pageDidEnterListener = () => {
 | 
					            this.pageDidEnterListener = () => {
 | 
				
			||||||
                if (this.calcPending) {
 | 
					                this.calculateHeight();
 | 
				
			||||||
                    this.calculateHeight();
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -143,20 +142,20 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
 | 
				
			|||||||
     * Calculate the height and check if we need to display show more or not.
 | 
					     * Calculate the height and check if we need to display show more or not.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected async calculateHeight(): Promise<void> {
 | 
					    protected async calculateHeight(): Promise<void> {
 | 
				
			||||||
 | 
					        if (this.loadingHeight) {
 | 
				
			||||||
 | 
					            // Already calculating, return.
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.loadingHeight = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.visiblePromise = CoreDom.waitToBeVisible(this.element);
 | 
				
			||||||
 | 
					        await this.visiblePromise;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Remove max-height (if any) to calculate the real height.
 | 
					        // Remove max-height (if any) to calculate the real height.
 | 
				
			||||||
        this.element.classList.add('collapsible-loading-height');
 | 
					        this.element.classList.add('collapsible-loading-height');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await this.waitFormatTextsRendered();
 | 
					        await this.waitFormatTextsRendered();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!this.element.clientHeight) {
 | 
					 | 
				
			||||||
            this.calcPending = true;
 | 
					 | 
				
			||||||
            this.element.classList.remove('collapsible-loading-height');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.calcPending = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.expandedHeight = this.element.getBoundingClientRect().height;
 | 
					        this.expandedHeight = this.element.getBoundingClientRect().height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Restore the max height now.
 | 
					        // Restore the max height now.
 | 
				
			||||||
@ -167,6 +166,7 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
 | 
				
			|||||||
        this.setExpandButtonEnabled(enable);
 | 
					        this.setExpandButtonEnabled(enable);
 | 
				
			||||||
        this.setGradientColor();
 | 
					        this.setGradientColor();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.loadingHeight = false;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -298,6 +298,7 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
 | 
				
			|||||||
        this.resizeListener?.off();
 | 
					        this.resizeListener?.off();
 | 
				
			||||||
        this.darkModeListener?.unsubscribe();
 | 
					        this.darkModeListener?.unsubscribe();
 | 
				
			||||||
        this.domPromise?.cancel();
 | 
					        this.domPromise?.cancel();
 | 
				
			||||||
 | 
					        this.visiblePromise?.cancel();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (this.page && this.pageDidEnterListener) {
 | 
					        if (this.page && this.pageDidEnterListener) {
 | 
				
			||||||
            this.page.removeEventListener('ionViewDidEnter', this.pageDidEnterListener);
 | 
					            this.page.removeEventListener('ionViewDidEnter', this.pageDidEnterListener);
 | 
				
			||||||
 | 
				
			|||||||
@ -256,7 +256,8 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
 | 
				
			|||||||
            button.classList.add('hidden');
 | 
					            button.classList.add('hidden');
 | 
				
			||||||
            button.setAttribute('aria-label', label);
 | 
					            button.setAttribute('aria-label', label);
 | 
				
			||||||
            // Add an ion-icon item to apply the right styles, but the ion-icon component won't be executed.
 | 
					            // Add an ion-icon item to apply the right styles, but the ion-icon component won't be executed.
 | 
				
			||||||
            button.innerHTML = '<ion-icon name="fas-search" aria-hidden="true" src="assets/fonts/font-awesome/solid/search.svg">\
 | 
					            button.innerHTML = '<ion-icon name="fas-expand-alt" aria-hidden="true" \
 | 
				
			||||||
 | 
					                src="assets/fonts/font-awesome/solid/expand-alt.svg">\
 | 
				
			||||||
            </ion-icon>';
 | 
					            </ion-icon>';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            button.addEventListener('click', (e: Event) => {
 | 
					            button.addEventListener('click', (e: Event) => {
 | 
				
			||||||
 | 
				
			|||||||
@ -11,9 +11,6 @@
 | 
				
			|||||||
            </h1>
 | 
					            </h1>
 | 
				
			||||||
        </ion-title>
 | 
					        </ion-title>
 | 
				
			||||||
        <ion-buttons slot="end">
 | 
					        <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-user-menu-button></core-user-menu-button>
 | 
					            <core-user-menu-button></core-user-menu-button>
 | 
				
			||||||
        </ion-buttons>
 | 
					        </ion-buttons>
 | 
				
			||||||
    </ion-toolbar>
 | 
					    </ion-toolbar>
 | 
				
			||||||
 | 
				
			|||||||
@ -19,7 +19,6 @@ import { CoreCourseBlock } from '@features/course/services/course';
 | 
				
			|||||||
import { CoreCoursesDashboard, CoreCoursesDashboardProvider } from '@features/courses/services/dashboard';
 | 
					import { CoreCoursesDashboard, CoreCoursesDashboardProvider } from '@features/courses/services/dashboard';
 | 
				
			||||||
import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager';
 | 
					import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager';
 | 
				
			||||||
import { IonRefresher } from '@ionic/angular';
 | 
					import { IonRefresher } from '@ionic/angular';
 | 
				
			||||||
import { CoreNavigator } from '@services/navigator';
 | 
					 | 
				
			||||||
import { CoreSites } from '@services/sites';
 | 
					import { CoreSites } from '@services/sites';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					import { CoreUtils } from '@services/utils/utils';
 | 
				
			||||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
					import { CoreEventObserver, CoreEvents } from '@singletons/events';
 | 
				
			||||||
@ -38,7 +37,6 @@ export class CoreCoursesMyCoursesPage implements OnInit, OnDestroy {
 | 
				
			|||||||
    @ViewChild(CoreBlockComponent) block!: CoreBlockComponent;
 | 
					    @ViewChild(CoreBlockComponent) block!: CoreBlockComponent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    siteName = '';
 | 
					    siteName = '';
 | 
				
			||||||
    searchEnabled = false;
 | 
					 | 
				
			||||||
    downloadCoursesEnabled = false;
 | 
					    downloadCoursesEnabled = false;
 | 
				
			||||||
    userId: number;
 | 
					    userId: number;
 | 
				
			||||||
    loadedBlock?: Partial<CoreCourseBlock>;
 | 
					    loadedBlock?: Partial<CoreCourseBlock>;
 | 
				
			||||||
@ -50,7 +48,6 @@ export class CoreCoursesMyCoursesPage implements OnInit, OnDestroy {
 | 
				
			|||||||
    constructor() {
 | 
					    constructor() {
 | 
				
			||||||
        // Refresh the enabled flags if site is updated.
 | 
					        // Refresh the enabled flags if site is updated.
 | 
				
			||||||
        this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
 | 
					        this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
 | 
				
			||||||
            this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite();
 | 
					 | 
				
			||||||
            this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
 | 
					            this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
 | 
				
			||||||
            this.loadSiteName();
 | 
					            this.loadSiteName();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -63,7 +60,6 @@ export class CoreCoursesMyCoursesPage implements OnInit, OnDestroy {
 | 
				
			|||||||
     * @inheritdoc
 | 
					     * @inheritdoc
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    ngOnInit(): void {
 | 
					    ngOnInit(): void {
 | 
				
			||||||
        this.searchEnabled = !CoreCourses.isSearchCoursesDisabledInSite();
 | 
					 | 
				
			||||||
        this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
 | 
					        this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const deepLinkManager = new CoreMainMenuDeepLinkManager();
 | 
					        const deepLinkManager = new CoreMainMenuDeepLinkManager();
 | 
				
			||||||
@ -122,13 +118,6 @@ export class CoreCoursesMyCoursesPage implements OnInit, OnDestroy {
 | 
				
			|||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Go to search courses.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    async openSearch(): Promise<void> {
 | 
					 | 
				
			||||||
        CoreNavigator.navigateToSitePath('/list', { params : { mode: 'search' } });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Refresh the data.
 | 
					     * Refresh the data.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
 | 
				
			|||||||
@ -11,10 +11,6 @@ ion-item ion-icon {
 | 
				
			|||||||
    @include margin-horizontal(null, var(--margin-end));
 | 
					    @include margin-horizontal(null, var(--margin-end));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
core-spacer ::ng-deep .item {
 | 
					core-spacer {
 | 
				
			||||||
    border-radius: var(--medium-radius);
 | 
					    --spacer-horizontal: 10px;
 | 
				
			||||||
    --horizontal-margin: 10px;
 | 
					 | 
				
			||||||
    margin-left: var(--horizontal-margin);
 | 
					 | 
				
			||||||
    margin-right: var(--horizontal-margin);
 | 
					 | 
				
			||||||
    width: auto;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -9,7 +9,6 @@
 | 
				
			|||||||
    "allparticipants": "All participants",
 | 
					    "allparticipants": "All participants",
 | 
				
			||||||
    "answer": "Answer",
 | 
					    "answer": "Answer",
 | 
				
			||||||
    "answered": "Answered",
 | 
					    "answered": "Answered",
 | 
				
			||||||
    "applyfilters": "Apply filters",
 | 
					 | 
				
			||||||
    "areyousure": "Are you sure?",
 | 
					    "areyousure": "Are you sure?",
 | 
				
			||||||
    "back": "Back",
 | 
					    "back": "Back",
 | 
				
			||||||
    "browser": "Browser",
 | 
					    "browser": "Browser",
 | 
				
			||||||
 | 
				
			|||||||
@ -1020,10 +1020,16 @@ ion-select::part(icon) {
 | 
				
			|||||||
    opacity: 1;
 | 
					    opacity: 1;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ion-select-popover ion-item.core-select-option-title {
 | 
					ion-select-popover {
 | 
				
			||||||
    cursor: pointer;
 | 
					    ion-item.core-select-option-border-bottom {
 | 
				
			||||||
    ion-radio {
 | 
					        border-bottom: 1px solid var(--stroke);
 | 
				
			||||||
        display: none;
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ion-item.core-select-option-title {
 | 
				
			||||||
 | 
					        cursor: pointer;
 | 
				
			||||||
 | 
					        ion-radio {
 | 
				
			||||||
 | 
					            display: none;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1091,20 +1097,28 @@ ion-chip {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ion-searchbar {
 | 
					ion-searchbar {
 | 
				
			||||||
    .searchbar-search-icon.ios {
 | 
					    height: var(--height) !important;
 | 
				
			||||||
        top: 4px;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    .searchbar-search-icon.md {
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .searchbar-input,
 | 
					    .searchbar-input-container {
 | 
				
			||||||
    .sc-ion-searchbar-md.searchbar-input,
 | 
					        color: var(--color) !important;
 | 
				
			||||||
    .sc-ion-searchbar-ios.searchbar-input {
 | 
					        height: var(--height) !important;
 | 
				
			||||||
        @include padding-horizontal(48px);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .searchbar-input {
 | 
					    .searchbar-input {
 | 
				
			||||||
        @include padding-horizontal(48px);
 | 
					        height: var(--height) !important;
 | 
				
			||||||
 | 
					        border: 1px solid var(--border-color) !important;
 | 
				
			||||||
 | 
					        box-shadow: none !important;
 | 
				
			||||||
 | 
					        border-radius: var(--border-radius) !important;
 | 
				
			||||||
 | 
					        background: var(--background) !important;
 | 
				
			||||||
 | 
					        @include padding-horizontal(var(--height) !important);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .searchbar-search-icon {
 | 
				
			||||||
 | 
					        @include position(null, null, null, calc(var(--height) / 4) !important);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    button {
 | 
				
			||||||
 | 
					        @include position(null, 0 !important, null, null);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -190,23 +190,16 @@
 | 
				
			|||||||
    --ion-searchbar-background: var(--ion-background-color);
 | 
					    --ion-searchbar-background: var(--ion-background-color);
 | 
				
			||||||
    --ion-searchbar-border-color: var(--core-input-stroke);
 | 
					    --ion-searchbar-border-color: var(--core-input-stroke);
 | 
				
			||||||
    --ion-searchbar-border-radius: var(--core-input-radius);
 | 
					    --ion-searchbar-border-radius: var(--core-input-radius);
 | 
				
			||||||
 | 
					    --ion-searchbar-height: var(--a11y-min-target-size);
 | 
				
			||||||
    --ion-searchbar-color: var(--text-color);
 | 
					    --ion-searchbar-color: var(--text-color);
 | 
				
			||||||
    --ion-searchbar-icon-color: var(--core-input-stroke);
 | 
					    --ion-searchbar-icon-color: var(--core-input-stroke);
 | 
				
			||||||
    ion-searchbar {
 | 
					    ion-searchbar {
 | 
				
			||||||
        --background: var(--ion-searchbar-background);
 | 
					        --background: var(--ion-searchbar-background);
 | 
				
			||||||
        --border-color: var(--ion-searchbar-border-color);
 | 
					        --border-color: var(--ion-searchbar-border-color);
 | 
				
			||||||
 | 
					        --color: var(--ion-searchbar-color);
 | 
				
			||||||
 | 
					        --border-radius: var(--ion-searchbar-border-radius);
 | 
				
			||||||
        --icon-color: var(--ion-searchbar-icon-color);
 | 
					        --icon-color: var(--ion-searchbar-icon-color);
 | 
				
			||||||
 | 
					        --height: var(--ion-searchbar-height);
 | 
				
			||||||
        .searchbar-input-container {
 | 
					 | 
				
			||||||
            color: var(--ion-searchbar-color) !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .searchbar-input {
 | 
					 | 
				
			||||||
            height: var(--a11y-min-target-size);
 | 
					 | 
				
			||||||
            border: 1px solid var(--ion-searchbar-border-color);
 | 
					 | 
				
			||||||
            box-shadow: none;
 | 
					 | 
				
			||||||
            border-radius: var(--ion-searchbar-border-radius);
 | 
					 | 
				
			||||||
            background: var(--ion-searchbar-background) !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    --core-search-box-background: var(--ion-background-color);
 | 
					    --core-search-box-background: var(--ion-background-color);
 | 
				
			||||||
@ -308,10 +301,8 @@
 | 
				
			|||||||
        --min-height: var(--item-divider-min-height);
 | 
					        --min-height: var(--item-divider-min-height);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    --spacer-background: var(--light);
 | 
					    --spacer-vertical: 8px;
 | 
				
			||||||
    core-spacer {
 | 
					    --spacer-color: var(--gray-300);
 | 
				
			||||||
        --item-divider-background: var(--spacer-background);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ion-note {
 | 
					    ion-note {
 | 
				
			||||||
        --color: var(--subdued-text-color);
 | 
					        --color: var(--subdued-text-color);
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user