MOBILE-3806 myoverview: Add layout selector

main
Pau Ferrer Ocaña 2021-11-08 16:19:08 +01:00
parent 29f6d6cd39
commit 69584e361c
3 changed files with 122 additions and 54 deletions

View File

@ -4,20 +4,19 @@
</ion-label> </ion-label>
<div slot="end" class="flex-row"> <div slot="end" class="flex-row">
<!-- Download all courses. --> <!-- Download all courses. -->
<div *ngIf="downloadCoursesEnabled && downloadEnabled && filteredCourses.length > 1 && !showFilter" <div *ngIf="downloadCoursesEnabled && downloadEnabled && filteredCourses.length > 1 && !showFilter" class="core-button-spinner">
class="core-button-spinner"> <ion-button *ngIf="!prefetchCoursesData[timeSelectorFilter].loading" fill="clear" color="dark" (click)="prefetchCourses()"
<ion-button *ngIf="!prefetchCoursesData[selectedFilter].loading" fill="clear" color="dark" (click)="prefetchCourses()"
[attr.aria-label]="'core.courses.downloadcourses' | translate"> [attr.aria-label]="'core.courses.downloadcourses' | translate">
<ion-icon [name]="prefetchCoursesData[selectedFilter].icon" slot="icon-only" aria-hidden="true"> <ion-icon [name]="prefetchCoursesData[timeSelectorFilter].icon" slot="icon-only" aria-hidden="true">
</ion-icon> </ion-icon>
</ion-button> </ion-button>
<ion-badge class="core-course-download-courses-progress" *ngIf="prefetchCoursesData[selectedFilter].badge" <ion-badge class="core-course-download-courses-progress" *ngIf="prefetchCoursesData[timeSelectorFilter].badge"
role="progressbar" [attr.aria-valuemax]="prefetchCoursesData[selectedFilter].total" role="progressbar" [attr.aria-valuemax]="prefetchCoursesData[timeSelectorFilter].total"
[attr.aria-valuenow]="prefetchCoursesData[selectedFilter].count" [attr.aria-valuenow]="prefetchCoursesData[timeSelectorFilter].count"
[attr.aria-valuetext]="prefetchCoursesData[selectedFilter].badgeA11yText"> [attr.aria-valuetext]="prefetchCoursesData[timeSelectorFilter].badgeA11yText">
{{prefetchCoursesData[selectedFilter].badge}} {{prefetchCoursesData[timeSelectorFilter].badge}}
</ion-badge> </ion-badge>
<ion-spinner *ngIf="prefetchCoursesData[selectedFilter].loading" [attr.aria-label]="'core.loading' | translate"> <ion-spinner *ngIf="prefetchCoursesData[timeSelectorFilter].loading" [attr.aria-label]="'core.loading' | translate">
</ion-spinner> </ion-spinner>
</div> </div>
<core-context-menu> <core-context-menu>
@ -40,9 +39,9 @@
</div> </div>
</ion-item-divider> </ion-item-divider>
<core-loading [hideUntil]="loaded" [fullscreen]="false"> <core-loading [hideUntil]="loaded" [fullscreen]="false">
<div class="safe-area-padding-horizontal" [hidden]="showFilter || !showSelectorFilter"> <div class="safe-area-padding-horizontal" [hidden]="showFilter || !showTimeSelectorFilter">
<!-- "Time" selector. --> <!-- "Time" selector. -->
<core-combobox [label]="'core.show' | translate" [selection]="selectedFilter" (onChange)="selectedChanged($event)"> <core-combobox [label]="'core.show' | translate" [selection]="timeSelectorFilter" (onChange)="timeSelectorChanged($event)">
<ion-select-option class="ion-text-wrap" value="allincludinghidden" *ngIf="showFilters.allincludinghidden != 'hidden'"> <ion-select-option class="ion-text-wrap" value="allincludinghidden" *ngIf="showFilters.allincludinghidden != 'hidden'">
{{ 'addon.block_myoverview.allincludinghidden' | translate }} {{ 'addon.block_myoverview.allincludinghidden' | translate }}
</ion-select-option> </ion-select-option>
@ -77,6 +76,16 @@
</core-combobox> </core-combobox>
</div> </div>
<div class="safe-area-padding-horizontal" [hidden]="showFilter || layouts.length <= 1">
<!-- "Layouts" selector. -->
<core-combobox [label]="'core.show' | translate" [selection]="selectedLayout" (onChange)="layoutChanged($event)">
<ng-container *ngFor="let layout of layouts">
<ion-select-option class="ion-text-wrap" [value]="layout">{{ 'addon.block_myoverview.'+layout | translate }}
</ion-select-option>
</ng-container>
</core-combobox>
</div>
<!-- Filter courses. --> <!-- Filter courses. -->
<ion-searchbar #searchbar *ngIf="showFilter" [(ngModel)]="courses.filter" (ionInput)="filterChanged($event)" <ion-searchbar #searchbar *ngIf="showFilter" [(ngModel)]="courses.filter" (ionInput)="filterChanged($event)"
(ionCancel)="filterChanged($event)" [placeholder]="'core.courses.filtermycourses' | translate"> (ionCancel)="filterChanged($event)" [placeholder]="'core.courses.filtermycourses' | translate">
@ -88,7 +97,7 @@
<!-- List of courses. --> <!-- List of courses. -->
<div class="safe-area-padding"> <div class="safe-area-padding">
<ion-grid class="ion-no-padding"> <ion-grid class="ion-no-padding" [class.core-no-grid]="selectedLayout != 'card'">
<ion-row class="ion-no-padding"> <ion-row class="ion-no-padding">
<ion-col *ngFor="let course of filteredCourses" class="ion-no-padding" size="12" size-sm="6" size-md="6" size-lg="4" <ion-col *ngFor="let course of filteredCourses" class="ion-no-padding" size="12" size-sm="6" size-md="6" size-lg="4"
size-xl="3"> size-xl="3">

View File

@ -59,54 +59,54 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
value: string; value: string;
}[] = []; }[] = [];
selectedFilter = 'inprogress'; timeSelectorFilter = 'inprogress';
sort = 'fullname'; sort = 'fullname';
currentSite?: CoreSite; currentSite?: CoreSite;
filteredCourses: CoreEnrolledCourseDataWithOptions[] = []; filteredCourses: CoreEnrolledCourseDataWithOptions[] = [];
prefetchCoursesData = { prefetchCoursesData: Record<string, CorePrefetchStatusInfo> = {
all: <CorePrefetchStatusInfo> { all: {
icon: '', icon: '',
statusTranslatable: 'core.loading', statusTranslatable: 'core.loading',
status: '', status: '',
loading: true, loading: true,
}, },
allincludinghidden: <CorePrefetchStatusInfo> { allincludinghidden: {
icon: '', icon: '',
statusTranslatable: 'core.loading', statusTranslatable: 'core.loading',
status: '', status: '',
loading: true, loading: true,
}, },
inprogress: <CorePrefetchStatusInfo> { inprogress: {
icon: '', icon: '',
statusTranslatable: 'core.loading', statusTranslatable: 'core.loading',
status: '', status: '',
loading: true, loading: true,
}, },
past: <CorePrefetchStatusInfo> { past: {
icon: '', icon: '',
statusTranslatable: 'core.loading', statusTranslatable: 'core.loading',
status: '', status: '',
loading: true, loading: true,
}, },
future: <CorePrefetchStatusInfo> { future: {
icon: '', icon: '',
statusTranslatable: 'core.loading', statusTranslatable: 'core.loading',
status: '', status: '',
loading: true, loading: true,
}, },
favourite: <CorePrefetchStatusInfo> { favourite: {
icon: '', icon: '',
statusTranslatable: 'core.loading', statusTranslatable: 'core.loading',
status: '', status: '',
loading: true, loading: true,
}, },
hidden: <CorePrefetchStatusInfo> { hidden: {
icon: '', icon: '',
statusTranslatable: 'core.loading', statusTranslatable: 'core.loading',
status: '', status: '',
loading: true, loading: true,
}, },
custom: <CorePrefetchStatusInfo> { custom: {
icon: '', icon: '',
statusTranslatable: '', statusTranslatable: '',
status: '', status: '',
@ -126,12 +126,15 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
}; };
showFilter = false; showFilter = false;
showSelectorFilter = false; showTimeSelectorFilter = false;
showSortFilter = false; showSortFilter = false;
downloadCourseEnabled = false; downloadCourseEnabled = false;
downloadCoursesEnabled = false; downloadCoursesEnabled = false;
showSortByShortName = false; showSortByShortName = false;
layouts: AddonBlockMyOverviewLayouts[] = [];
selectedLayout: AddonBlockMyOverviewLayouts = 'card';
protected prefetchIconsInitialized = false; protected prefetchIconsInitialized = false;
protected isDestroyed = false; protected isDestroyed = false;
protected coursesObserver?: CoreEventObserver; protected coursesObserver?: CoreEventObserver;
@ -169,21 +172,31 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
CoreSites.getCurrentSiteId(), CoreSites.getCurrentSiteId(),
); );
this.currentSite = CoreSites.getCurrentSite(); this.currentSite = CoreSites.getRequiredCurrentSite();
const promises: Promise<void>[] = []; const promises: Promise<void>[] = [];
if (this.currentSite) {
promises.push(this.currentSite.getLocalSiteConfig('AddonBlockMyOverviewSort', this.sort).then((value) => { promises.push(this.currentSite.getLocalSiteConfig('AddonBlockMyOverviewSort', this.sort).then((value) => {
this.sort = value; this.sort = value;
return; return;
})); }));
promises.push(this.currentSite.getLocalSiteConfig('AddonBlockMyOverviewFilter', this.selectedFilter).then((value) => { promises.push(this.currentSite.getLocalSiteConfig(
this.selectedFilter = value; 'AddonBlockMyOverviewFilter',
this.timeSelectorFilter,
).then((value) => {
this.timeSelectorFilter = value;
return;
}));
promises.push(this.currentSite.getLocalSiteConfig(
'AddonBlockMyOverviewLayout',
this.selectedLayout,
).then((value) => {
this.selectedLayout = value;
return; return;
})); }));
}
Promise.all(promises).finally(() => { Promise.all(promises).finally(() => {
super.ngOnInit(); super.ngOnInit();
@ -229,6 +242,8 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
protected async fetchContent(refresh?: boolean): Promise<void> { protected async fetchContent(refresh?: boolean): Promise<void> {
const config = this.block.configsRecord; const config = this.block.configsRecord;
this.loadLayouts(config?.layouts?.value.split(','));
const showCategories = config?.displaycategories?.value == '1'; const showCategories = config?.displaycategories?.value == '1';
const courses = await CoreCoursesHelper.getUserCoursesWithOptions(this.sort, undefined, undefined, showCategories, { const courses = await CoreCoursesHelper.getUserCoursesWithOptions(this.sort, undefined, undefined, showCategories, {
@ -280,23 +295,23 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
this.courses.future.length === 0, this.courses.future.length === 0,
); );
this.showSelectorFilter = courses.length > 0 && (this.courses.past.length > 0 || this.courses.future.length > 0 || this.showTimeSelectorFilter = courses.length > 0 && (this.courses.past.length > 0 || this.courses.future.length > 0 ||
typeof courses[0].enddate != 'undefined'); typeof courses[0].enddate != 'undefined');
this.showFilters.hidden = this.getShowFilterValue( this.showFilters.hidden = this.getShowFilterValue(
this.showSelectorFilter && typeof courses[0].hidden != 'undefined' && this.showTimeSelectorFilter && typeof courses[0].hidden != 'undefined' &&
(!config || config.displaygroupinghidden?.value == '1'), (!config || config.displaygroupinghidden?.value == '1'),
this.courses.hidden.length === 0, this.courses.hidden.length === 0,
); );
this.showFilters.favourite = this.getShowFilterValue( this.showFilters.favourite = this.getShowFilterValue(
this.showSelectorFilter && typeof courses[0].isfavourite != 'undefined' && this.showTimeSelectorFilter && typeof courses[0].isfavourite != 'undefined' &&
(!config || config.displaygroupingstarred?.value == '1' || config.displaygroupingfavourites?.value == '1'), (!config || config.displaygroupingstarred?.value == '1' || config.displaygroupingfavourites?.value == '1'),
this.courses.favourite.length === 0, this.courses.favourite.length === 0,
); );
this.showFilters.custom = this.getShowFilterValue( this.showFilters.custom = this.getShowFilterValue(
this.showSelectorFilter && config?.displaygroupingcustomfield?.value == '1' && !!config?.customfieldsexport?.value, this.showTimeSelectorFilter && config?.displaygroupingcustomfield?.value == '1' && !!config?.customfieldsexport?.value,
false, false,
); );
if (this.showFilters.custom == 'show') { if (this.showFilters.custom == 'show') {
@ -305,25 +320,54 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
this.customFilter = []; this.customFilter = [];
} }
if (this.showSelectorFilter) { if (this.showTimeSelectorFilter) {
// Check if any selector is shown and not disabled. // Check if any selector is shown and not disabled.
this.showSelectorFilter = Object.keys(this.showFilters).some((key) => this.showFilters[key] == 'show'); this.showTimeSelectorFilter = Object.keys(this.showFilters).some((key) => this.showFilters[key] == 'show');
if (!this.showSelectorFilter) { if (!this.showTimeSelectorFilter) {
// All filters disabled, display all the courses. // All filters disabled, display all the courses.
this.showFilters.all = 'show'; this.showFilters.all = 'show';
} }
} }
if (!this.showSelectorFilter) { if (!this.showTimeSelectorFilter) {
// No selector, display all the courses. // No selector, display all the courses.
this.selectedFilter = 'all'; this.timeSelectorFilter = 'all';
} }
this.setCourseFilter(this.selectedFilter); this.setCourseFilter(this.timeSelectorFilter);
this.initPrefetchCoursesIcons(); this.initPrefetchCoursesIcons();
} }
/**
* Load block layouts.
*
* @param layouts Config available layouts.
*/
protected loadLayouts(layouts: string[] = []): void {
this.layouts = [];
layouts.forEach((layout) => {
if (layout == '') {
return;
}
const validLayout: AddonBlockMyOverviewLayouts = layout == 'summary' ? 'list' : layout as AddonBlockMyOverviewLayouts;
if (!this.layouts.includes(validLayout)) {
this.layouts.push(validLayout);
}
});
// If no layout is available use card.
if (this.layouts.length == 0) {
this.layouts = ['card'];
}
if (!this.layouts.includes(this.selectedLayout)) {
this.selectedLayout = this.layouts[0];
}
}
/** /**
* Helper function to help with filter values. * Helper function to help with filter values.
* *
@ -417,7 +461,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
* @return Promise resolved when done. * @return Promise resolved when done.
*/ */
async prefetchCourses(): Promise<void> { async prefetchCourses(): Promise<void> {
const selected = this.selectedFilter; const selected = this.timeSelectorFilter;
const initialIcon = this.prefetchCoursesData[selected].icon; const initialIcon = this.prefetchCoursesData[selected].icon;
try { try {
@ -442,13 +486,14 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
} }
/** /**
* The selected courses filter have changed. * The layout have changed.
* *
* @param filter New filter * @param layout New layout.
*/ */
selectedChanged(filter: string): void { layoutChanged(layout: AddonBlockMyOverviewLayouts): void {
this.selectedFilter = filter; this.selectedLayout = layout;
this.setCourseFilter(this.selectedFilter);
this.currentSite?.setLocalSiteConfig('AddonBlockMyOverviewLayout', layout);
} }
/** /**
@ -457,7 +502,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
* @param filter Filter name to set. * @param filter Filter name to set.
*/ */
protected async setCourseFilter(filter: string): Promise<void> { protected async setCourseFilter(filter: string): Promise<void> {
this.selectedFilter = filter; this.timeSelectorFilter = filter;
if (this.showFilters.custom == 'show' && filter.startsWith('custom-') && if (this.showFilters.custom == 'show' && filter.startsWith('custom-') &&
typeof this.customFilter[filter.substr(7)] != 'undefined') { typeof this.customFilter[filter.substr(7)] != 'undefined') {
@ -497,6 +542,16 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
} }
} }
/**
* 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. * Init courses filters.
* *
@ -556,7 +611,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
} }
}); });
this.setCourseFilter(this.selectedFilter); this.setCourseFilter(this.timeSelectorFilter);
} }
/** /**
@ -580,7 +635,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
if (this.showFilter) { if (this.showFilter) {
this.filteredCourses = this.courses.allincludinghidden; this.filteredCourses = this.courses.allincludinghidden;
} else { } else {
this.setCourseFilter(this.selectedFilter); this.setCourseFilter(this.timeSelectorFilter);
} }
} }
@ -614,3 +669,5 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
} }
} }
type AddonBlockMyOverviewLayouts = 'card'|'list';

View File

@ -1,11 +1,13 @@
{ {
"all": "All (except removed from view)", "all": "All (except removed from view)",
"allincludinghidden": "All", "allincludinghidden": "All",
"card": "Card",
"favourites": "Starred", "favourites": "Starred",
"future": "Future", "future": "Future",
"hiddencourses": "Removed from view", "hiddencourses": "Removed from view",
"inprogress": "In progress", "inprogress": "In progress",
"lastaccessed": "Last accessed", "lastaccessed": "Last accessed",
"list": "List",
"nocourses": "No courses", "nocourses": "No courses",
"past": "Past", "past": "Past",
"pluginname": "Course overview", "pluginname": "Course overview",