MOBILE-3659 course: Implement section selector
This commit is contained in:
		
							parent
							
								
									6d4e37431a
								
							
						
					
					
						commit
						01e9e20014
					
				@ -23,6 +23,7 @@ import { CoreCourseFormatComponent } from './format/format';
 | 
				
			|||||||
import { CoreCourseModuleComponent } from './module/module';
 | 
					import { CoreCourseModuleComponent } from './module/module';
 | 
				
			||||||
import { CoreCourseModuleCompletionComponent } from './module-completion/module-completion';
 | 
					import { CoreCourseModuleCompletionComponent } from './module-completion/module-completion';
 | 
				
			||||||
import { CoreCourseModuleDescriptionComponent } from './module-description/module-description';
 | 
					import { CoreCourseModuleDescriptionComponent } from './module-description/module-description';
 | 
				
			||||||
 | 
					import { CoreCourseSectionSelectorComponent } from './section-selector/section-selector';
 | 
				
			||||||
import { CoreCourseUnsupportedModuleComponent } from './unsupported-module/unsupported-module';
 | 
					import { CoreCourseUnsupportedModuleComponent } from './unsupported-module/unsupported-module';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@NgModule({
 | 
					@NgModule({
 | 
				
			||||||
@ -31,6 +32,7 @@ import { CoreCourseUnsupportedModuleComponent } from './unsupported-module/unsup
 | 
				
			|||||||
        CoreCourseModuleComponent,
 | 
					        CoreCourseModuleComponent,
 | 
				
			||||||
        CoreCourseModuleCompletionComponent,
 | 
					        CoreCourseModuleCompletionComponent,
 | 
				
			||||||
        CoreCourseModuleDescriptionComponent,
 | 
					        CoreCourseModuleDescriptionComponent,
 | 
				
			||||||
 | 
					        CoreCourseSectionSelectorComponent,
 | 
				
			||||||
        CoreCourseUnsupportedModuleComponent,
 | 
					        CoreCourseUnsupportedModuleComponent,
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    imports: [
 | 
					    imports: [
 | 
				
			||||||
@ -45,6 +47,7 @@ import { CoreCourseUnsupportedModuleComponent } from './unsupported-module/unsup
 | 
				
			|||||||
        CoreCourseModuleComponent,
 | 
					        CoreCourseModuleComponent,
 | 
				
			||||||
        CoreCourseModuleCompletionComponent,
 | 
					        CoreCourseModuleCompletionComponent,
 | 
				
			||||||
        CoreCourseModuleDescriptionComponent,
 | 
					        CoreCourseModuleDescriptionComponent,
 | 
				
			||||||
 | 
					        CoreCourseSectionSelectorComponent,
 | 
				
			||||||
        CoreCourseUnsupportedModuleComponent,
 | 
					        CoreCourseUnsupportedModuleComponent,
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@
 | 
				
			|||||||
<core-navbar-buttons slot="end" *ngIf="loaded">
 | 
					<core-navbar-buttons slot="end" *ngIf="loaded">
 | 
				
			||||||
    <core-context-menu>
 | 
					    <core-context-menu>
 | 
				
			||||||
        <core-context-menu-item [hidden]="!displaySectionSelector || !sections || !sections.length" [priority]="500"
 | 
					        <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-item>
 | 
				
			||||||
    </core-context-menu>
 | 
					    </core-context-menu>
 | 
				
			||||||
</core-navbar-buttons>
 | 
					</core-navbar-buttons>
 | 
				
			||||||
@ -19,7 +19,7 @@
 | 
				
			|||||||
                <div *ngIf="displaySectionSelector && sections && hasSeveralSections"
 | 
					                <div *ngIf="displaySectionSelector && sections && hasSeveralSections"
 | 
				
			||||||
                    class="ion-text-wrap clearfix ion-justify-content-between core-button-selector-row"
 | 
					                    class="ion-text-wrap clearfix ion-justify-content-between core-button-selector-row"
 | 
				
			||||||
                    [class.core-section-download]="downloadEnabled">
 | 
					                    [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"
 | 
					                        class="core-button-select button-no-uppercase" [attr.aria-expanded]="sectionSelectorExpanded"
 | 
				
			||||||
                        id="core-course-section-button" expand="block"> <!-- @todo: attr.aria-label? -->
 | 
					                        id="core-course-section-button" expand="block"> <!-- @todo: attr.aria-label? -->
 | 
				
			||||||
                        <core-icon name="fas-folder" slot="start"></core-icon>
 | 
					                        <core-icon name="fas-folder" slot="start"></core-icon>
 | 
				
			||||||
 | 
				
			|||||||
@ -46,6 +46,8 @@ import { CoreUtils } from '@services/utils/utils';
 | 
				
			|||||||
import { CoreBlockCourseBlocksComponent } from '@features/block/components/course-blocks/course-blocks';
 | 
					import { CoreBlockCourseBlocksComponent } from '@features/block/components/course-blocks/course-blocks';
 | 
				
			||||||
import { CoreCourseSectionFormatted } from '@features/course/services/course-helper';
 | 
					import { CoreCourseSectionFormatted } from '@features/course/services/course-helper';
 | 
				
			||||||
import { CoreCourseModuleStatusChangedData } from '../module/module';
 | 
					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.
 | 
					 * 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.
 | 
					     * Display the section selector modal.
 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param event Event.
 | 
					 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
					    async showSectionSelector(): Promise<void> {
 | 
				
			||||||
    showSectionSelector(event?: MouseEvent): void {
 | 
					 | 
				
			||||||
        if (this.sectionSelectorExpanded) {
 | 
					        if (this.sectionSelectorExpanded) {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // @todo this.sectionSelectorExpanded = true;
 | 
					        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 = false;
 | 
					        const modal = await ModalController.instance.create({
 | 
				
			||||||
        // });
 | 
					            component: CoreCourseSectionSelectorComponent,
 | 
				
			||||||
 | 
					            componentProps: {
 | 
				
			||||||
 | 
					                course: this.course,
 | 
				
			||||||
 | 
					                sections: this.sections,
 | 
				
			||||||
 | 
					                selected: this.selectedSection,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        await modal.present();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // modal.present({
 | 
					        const result = await modal.onWillDismiss();
 | 
				
			||||||
        //     ev: event
 | 
					
 | 
				
			||||||
        // });
 | 
					        this.sectionSelectorExpanded = false;
 | 
				
			||||||
 | 
					        if (result?.data) {
 | 
				
			||||||
 | 
					            this.sectionChanged(result?.data);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 | 
				
			|||||||
@ -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>
 | 
				
			||||||
@ -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;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -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;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -169,6 +169,9 @@ ion-toolbar {
 | 
				
			|||||||
.item.core-danger-item {
 | 
					.item.core-danger-item {
 | 
				
			||||||
    --border-color: var(--ion-color-danger);
 | 
					    --border-color: var(--ion-color-danger);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					.item-dimmed {
 | 
				
			||||||
 | 
					    opacity: 0.71;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Extra text colors.
 | 
					// Extra text colors.
 | 
				
			||||||
.text-gray {
 | 
					.text-gray {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user