diff --git a/scripts/langindex.json b/scripts/langindex.json
index a8d9ddc2b..db1744aee 100644
--- a/scripts/langindex.json
+++ b/scripts/langindex.json
@@ -40,11 +40,13 @@
"addon.block_learningplans.pluginname": "block_lp",
"addon.block_myoverview.all": "block_myoverview",
"addon.block_myoverview.allincludinghidden": "block_myoverview",
- "addon.block_myoverview.favourites": "block_myoverview",
+ "addon.block_myoverview.aria:hiddencourses": "block_myoverview",
+ "addon.block_myoverview.card": "block_myoverview",
+ "addon.block_myoverview.favouritesonly": "local_moodlemobileapp",
"addon.block_myoverview.future": "block_myoverview",
- "addon.block_myoverview.hiddencourses": "block_myoverview",
"addon.block_myoverview.inprogress": "block_myoverview",
"addon.block_myoverview.lastaccessed": "block_myoverview",
+ "addon.block_myoverview.list": "block_myoverview",
"addon.block_myoverview.nocourses": "block_myoverview",
"addon.block_myoverview.past": "block_myoverview",
"addon.block_myoverview.pluginname": "block_myoverview",
@@ -1400,6 +1402,7 @@
"core.allparticipants": "moodle",
"core.answer": "moodle",
"core.answered": "quiz",
+ "core.applyfilters": "user",
"core.areyousure": "moodle",
"core.back": "moodle",
"core.block.blocks": "moodle",
diff --git a/src/addons/block/myoverview/components/components.module.ts b/src/addons/block/myoverview/components/components.module.ts
index c1829bd61..49e53a2f4 100644
--- a/src/addons/block/myoverview/components/components.module.ts
+++ b/src/addons/block/myoverview/components/components.module.ts
@@ -17,10 +17,12 @@ import { NgModule } from '@angular/core';
import { CoreSharedModule } from '@/core/shared.module';
import { CoreCoursesComponentsModule } from '@features/courses/components/components.module';
import { AddonBlockMyOverviewComponent } from './myoverview/myoverview';
+import { AddonBlockMyOverviewFilterOptionsComponent } from './filteroptions/filteroptions';
@NgModule({
declarations: [
AddonBlockMyOverviewComponent,
+ AddonBlockMyOverviewFilterOptionsComponent,
],
imports: [
CoreSharedModule,
diff --git a/src/addons/block/myoverview/components/filteroptions/filteroptions.html b/src/addons/block/myoverview/components/filteroptions/filteroptions.html
new file mode 100644
index 000000000..414d1fbd4
--- /dev/null
+++ b/src/addons/block/myoverview/components/filteroptions/filteroptions.html
@@ -0,0 +1,56 @@
+
+
+ {{ 'core.courses.filtermycourses' | translate }}
+
+
+
+
+
+
+
+
+
+
+
+ {{'addon.block_myoverview.allincludinghidden' | translate}}
+
+
+
+ {{'addon.block_myoverview.inprogress' | translate}}
+
+
+
+ {{'addon.block_myoverview.future' | translate}}
+
+
+
+ {{'addon.block_myoverview.past' | translate}}
+
+
+
+
+
+
+
+ {{customOption.name}}
+
+
+
+
+
+
+ {{ 'addon.block_myoverview.favouritesonly' | translate }}
+
+
+
+
+ {{ 'addon.block_myoverview.aria:hiddencourses' | translate }}
+
+
+
+
+
+
+ {{ 'core.applyfilters' | translate }}
+
+
diff --git a/src/addons/block/myoverview/components/filteroptions/filteroptions.ts b/src/addons/block/myoverview/components/filteroptions/filteroptions.ts
new file mode 100644
index 000000000..93b9d753a
--- /dev/null
+++ b/src/addons/block/myoverview/components/filteroptions/filteroptions.ts
@@ -0,0 +1,44 @@
+// (C) Copyright 2015 Moodle Pty Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { Component, Input } from '@angular/core';
+import { ModalController } from '@singletons';
+import { AddonBlockMyOverviewFilterOptions } from '../myoverview/myoverview';
+
+/**
+ * Component to render a my overview filter options.
+ */
+@Component({
+ selector: 'addon-block-myoverview-filter-options',
+ templateUrl: 'filteroptions.html',
+})
+export class AddonBlockMyOverviewFilterOptionsComponent {
+
+ @Input() options!: AddonBlockMyOverviewFilterOptions;
+
+ /**
+ * Appl filters.
+ */
+ apply(): void {
+ ModalController.dismiss(this.options);
+ }
+
+ /**
+ * Close modal.
+ */
+ closeModal(): void {
+ ModalController.dismiss();
+ }
+
+}
diff --git a/src/addons/block/myoverview/components/myoverview/addon-block-myoverview.html b/src/addons/block/myoverview/components/myoverview/addon-block-myoverview.html
index 422f1b3e7..121ae723b 100644
--- a/src/addons/block/myoverview/components/myoverview/addon-block-myoverview.html
+++ b/src/addons/block/myoverview/components/myoverview/addon-block-myoverview.html
@@ -4,105 +4,81 @@
-
1 && !showFilter" class="core-button-spinner">
- 1" class="core-button-spinner">
+
-
+
-
- {{prefetchCoursesData[timeSelectorFilter].badge}}
+
+ {{prefetchCoursesData.badge}}
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ 'addon.block_myoverview.allincludinghidden' | translate }}
-
-
- {{ 'addon.block_myoverview.all' | translate }}
-
-
- {{ 'addon.block_myoverview.inprogress' | translate }}
-
-
- {{ 'addon.block_myoverview.future' | translate }}
-
-
- {{ 'addon.block_myoverview.past' | translate }}
-
-
-
- {{ customOption.name }}
-
-
-
- {{ 'addon.block_myoverview.favourites' | translate }}
-
-
- {{ 'addon.block_myoverview.hiddencourses' | translate }}
-
-
-
-
-
-
-
- {{ 'addon.block_myoverview.'+layout | translate }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{'addon.block_myoverview.title' | translate}}
-
-
-
-
-
-
-
+
+ {{'addon.block_myoverview.shortname' | translate}}
+
+
+ {{'addon.block_myoverview.lastaccessed' | translate}}
+
+
+
+ 1">
+
+
+
+ {{ 'addon.block_myoverview.'+layout | translate }}
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+ [showDownload]="downloadCourseEnabled && downloadEnabled" [layout]="layouts.selected">
diff --git a/src/addons/block/myoverview/components/myoverview/myoverview.ts b/src/addons/block/myoverview/components/myoverview/myoverview.ts
index 0f3cf060f..a3970be95 100644
--- a/src/addons/block/myoverview/components/myoverview/myoverview.ts
+++ b/src/addons/block/myoverview/components/myoverview/myoverview.ts
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { Component, OnInit, Input, OnDestroy, ViewChild, OnChanges, SimpleChange } from '@angular/core';
-import { IonSearchbar } from '@ionic/angular';
+import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChange } from '@angular/core';
+import { ModalOptions } from '@ionic/core';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
@@ -27,8 +27,10 @@ import { CoreUtils } from '@services/utils/utils';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreTextUtils } from '@services/utils/text';
import { AddonCourseCompletion } from '@/addons/coursecompletion/services/coursecompletion';
+import { AddonBlockMyOverviewFilterOptionsComponent } from '../filteroptions/filteroptions';
+import { IonSearchbar } from '@ionic/angular';
-const FILTER_PRIORITY = ['all', 'allincludinghidden', 'inprogress', 'future', 'past', 'favourite', 'hidden', 'custom'];
+const FILTER_PRIORITY: AddonBlockMyOverviewTimeFilters[] = ['all', 'inprogress', 'future', 'past'];
/**
* Component to render a my overview block.
@@ -39,107 +41,62 @@ const FILTER_PRIORITY = ['all', 'allincludinghidden', 'inprogress', 'future', 'p
})
export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implements OnInit, OnChanges, OnDestroy {
- @ViewChild('searchbar') searchbar?: IonSearchbar;
@Input() downloadEnabled = false;
- courses = {
- filter: '',
- all: [],
- allincludinghidden: [],
- past: [],
- inprogress: [],
- future: [],
- favourite: [],
- hidden: [],
- custom: [], // Leave it empty to avoid download all those courses.
- };
-
- customFilter: {
- name: string;
- value: string;
- }[] = [];
-
- timeSelectorFilter = 'inprogress';
- sort = 'fullname';
- currentSite?: CoreSite;
filteredCourses: CoreEnrolledCourseDataWithOptions[] = [];
- prefetchCoursesData: Record = {
- all: {
- icon: '',
- statusTranslatable: 'core.loading',
- status: '',
- loading: true,
- },
- allincludinghidden: {
- icon: '',
- statusTranslatable: 'core.loading',
- status: '',
- loading: true,
- },
- inprogress: {
- icon: '',
- statusTranslatable: 'core.loading',
- status: '',
- loading: true,
- },
- past: {
- icon: '',
- statusTranslatable: 'core.loading',
- status: '',
- loading: true,
- },
- future: {
- icon: '',
- statusTranslatable: 'core.loading',
- status: '',
- loading: true,
- },
- favourite: {
- icon: '',
- statusTranslatable: 'core.loading',
- status: '',
- loading: true,
- },
- hidden: {
- icon: '',
- statusTranslatable: 'core.loading',
- status: '',
- loading: true,
- },
- custom: {
- icon: '',
- statusTranslatable: '',
- status: '',
- loading: false,
- }, // Leave it empty to avoid download all those courses.
+
+ prefetchCoursesData: CorePrefetchStatusInfo = {
+ icon: '',
+ statusTranslatable: 'core.loading',
+ status: '',
+ loading: true,
};
- showFilters = { // Options are show, disabled, hidden.
- all: 'show',
- allincludinghidden: 'show',
- past: 'show',
- inprogress: 'show',
- future: 'show',
- favourite: 'show',
- hidden: 'show',
- custom: 'hidden',
- };
-
- showFilter = false;
- showTimeSelectorFilter = false;
- showSortFilter = false;
downloadCourseEnabled = false;
downloadCoursesEnabled = false;
- showSortByShortName = false;
- layouts: AddonBlockMyOverviewLayouts[] = [];
- selectedLayout: AddonBlockMyOverviewLayouts = 'card';
+ filters: AddonBlockMyOverviewFilterOptions = {
+ enabled: false,
+ show: { // Options are visible, disabled, hidden.
+ all: true,
+ past: true,
+ inprogress: true,
+ future: true,
+ favourite: true,
+ hidden: true,
+ custom: false,
+ },
+ timeFilterSelected: 'inprogress',
+ favouriteSelected: false,
+ hiddenSelected: false,
+ customFilters: [],
+ count: 0,
+ };
+ filterModalOptions: ModalOptions = {
+ component: AddonBlockMyOverviewFilterOptionsComponent,
+ };
+
+ layouts: AddonBlockMyOverviewLayoutOptions = {
+ options: [],
+ selected: 'card',
+ };
+
+ sort: AddonBlockMyOverviewSortOptions = {
+ shortnameEnabled: false,
+ selected: 'fullname',
+ enabled: false,
+ };
+
+ textFilter = '';
+ hasCourses = false;
+
+ protected currentSite!: CoreSite;
+ protected allCourses: CoreEnrolledCourseDataWithOptions[] = [];
protected prefetchIconsInitialized = false;
protected isDestroyed = false;
protected coursesObserver?: CoreEventObserver;
protected updateSiteObserver?: CoreEventObserver;
- protected courseIds: number[] = [];
protected fetchContentDefaultError = 'Error getting my overview data.';
constructor() {
@@ -147,7 +104,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
}
/**
- * Component being initialized.
+ * @inheritdoc
*/
async ngOnInit(): Promise {
// Refresh the enabled flags if enabled.
@@ -164,10 +121,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
this.coursesObserver = CoreEvents.on(
CoreCoursesProvider.EVENT_MY_COURSES_UPDATED,
(data) => {
-
- if (this.shouldRefreshOnUpdatedEvent(data)) {
- this.refreshCourseList();
- }
+ this.refreshCourseList(data);
},
CoreSites.getCurrentSiteId(),
);
@@ -175,25 +129,62 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
this.currentSite = CoreSites.getRequiredCurrentSite();
const promises: Promise[] = [];
- promises.push(this.currentSite.getLocalSiteConfig('AddonBlockMyOverviewSort', this.sort).then((value) => {
- this.sort = value;
- return;
- }));
promises.push(this.currentSite.getLocalSiteConfig(
- 'AddonBlockMyOverviewFilter',
- this.timeSelectorFilter,
+ 'AddonBlockMyOverviewSort',
+ this.sort.selected,
).then((value) => {
- this.timeSelectorFilter = value;
+ this.sort.selected = value;
return;
}));
promises.push(this.currentSite.getLocalSiteConfig(
'AddonBlockMyOverviewLayout',
- this.selectedLayout,
+ this.layouts.selected,
).then((value) => {
- this.selectedLayout = value;
+ this.layouts.selected = value;
+
+ return;
+ }));
+
+ // Wait for the migration.
+ await this.currentSite.getLocalSiteConfig(
+ 'AddonBlockMyOverviewFilter',
+ 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) => {
+ this.filters.favouriteSelected = value == 1;
+
+ return;
+ }));
+
+ promises.push(this.currentSite.getLocalSiteConfig(
+ 'AddonBlockMyOverviewHiddenFilter',
+ this.filters.hiddenSelected ? 1 : 0,
+ ).then((value) => {
+ this.filters.hiddenSelected = value == 1;
return;
}));
@@ -204,7 +195,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
}
/**
- * Detect changes on input properties.
+ * @inheritdoc
*/
ngOnChanges(changes: {[name: string]: SimpleChange}): void {
if (changes.downloadEnabled && !changes.downloadEnabled.previousValue && this.downloadEnabled && this.loaded) {
@@ -214,21 +205,35 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
}
/**
- * Perform the invalidate content function.
- *
- * @return Resolved when done.
+ * @inheritdoc
*/
protected async invalidateContent(): Promise {
+ const courseIds = this.allCourses.map((course) => course.id);
+
+ await this.invalidateCourses(courseIds);
+ }
+
+ /**
+ * Helper function to invalidate only selected courses.
+ *
+ * @param courseIds Course Id array.
+ * @return Promise resolved when done.
+ */
+ protected async invalidateCourses(courseIds: number[]): Promise {
const promises: Promise[] = [];
// Invalidate course completion data.
promises.push(CoreCourses.invalidateUserCourses().finally(() =>
- CoreUtils.allPromises(this.courseIds.map((courseId) =>
+ CoreUtils.allPromises(courseIds.map((courseId) =>
AddonCourseCompletion.invalidateCourseCompletion(courseId)))));
- promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions());
- if (this.courseIds.length > 0) {
- promises.push(CoreCourses.invalidateCoursesByField('ids', this.courseIds.join(',')));
+ if (courseIds.length == 1) {
+ promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions(courseIds[0]));
+ } else {
+ promises.push(CoreCourseOptionsDelegate.clearAndInvalidateCoursesOptions());
+ }
+ if (courseIds.length > 0) {
+ promises.push(CoreCourses.invalidateCoursesByField('ids', courseIds.join(',')));
}
await CoreUtils.allPromises(promises).finally(() => {
@@ -242,101 +247,88 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
protected async fetchContent(refresh?: boolean): Promise {
const config = this.block.configsRecord;
- this.loadLayouts(config?.layouts?.value.split(','));
-
const showCategories = config?.displaycategories?.value == '1';
- const courses = await CoreCoursesHelper.getUserCoursesWithOptions(this.sort, undefined, undefined, showCategories, {
- readingStrategy: refresh ? CoreSitesReadingStrategy.PREFER_NETWORK : undefined,
- });
+ this.allCourses = await CoreCoursesHelper.getUserCoursesWithOptions(
+ this.sort.selected,
+ undefined,
+ undefined,
+ showCategories,
+ {
+ readingStrategy: refresh ? CoreSitesReadingStrategy.PREFER_NETWORK : undefined,
+ },
+ );
+
+ this.hasCourses = this.allCourses.length > 0;
+
+ this.loadSort();
+ this.loadLayouts(config?.layouts?.value.split(','));
+ this.loadFilters(config);
+ }
+
+ /**
+ * Load sort.
+ */
+ protected loadSort(): void {
+ const sampleCourse = this.allCourses[0];
// Check to show sort by short name only if the text is visible.
- if (courses.length > 0) {
- const sampleCourse = courses[0];
- this.showSortByShortName = !!sampleCourse.displayname && !!sampleCourse.shortname &&
- sampleCourse.fullname != sampleCourse.displayname;
- }
+ this.sort.shortnameEnabled = !!sampleCourse?.displayname && !!sampleCourse?.shortname &&
+ sampleCourse?.fullname != sampleCourse?.displayname;
// Rollback to sort by full name if user is sorting by short name then Moodle web change the config.
- if (!this.showSortByShortName && this.sort === 'shortname') {
- this.switchSort('fullname');
+ if (!this.sort.shortnameEnabled && this.sort.selected === 'shortname') {
+ this.saveSort('fullname');
}
- this.courseIds = courses.map((course) => course.id);
+ this.sort.enabled = sampleCourse?.lastaccess !== undefined;
+ }
- this.showSortFilter = courses.length > 0 && typeof courses[0].lastaccess != 'undefined';
+ /**
+ * Load filters.
+ *
+ * @param config Block configuration.
+ */
+ protected loadFilters(
+ config?: Record,
+ ): void {
+ this.textFilter = '';
- this.initCourseFilters(courses);
+ const sampleCourse = this.allCourses[0];
- this.courses.filter = '';
- this.showFilter = false;
+ // Do not show hidden if config it's not present (before 3.8) but if hidden is enabled.
+ this.filters.show.hidden =
+ config?.displaygroupingallincludinghidden?.value == '1' ||
+ sampleCourse.hidden !== undefined && (!config || config.displaygroupinghidden?.value == '1');
- this.showFilters.all = this.getShowFilterValue(
- !config || config.displaygroupingall?.value == '1',
- this.courses.all.length === 0,
- );
- // Do not show allincludinghiddenif config it's not present (before 3.8).
- this.showFilters.allincludinghidden =
- this.getShowFilterValue(
- config?.displaygroupingallincludinghidden?.value == '1',
- this.courses.allincludinghidden.length === 0,
- );
+ this.filters.show.all = !config || config.displaygroupingall?.value == '1';
+ this.filters.show.inprogress = !config || config.displaygroupinginprogress?.value == '1';
+ this.filters.show.past = !config || config.displaygroupingpast?.value == '1';
+ this.filters.show.future = !config || config.displaygroupingfuture?.value == '1';
- this.showFilters.inprogress = this.getShowFilterValue(
- !config || config.displaygroupinginprogress?.value == '1',
- this.courses.inprogress.length === 0,
- );
- this.showFilters.past = this.getShowFilterValue(
- !config || config.displaygroupingpast?.value == '1',
- this.courses.past.length === 0,
- );
- this.showFilters.future = this.getShowFilterValue(
- !config || config.displaygroupingfuture?.value == '1',
- this.courses.future.length === 0,
- );
+ this.filters.show.favourite = sampleCourse.isfavourite !== undefined &&
+ (!config || config.displaygroupingstarred?.value == '1' || config.displaygroupingfavourites?.value == '1');
- this.showTimeSelectorFilter = courses.length > 0 && (this.courses.past.length > 0 || this.courses.future.length > 0 ||
- typeof courses[0].enddate != 'undefined');
+ this.filters.show.custom = config?.displaygroupingcustomfield?.value == '1' && !!config?.customfieldsexport?.value;
- this.showFilters.hidden = this.getShowFilterValue(
- this.showTimeSelectorFilter && typeof courses[0].hidden != 'undefined' &&
- (!config || config.displaygroupinghidden?.value == '1'),
- this.courses.hidden.length === 0,
- );
+ this.filters.customFilters = this.filters.show.custom
+ ? CoreTextUtils.parseJSON(config?.customfieldsexport?.value || '[]', [])
+ : [];
- this.showFilters.favourite = this.getShowFilterValue(
- this.showTimeSelectorFilter && typeof courses[0].isfavourite != 'undefined' &&
- (!config || config.displaygroupingstarred?.value == '1' || config.displaygroupingfavourites?.value == '1'),
- this.courses.favourite.length === 0,
- );
+ // Check if any selector is shown and not disabled.
+ this.filters.enabled = Object.keys(this.filters.show).some((key) => this.filters.show[key]);
- this.showFilters.custom = this.getShowFilterValue(
- this.showTimeSelectorFilter && config?.displaygroupingcustomfield?.value == '1' && !!config?.customfieldsexport?.value,
- false,
- );
- if (this.showFilters.custom == 'show') {
- this.customFilter = CoreTextUtils.parseJSON(config?.customfieldsexport?.value || '[]', []);
- } else {
- this.customFilter = [];
+ if (!this.filters.enabled) {
+ // All filters disabled, display all the courses.
+ this.filters.show.all = true;
+ this.saveFilters('all');
}
- if (this.showTimeSelectorFilter) {
- // Check if any selector is shown and not disabled.
- this.showTimeSelectorFilter = Object.keys(this.showFilters).some((key) => this.showFilters[key] == 'show');
+ this.filterModalOptions.componentProps = {
+ options: Object.assign({}, this.filters),
+ };
- if (!this.showTimeSelectorFilter) {
- // All filters disabled, display all the courses.
- this.showFilters.all = 'show';
- }
- }
-
- if (!this.showTimeSelectorFilter) {
- // No selector, display all the courses.
- this.timeSelectorFilter = 'all';
- }
- this.setCourseFilter(this.timeSelectorFilter);
-
- this.initPrefetchCoursesIcons();
+ this.filterCourses();
}
/**
@@ -344,8 +336,14 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
*
* @param layouts Config available layouts.
*/
- protected loadLayouts(layouts: string[] = []): void {
- this.layouts = [];
+ protected loadLayouts(layouts?: string[]): void {
+ this.layouts.options = [];
+
+ if (layouts === undefined) {
+ this.layouts.options = ['card', 'list'];
+
+ return;
+ }
layouts.forEach((layout) => {
if (layout == '') {
@@ -353,95 +351,71 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
}
const validLayout: AddonBlockMyOverviewLayouts = layout == 'summary' ? 'list' : layout as AddonBlockMyOverviewLayouts;
- if (!this.layouts.includes(validLayout)) {
- this.layouts.push(validLayout);
+ if (!this.layouts.options.includes(validLayout)) {
+ this.layouts.options.push(validLayout);
}
});
// If no layout is available use card.
- if (this.layouts.length == 0) {
- this.layouts = ['card'];
+ if (this.layouts.options.length == 0) {
+ this.layouts.options = ['card'];
}
- if (!this.layouts.includes(this.selectedLayout)) {
- this.selectedLayout = this.layouts[0];
+ if (!this.layouts.options.includes(this.layouts.selected)) {
+ this.layouts.selected = this.layouts.options[0];
}
}
/**
- * Helper function to help with filter values.
- *
- * @param showCondition If true, filter will be shown.
- * @param disabledCondition If true, and showCondition is also met, it will be shown as disabled.
- * @return show / disabled / hidden value.
- */
- protected getShowFilterValue(showCondition: boolean, disabledCondition: boolean): string {
- return showCondition ? (disabledCondition ? 'disabled' : 'show') : 'hidden';
- }
-
- /**
- * Whether list should be refreshed based on a EVENT_MY_COURSES_UPDATED event.
+ * Refresh course list based on a EVENT_MY_COURSES_UPDATED event.
*
* @param data Event data.
- * @return Whether to refresh.
+ * @return Promise resolved when done.
*/
- protected shouldRefreshOnUpdatedEvent(data: CoreCoursesMyCoursesUpdatedEventData): boolean {
+ protected async refreshCourseList(data: CoreCoursesMyCoursesUpdatedEventData): Promise {
if (data.action == CoreCoursesProvider.ACTION_ENROL) {
// Always update if user enrolled in a course.
- return true;
+ return await this.refreshContent();
}
+ const course = this.allCourses.find((course) => course.id == data.courseId);
if (data.action == CoreCoursesProvider.ACTION_STATE_CHANGED) {
- // Update list when course state changes (favourite, hidden).
- return true;
+ if (!course) {
+ // Not found, use WS update.
+ return await this.refreshContent();
+ }
+
+ if (data.state == CoreCoursesProvider.STATE_FAVOURITE) {
+ course.isfavourite = !!data.value;
+ }
+
+ if (data.state == CoreCoursesProvider.STATE_HIDDEN) {
+ course.hidden = !!data.value;
+ }
+
+ await this.invalidateCourses([course.id]);
+ await this.filterCourses();
}
if (data.action == CoreCoursesProvider.ACTION_VIEW && data.courseId != CoreSites.getCurrentSiteHomeId()) {
- // User viewed a course. If it isn't the most recent accessed course, update the list.
- let recentAccessedCourse: CoreEnrolledCourseDataWithOptions | undefined;
- if (this.sort == 'lastaccess') {
- recentAccessedCourse = this.courses.allincludinghidden[0];
- } else {
- recentAccessedCourse = Array.from(this.courses.allincludinghidden)
- .sort((a, b) => (b.lastaccess || 0) - (a.lastaccess || 0))[0];
+ if (!course) {
+ // Not found, use WS update.
+ return await this.refreshContent();
}
- if (recentAccessedCourse && data.courseId != recentAccessedCourse.id) {
- return true;
- }
- }
+ course.lastaccess = CoreTimeUtils.timestamp();
- return false;
- }
-
- /**
- * The filter has changed.
- *
- * @param Received Event.
- */
- filterChanged(event: Event): void {
- const target = event?.target || null;
-
- const newValue = target?.value.trim().toLowerCase();
- if (!newValue || this.courses.allincludinghidden.length <= 0) {
- this.filteredCourses = this.courses.allincludinghidden;
- } else {
- // Use displayname if available, or fullname if not.
- if (this.courses.allincludinghidden.length > 0 &&
- typeof this.courses.allincludinghidden[0].displayname != 'undefined') {
- this.filteredCourses = this.courses.allincludinghidden.filter((course) =>
- course.displayname && course.displayname.toLowerCase().indexOf(newValue) > -1);
- } else {
- this.filteredCourses = this.courses.allincludinghidden.filter((course) =>
- course.fullname.toLowerCase().indexOf(newValue) > -1);
- }
+ await this.invalidateCourses([course.id]);
+ await this.filterCourses();
}
}
/**
* Initialize the prefetch icon for selected courses.
+ *
+ * @return Promise resolved when done.
*/
- protected initPrefetchCoursesIcons(): void {
+ protected async initPrefetchCoursesIcons(): Promise {
if (this.prefetchIconsInitialized || !this.downloadEnabled) {
// Already initialized.
return;
@@ -449,10 +423,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
this.prefetchIconsInitialized = true;
- Object.keys(this.prefetchCoursesData).forEach(async (filter) => {
- this.prefetchCoursesData[filter] =
- await CoreCourseHelper.initPrefetchCoursesIcons(this.courses[filter], this.prefetchCoursesData[filter]);
- });
+ this.prefetchCoursesData = await CoreCourseHelper.initPrefetchCoursesIcons(this.filteredCourses, this.prefetchCoursesData);
}
/**
@@ -461,206 +432,218 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
* @return Promise resolved when done.
*/
async prefetchCourses(): Promise {
- const selected = this.timeSelectorFilter;
- const initialIcon = this.prefetchCoursesData[selected].icon;
+ const initialIcon = this.prefetchCoursesData.icon;
try {
- await CoreCourseHelper.prefetchCourses(this.courses[selected], this.prefetchCoursesData[selected]);
+ await CoreCourseHelper.prefetchCourses(this.filteredCourses, this.prefetchCoursesData);
} catch (error) {
if (!this.isDestroyed) {
CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
- this.prefetchCoursesData[selected].icon = initialIcon;
+ this.prefetchCoursesData.icon = initialIcon;
}
}
}
/**
- * Refresh the list of courses.
+ * Text filter changed.
*
- * @return Promise resolved when done.
+ * @param target Searchbar element.
*/
- protected async refreshCourseList(): Promise {
- CoreEvents.trigger(CoreCoursesProvider.EVENT_MY_COURSES_REFRESHED);
+ filterTextChanged(target: IonSearchbar): void {
+ this.textFilter = target.value || '';
- await this.loadContent(true);
- }
-
- /**
- * The layout have changed.
- *
- * @param layout New layout.
- */
- layoutChanged(layout: AddonBlockMyOverviewLayouts): void {
- this.selectedLayout = layout;
-
- this.currentSite?.setLocalSiteConfig('AddonBlockMyOverviewLayout', layout);
+ this.filterCourses();
}
/**
* Set selected courses filter.
- *
- * @param filter Filter name to set.
*/
- protected async setCourseFilter(filter: string): Promise {
- this.timeSelectorFilter = filter;
+ protected async filterCourses(): Promise {
+ this.filters.count = 0;
- if (this.showFilters.custom == 'show' && filter.startsWith('custom-') &&
- typeof this.customFilter[filter.substr(7)] != 'undefined') {
+ let timeFilter = this.filters.timeFilterSelected;
- const filterName = this.block.configsRecord!.customfiltergrouping.value;
- const filterValue = this.customFilter[filter.substr(7)].value;
+ // 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;
+
+ const customFilterName = this.block.configsRecord?.customfiltergrouping.value;
+ const customFilterValue = this.filters.customSelected;
+ if (customFilterName && this.filters.show.custom && customFilterValue !== undefined) {
+ this.filters.count++;
this.loaded = false;
try {
- const courses = await CoreCourses.getEnrolledCoursesByCustomField(filterName, filterValue);
+ 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);
- this.filteredCourses = this.courses.allincludinghidden.filter((allCourse) =>
- courseIds.indexOf(allCourse.id) !== -1);
+ this.filteredCourses = this.filteredCourses.filter((course) => courseIds.includes(course.id));
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, this.fetchContentDefaultError);
} finally {
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 && course.enddate < today) || course.completed) {
+ // Courses that have already ended.
+ include = timeFilter == 'past';
+ } else if (course.startdate && course.startdate > today) {
+ // Courses that have not started yet.
+ include = timeFilter == 'future';
+ } else {
+ // Courses still in progress.
+ include = timeFilter == 'inprogress';
+ }
+ }
+
+ if (onlyFavourite) {
+ include = include && !!course.isfavourite;
+ }
+
+ if (!showHidden) {
+ include = include && !course.hidden;
+ }
+
+ return include;
+ });
+
+ // Text filter.
+ const value = this.textFilter.trim().toLowerCase();
+ if (value != '' && this.filteredCourses.length > 0) {
+ // Use displayname if available, or fullname if not.
+ if (this.filteredCourses[0].displayname !== undefined) {
+ this.filteredCourses = this.filteredCourses.filter((course) =>
+ course.displayname && course.displayname.toLowerCase().indexOf(value) > -1);
+ } else {
+ this.filteredCourses = this.filteredCourses.filter((course) =>
+ course.fullname.toLowerCase().indexOf(value) > -1);
+ }
+ }
+
+ this.sortCourses(this.sort.selected);
+
+ // Refresh prefetch data (if enabled).
+ this.prefetchIconsInitialized = false;
+ this.initPrefetchCoursesIcons();
+ }
+
+ /**
+ * Sort courses
+ *
+ * @param sort Sort by value.
+ */
+ sortCourses(sort: string): void {
+ if (!this.sort.enabled) {
return;
}
- // Only save the filter if not a custom one.
- this.currentSite?.setLocalSiteConfig('AddonBlockMyOverviewFilter', filter);
-
- if (this.showFilters[filter] == 'show') {
- this.filteredCourses = this.courses[filter];
- } else {
- const activeFilter = FILTER_PRIORITY.find((name) => this.showFilters[name] == 'show');
-
- if (activeFilter) {
- this.setCourseFilter(activeFilter);
- }
- }
- }
-
- /**
- * The time selector courses filter have changed.
- *
- * @param filter New filter
- */
- timeSelectorChanged(filter: string): void {
- this.timeSelectorFilter = filter;
- this.setCourseFilter(this.timeSelectorFilter);
- }
-
- /**
- * Init courses filters.
- *
- * @param courses Courses to filter.
- */
- initCourseFilters(courses: CoreEnrolledCourseDataWithOptions[]): void {
- this.courses.allincludinghidden = courses;
-
- if (this.showSortFilter) {
- if (this.sort == 'lastaccess') {
- courses.sort((a, b) => (b.lastaccess || 0) - (a.lastaccess || 0));
- } else if (this.sort == 'fullname') {
- courses.sort((a, b) => {
- const compareA = a.fullname.toLowerCase();
- const compareB = b.fullname.toLowerCase();
-
- return compareA.localeCompare(compareB);
- });
- } else if (this.sort == 'shortname') {
- courses.sort((a, b) => {
- const compareA = a.shortname.toLowerCase();
- const compareB = b.shortname.toLowerCase();
-
- return compareA.localeCompare(compareB);
- });
- }
+ if (this.sort.selected != sort) {
+ this.saveSort(sort);
}
- this.courses.all = [];
- this.courses.past = [];
- this.courses.inprogress = [];
- this.courses.future = [];
- this.courses.favourite = [];
- this.courses.hidden = [];
+ if (this.sort.selected == 'lastaccess') {
+ this.filteredCourses.sort((a, b) => (b.lastaccess || 0) - (a.lastaccess || 0));
+ } else if (this.sort.selected == 'fullname') {
+ this.filteredCourses.sort((a, b) => {
+ const compareA = a.fullname.toLowerCase();
+ const compareB = b.fullname.toLowerCase();
- const today = CoreTimeUtils.timestamp();
- courses.forEach((course) => {
- if (course.hidden) {
- this.courses.hidden.push(course);
- } else {
- this.courses.all.push(course);
+ return compareA.localeCompare(compareB);
+ });
+ } else if (this.sort.selected == 'shortname') {
+ this.filteredCourses.sort((a, b) => {
+ const compareA = a.shortname.toLowerCase();
+ const compareB = b.shortname.toLowerCase();
- if ((course.enddate && course.enddate < today) || course.completed) {
- // Courses that have already ended.
- this.courses.past.push(course);
- } else if (course.startdate && course.startdate > today) {
- // Courses that have not started yet.
- this.courses.future.push(course);
- } else {
- // Courses still in progress.
- this.courses.inprogress.push(course);
- }
-
- if (course.isfavourite) {
- this.courses.favourite.push(course);
- }
- }
- });
-
- this.setCourseFilter(this.timeSelectorFilter);
- }
-
- /**
- * The selected courses sort filter have changed.
- *
- * @param sort New sorting.
- */
- switchSort(sort: string): void {
- this.sort = sort;
- this.currentSite?.setLocalSiteConfig('AddonBlockMyOverviewSort', this.sort);
- this.initCourseFilters(this.courses.allincludinghidden);
- }
-
- /**
- * Show or hide the filter.
- */
- switchFilter(): void {
- this.showFilter = !this.showFilter;
- this.courses.filter = '';
-
- if (this.showFilter) {
- this.filteredCourses = this.courses.allincludinghidden;
- } else {
- this.setCourseFilter(this.timeSelectorFilter);
- }
- }
-
- /**
- * Popover closed after clicking switch filter.
- */
- switchFilterClosed(): void {
- if (this.showFilter) {
- setTimeout(() => {
- this.searchbar?.setFocus();
+ return compareA.localeCompare(compareB);
});
}
}
/**
- * If switch button that enables the filter input is shown or not.
+ * Saves filters value.
*
- * @return If switch button that enables the filter input is shown or not.
+ * @param timeFilter New time filter.
+ * @return Promise resolved when done.
*/
- showFilterSwitchButton(): boolean {
- return this.loaded && this.courses.allincludinghidden && this.courses.allincludinghidden.length > 5;
+ async saveFilters(timeFilter: AddonBlockMyOverviewTimeFilters): Promise {
+ this.filters.timeFilterSelected = 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),
+ ]);
}
/**
- * Component being destroyed.
+ * Saves layout value.
+ *
+ * @param layout New layout.
+ * @return Promise resolved when done.
+ */
+ async saveLayout(layout: AddonBlockMyOverviewLayouts): Promise {
+ this.layouts.selected = layout;
+
+ await this.currentSite.setLocalSiteConfig('AddonBlockMyOverviewLayout', this.layouts.selected);
+ }
+
+ /**
+ * Saves sort courses value.
+ *
+ * @param sort New sorting.
+ * @return Promise resolved when done.
+ */
+ async saveSort(sort: string): Promise {
+ this.sort.selected = sort;
+
+ await this.currentSite.setLocalSiteConfig('AddonBlockMyOverviewSort', this.sort.selected);
+ }
+
+ /**
+ * Opens display Options modal.
+ *
+ * @return Promise resolved when done.
+ */
+ filterOptionsChanged(modalData: AddonBlockMyOverviewFilterOptions): void {
+ this.filters = modalData;
+ this.saveFilters(this.filters.timeFilterSelected);
+ this.filterCourses();
+ }
+
+ /**
+ * @inheritdoc
*/
ngOnDestroy(): void {
this.isDestroyed = true;
@@ -671,3 +654,37 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
}
type AddonBlockMyOverviewLayouts = 'card'|'list';
+type AddonBlockMyOverviewTimeFilters = 'all'|'inprogress'|'future'|'past';
+
+export type AddonBlockMyOverviewFilterOptions = {
+ enabled: boolean;
+ show: {
+ all: boolean;
+ inprogress: boolean;
+ future: boolean;
+ past: boolean;
+ favourite: boolean;
+ hidden: boolean;
+ custom: boolean;
+ };
+ timeFilterSelected: AddonBlockMyOverviewTimeFilters;
+ favouriteSelected: boolean;
+ hiddenSelected: boolean;
+ customFilters: {
+ name: string;
+ value: string;
+ }[];
+ customSelected?: string;
+ count: number;
+};
+
+type AddonBlockMyOverviewLayoutOptions = {
+ options: AddonBlockMyOverviewLayouts[];
+ selected: AddonBlockMyOverviewLayouts;
+};
+
+type AddonBlockMyOverviewSortOptions = {
+ shortnameEnabled: boolean;
+ selected: string;
+ enabled: boolean;
+};
diff --git a/src/addons/block/myoverview/lang.json b/src/addons/block/myoverview/lang.json
index 19cbb6144..9b0831599 100644
--- a/src/addons/block/myoverview/lang.json
+++ b/src/addons/block/myoverview/lang.json
@@ -1,10 +1,10 @@
{
"all": "All (except removed from view)",
"allincludinghidden": "All",
+ "aria:hiddencourses": "Show courses removed from view",
"card": "Card",
- "favourites": "Starred",
+ "favouritesonly": "Show starred courses only",
"future": "Future",
- "hiddencourses": "Removed from view",
"inprogress": "In progress",
"lastaccessed": "Last accessed",
"list": "List",
diff --git a/src/core/features/courses/components/course-list-item/core-courses-course-list-item.html b/src/core/features/courses/components/course-list-item/core-courses-course-list-item.html
index 772b58c0b..73b78fa5a 100644
--- a/src/core/features/courses/components/course-list-item/core-courses-course-list-item.html
+++ b/src/core/features/courses/components/course-list-item/core-courses-course-list-item.html
@@ -9,7 +9,7 @@
-
+
@@ -32,7 +32,7 @@
-
+
diff --git a/src/core/lang.json b/src/core/lang.json
index 99182daa6..793e0da74 100644
--- a/src/core/lang.json
+++ b/src/core/lang.json
@@ -7,6 +7,7 @@
"all": "All",
"allgroups": "All groups",
"allparticipants": "All participants",
+ "applyfilters": "Apply filters",
"answer": "Answer",
"answered": "Answered",
"areyousure": "Are you sure?",