MOBILE-3659 course: Implement section selector

main
Dani Palou 2021-01-18 16:16:18 +01:00
parent 6d4e37431a
commit 01e9e20014
7 changed files with 181 additions and 18 deletions

View File

@ -23,6 +23,7 @@ import { CoreCourseFormatComponent } from './format/format';
import { CoreCourseModuleComponent } from './module/module';
import { CoreCourseModuleCompletionComponent } from './module-completion/module-completion';
import { CoreCourseModuleDescriptionComponent } from './module-description/module-description';
import { CoreCourseSectionSelectorComponent } from './section-selector/section-selector';
import { CoreCourseUnsupportedModuleComponent } from './unsupported-module/unsupported-module';
@NgModule({
@ -31,6 +32,7 @@ import { CoreCourseUnsupportedModuleComponent } from './unsupported-module/unsup
CoreCourseModuleComponent,
CoreCourseModuleCompletionComponent,
CoreCourseModuleDescriptionComponent,
CoreCourseSectionSelectorComponent,
CoreCourseUnsupportedModuleComponent,
],
imports: [
@ -45,6 +47,7 @@ import { CoreCourseUnsupportedModuleComponent } from './unsupported-module/unsup
CoreCourseModuleComponent,
CoreCourseModuleCompletionComponent,
CoreCourseModuleDescriptionComponent,
CoreCourseSectionSelectorComponent,
CoreCourseUnsupportedModuleComponent,
],
})

View File

@ -2,7 +2,7 @@
<core-navbar-buttons slot="end" *ngIf="loaded">
<core-context-menu>
<core-context-menu-item [hidden]="!displaySectionSelector || !sections || !sections.length" [priority]="500"
[content]="'core.course.sections' | translate" (action)="showSectionSelector($event)" iconAction="menu">
[content]="'core.course.sections' | translate" (action)="showSectionSelector()" iconAction="menu">
</core-context-menu-item>
</core-context-menu>
</core-navbar-buttons>
@ -19,7 +19,7 @@
<div *ngIf="displaySectionSelector && sections && hasSeveralSections"
class="ion-text-wrap clearfix ion-justify-content-between core-button-selector-row"
[class.core-section-download]="downloadEnabled">
<ion-button class="ion-float-start" (click)="showSectionSelector($event)" color="light" aria-haspopup="true"
<ion-button class="ion-float-start" (click)="showSectionSelector()" color="light" aria-haspopup="true"
class="core-button-select button-no-uppercase" [attr.aria-expanded]="sectionSelectorExpanded"
id="core-course-section-button" expand="block"> <!-- @todo: attr.aria-label? -->
<core-icon name="fas-folder" slot="start"></core-icon>

View File

@ -46,6 +46,8 @@ import { CoreUtils } from '@services/utils/utils';
import { CoreBlockCourseBlocksComponent } from '@features/block/components/course-blocks/course-blocks';
import { CoreCourseSectionFormatted } from '@features/course/services/course-helper';
import { CoreCourseModuleStatusChangedData } from '../module/module';
import { ModalController } from '@singletons';
import { CoreCourseSectionSelectorComponent } from '../section-selector/section-selector';
/**
* Component to display course contents using a certain format. If the format isn't found, use default one.
@ -330,29 +332,30 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
/**
* Display the section selector modal.
*
* @param event Event.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
showSectionSelector(event?: MouseEvent): void {
async showSectionSelector(): Promise<void> {
if (this.sectionSelectorExpanded) {
return;
}
// @todo this.sectionSelectorExpanded = true;
// const modal = this.modalCtrl.create('CoreCourseSectionSelectorPage',
// {course: this.course, sections: this.sections, selected: this.selectedSection});
// modal.onDidDismiss((newSection) => {
// if (newSection) {
// this.sectionChanged(newSection);
// }
this.sectionSelectorExpanded = true;
// this.sectionSelectorExpanded = false;
// });
const modal = await ModalController.instance.create({
component: CoreCourseSectionSelectorComponent,
componentProps: {
course: this.course,
sections: this.sections,
selected: this.selectedSection,
},
});
await modal.present();
// modal.present({
// ev: event
// });
const result = await modal.onWillDismiss();
this.sectionSelectorExpanded = false;
if (result?.data) {
this.sectionChanged(result?.data);
}
}
/**

View File

@ -0,0 +1,39 @@
<ion-header>
<ion-toolbar>
<ion-title>{{ 'core.course.sections' | translate }}</ion-title>
<ion-buttons slot="end">
<ion-button (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
<ion-icon slot="icon-only" name="fas-times"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list id="core-course-section-selector" role="menu">
<ng-container *ngFor="let section of sections">
<ion-item *ngIf="!section.hiddenbynumsections && section.id != stealthModulesSectionId" class="ion-text-wrap"
(click)="selectSection(section)" [class.core-primary-selected-item]="selected?.id == section.id"
[class.item-dimmed]="section.visible === 0 || section.uservisible === false" detail="false" role="menuitem"
[attr.aria-hidden]="section.uservisible === false">
<ion-icon name="fas-folder" slot="start"></ion-icon>
<ion-label>
<h2><core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id">
</core-format-text></h2>
<core-progress-bar *ngIf="section.progress >= 0" [progress]="section.progress"></core-progress-bar>
<ion-badge color="secondary" *ngIf="section.visible === 0 && section.uservisible !== false" class="ion-text-wrap">
{{ 'core.course.hiddenfromstudents' | translate }}
</ion-badge>
<ion-badge color="secondary" *ngIf="section.visible === 0 && section.uservisible === false" class="ion-text-wrap">
{{ 'core.notavailable' | translate }}
</ion-badge>
<ion-badge color="secondary" *ngIf="section.availabilityinfo" class="ion-text-wrap">
<core-format-text [text]=" section.availabilityinfo" contextLevel="course" [contextInstanceId]="course?.id">
</core-format-text>
</ion-badge>
</ion-label>
</ion-item>
</ng-container>
</ion-list>
</ion-content>

View File

@ -0,0 +1,17 @@
:host {
core-progress-bar {
.core-progress-text {
line-height: 24px;
position: absolute;
top: -8px;
right: 10px;
}
progress {
margin: 8px 0 4px 0;
}
}
ion-badge {
text-align: start;
}
}

View File

@ -0,0 +1,98 @@
// (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, OnInit } from '@angular/core';
import { CoreCourseSectionFormatted } from '@features/course/services/course-helper';
import { CoreCourseProvider } from '@features/course/services/course';
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
import { CoreUtils } from '@services/utils/utils';
import { ModalController } from '@singletons';
/**
* Component to display course section selector in a modal.
*/
@Component({
selector: 'core-course-section-selector',
templateUrl: 'section-selector.html',
})
export class CoreCourseSectionSelectorComponent implements OnInit {
@Input() sections?: SectionWithProgress[];
@Input() selected?: CoreCourseSectionFormatted;
@Input() course?: CoreCourseAnyCourseData;
stealthModulesSectionId = CoreCourseProvider.STEALTH_MODULES_SECTION_ID;
/**
* Component being initialized.
*/
ngOnInit(): void {
if (!this.course || !this.sections || !this.course.enablecompletion || !('courseformatoptions' in this.course) ||
!this.course.courseformatoptions) {
return;
}
const formatOptions = CoreUtils.instance.objectToKeyValueMap(this.course.courseformatoptions, 'name', 'value');
if (!formatOptions || formatOptions.coursedisplay != 1 || formatOptions.completionusertracked === false) {
return;
}
this.sections.forEach((section) => {
let complete = 0;
let total = 0;
section.modules.forEach((module) => {
if (!module.uservisible || module.completiondata === undefined || module.completiondata.tracking === undefined ||
module.completiondata.tracking <= CoreCourseProvider.COMPLETION_TRACKING_NONE) {
return;
}
total++;
if (module.completiondata.state == CoreCourseProvider.COMPLETION_COMPLETE ||
module.completiondata.state == CoreCourseProvider.COMPLETION_COMPLETE_PASS) {
complete++;
}
});
if (total > 0) {
section.progress = complete / total * 100;
}
});
}
/**
* Close the modal.
*/
closeModal(): void {
ModalController.instance.dismiss();
}
/**
* Select a section.
*
* @param section Selected section object.
*/
selectSection(section: SectionWithProgress): void {
if (section.uservisible !== false) {
ModalController.instance.dismiss(section);
}
}
}
type SectionWithProgress = CoreCourseSectionFormatted & {
progress?: number;
};

View File

@ -169,6 +169,9 @@ ion-toolbar {
.item.core-danger-item {
--border-color: var(--ion-color-danger);
}
.item-dimmed {
opacity: 0.71;
}
// Extra text colors.
.text-gray {