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,32 +434,21 @@ 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;
|
||||||
|
|
||||||
|
if (this.filters.show.custom && timeFilter.startsWith('custom-')) {
|
||||||
|
// Custom filter.
|
||||||
const customFilterName = this.block.configsRecord?.customfiltergrouping.value;
|
const customFilterName = this.block.configsRecord?.customfiltergrouping.value;
|
||||||
const customFilterValue = this.filters.customSelected;
|
const customFilterValue = this.filters.customFilters[timeFilter.substring(7)]?.value;
|
||||||
if (customFilterName && this.filters.show.custom && customFilterValue !== undefined) {
|
|
||||||
this.filters.count++;
|
|
||||||
|
|
||||||
|
if (customFilterName !== undefined && customFilterValue !== undefined) {
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
try {
|
try {
|
||||||
const courses = await CoreCourses.getEnrolledCoursesByCustomField(customFilterName, customFilterValue);
|
const courses = await CoreCourses.getEnrolledCoursesByCustomField(customFilterName, customFilterValue);
|
||||||
|
|
||||||
|
// Get the courses information from allincludinghidden to get the max info about the course.
|
||||||
const courseIds = courses.map((course) => course.id);
|
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));
|
||||||
|
@ -507,46 +458,42 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
|
||||||
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 {
|
} else {
|
||||||
// Courses still in progress.
|
// Filter is not active, take the first active or all. Custom is never saved.
|
||||||
include = timeFilter == 'inprogress';
|
if (!this.filters.show[timeFilter]) {
|
||||||
}
|
timeFilter = FILTER_PRIORITY.find((name) => this.filters.show[name]) || 'all';
|
||||||
}
|
}
|
||||||
|
this.saveFilters(timeFilter);
|
||||||
|
|
||||||
if (onlyFavourite) {
|
// Update today date.
|
||||||
include = include && !!course.isfavourite;
|
this.today = Date.now();
|
||||||
}
|
|
||||||
|
|
||||||
if (!showHidden) {
|
// Apply filters.
|
||||||
include = include && !course.hidden;
|
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,7 +27,10 @@
|
||||||
@include position(0, 0, null, null);
|
@include position(0, 0, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-header ion-toolbar h1 {
|
ion-header ion-toolbar ion-title {
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
h1 {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -47,6 +50,7 @@
|
||||||
@include margin-horizontal(6px, null);
|
@include margin-horizontal(6px, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:host-context(.ios) {
|
:host-context(.ios) {
|
||||||
|
|
|
@ -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,11 +1020,17 @@ ion-select::part(icon) {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-select-popover ion-item.core-select-option-title {
|
ion-select-popover {
|
||||||
|
ion-item.core-select-option-border-bottom {
|
||||||
|
border-bottom: 1px solid var(--stroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-item.core-select-option-title {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
ion-radio {
|
ion-radio {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-badge {
|
ion-badge {
|
||||||
|
@ -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…
Reference in New Issue