2
0
Fork 0

MOBILE-3915 course: Align icons on course index

main
Pau Ferrer Ocaña 2022-02-01 11:40:31 +01:00
parent 6b3d39068c
commit 1e2eb7e4fb
5 changed files with 131 additions and 107 deletions

View File

@ -12,9 +12,9 @@
</ion-header>
<ion-content>
<ion-list id="core-course-section-selector" role="listbox" aria-labelledby="core-course-section-selector-label">
<ng-container *ngFor="let section of sections">
<ion-item *ngIf="allSectionId == section.id" class="ion-text-wrap divider" (click)="selectSection($event, section)" button
[class.item-current]="selectedId === section.id" detail="false">
<ng-container *ngFor="let section of sectionsToRender">
<ion-item *ngIf="allSectionId == section.id" class="ion-text-wrap divider core-course-index-all"
(click)="selectSectionOrModule($event, section.id)" button [class.item-current]="selectedId === section.id" detail="false">
<ion-label>
<p class="item-heading">
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id">
@ -22,16 +22,17 @@
</p>
</ion-label>
</ion-item>
<ng-container *ngIf="allSectionId != section.id && !section.hiddenbynumsections &&
section.id != stealthModulesSectionId && section.uservisible !== false">
<ion-item class="ion-text-wrap divider section" (click)="selectSection($event, section)"
[button]="section.visible !== 0 && section.uservisible !== false" [class.item-current]="selectedId === section.id"
[class.item-dimmed]="section.visible === 0" detail="false" sticky="true">
<ion-icon [name]="section.expanded ? 'fas-chevron-down' : 'fas-chevron-right'" flip-rtl slot="start"
class="expandable-status-icon" (click)="toggleExpand($event, section)"
<ng-container *ngIf="allSectionId != section.id">
<ion-item class="ion-text-wrap divider section" (click)="selectSectionOrModule($event, section.id)" button
[class.item-current]="selectedId === section.id" [class.item-dimmed]="section.visible === 0" detail="false"
sticky="true">
<ion-icon *ngIf="section.hasVisibleModules" [name]="section.expanded ? 'fas-chevron-down' : 'fas-chevron-right'"
flip-rtl slot="start" class="expandable-status-icon" (click)="toggleExpand($event, section)"
[attr.aria-label]="(section.expanded ? 'core.collapse' : 'core.expand') | translate"
[attr.aria-expanded]="section.expanded" [attr.aria-controls]="'core-course-index-section-' + section.id">
</ion-icon>
<ion-icon *ngIf="!section.hasVisibleModules" name="" slot="start" aria-hidden="true" class="expandable-status-icon">
</ion-icon>
<ion-label>
<p class="item-heading">
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id">
@ -42,37 +43,34 @@
<ion-icon name="fas-lock" *ngIf="section.availabilityinfo" slot="end" class="restricted"
[attr.aria-label]="'core.restricted' | translate"></ion-icon>
</ion-item>
<div [hidden]="!section.expanded" [id]="'core-course-index-section-' + section.id">
<ng-container *ngIf="section.expanded">
<ng-container *ngFor="let module of section.modules">
<ion-item [class.item-dimmed]="module.visible === 0"
*ngIf="module.visibleoncoursepage !== 0 && !module.noviewlink"
(click)="selectModule($event, section, module)" button>
<ion-icon class="completioninfo completion_none" name="" *ngIf="module.completionStatus === undefined"
slot="start" aria-hidden="true"></ion-icon>
<ion-icon class="completioninfo completion_incomplete" name="far-circle"
*ngIf="module.completionStatus === 0" slot="start" [attr.aria-label]="'core.course.todo' | translate">
</ion-icon>
<ion-icon class="completioninfo completion_complete" name="fas-circle"
*ngIf="module.completionStatus === 1 || module.completionStatus === 2" color="success" slot="start"
[attr.aria-label]="'core.course.done' | translate">
</ion-icon>
<ion-icon class="completioninfo completion_fail" name="fas-circle" *ngIf="module.completionStatus === 3"
color="danger" slot="start" [attr.aria-label]="'core.course.failed' | translate">
</ion-icon>
<ion-label>
<p class="item-heading">
<core-format-text [text]="module.name" contextLevel="module" [contextInstanceId]="module.id"
[courseId]="module.courseid">
</core-format-text>
</p>
</ion-label>
<ion-icon name="fas-lock" *ngIf="module.uservisible === false" slot="end" class="restricted"
[attr.aria-label]="'core.restricted' | translate"></ion-icon>
</ion-item>
</ng-container>
<ng-container *ngIf="section.expanded">
<ng-container *ngFor="let module of section.modules">
<ion-item [class.item-dimmed]="!module.visible" (click)="selectSectionOrModule($event, section.id, module.id)"
button>
<ion-icon class="completioninfo completion_none" name="" *ngIf="module.completionStatus === undefined"
slot="start" aria-hidden="true"></ion-icon>
<ion-icon class="completioninfo completion_incomplete" name="far-circle" *ngIf="module.completionStatus === 0"
slot="start" [attr.aria-label]="'core.course.todo' | translate">
</ion-icon>
<ion-icon class="completioninfo completion_complete" name="fas-circle"
*ngIf="module.completionStatus === 1 || module.completionStatus === 2" color="success" slot="start"
[attr.aria-label]="'core.course.done' | translate">
</ion-icon>
<ion-icon class="completioninfo completion_fail" name="fas-circle" *ngIf="module.completionStatus === 3"
color="danger" slot="start" [attr.aria-label]="'core.course.failed' | translate">
</ion-icon>
<ion-label>
<p class="item-heading">
<core-format-text [text]="module.name" contextLevel="module" [contextInstanceId]="module.id"
[courseId]="module.course">
</core-format-text>
</p>
</ion-label>
<ion-icon name="fas-lock" *ngIf="!module.uservisible" slot="end" class="restricted"
[attr.aria-label]="'core.restricted' | translate"></ion-icon>
</ion-item>
</ng-container>
</div>
</ng-container>
</ng-container>
</ng-container>
</ion-list>

View File

@ -16,18 +16,21 @@ ion-icon.completioninfo {
width: 18px;
}
ion-item.section::part(native) {
ion-item::part(native) {
--padding-start: 0;
}
ion-icon.expandable-status-icon {
ion-icon {
margin: 0;
@include padding(12px, 32px, 12px, 16px);
}
ion-item.core-course-index-all::part(native) {
--padding-start: 16px;
}
ion-item.item-current ion-icon.expandable-status-icon {
@include padding(null, null, null, 11px);
}
ion-icon.restricted {

View File

@ -13,19 +13,18 @@
// limitations under the License.
import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
import { CoreCourseModuleData, CoreCourseSection } from '@features/course/services/course-helper';
import {
CoreCourseModuleCompletionStatus,
CoreCourseModuleCompletionTracking,
CoreCourseProvider,
} from '@features/course/services/course';
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
import { CoreUtils } from '@services/utils/utils';
import { ModalController } from '@singletons';
import { CoreCourseSection } from '@features/course/services/course-helper';
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
import { IonContent } from '@ionic/angular';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
import { ModalController } from '@singletons';
/**
* Component to display course index modal.
@ -39,13 +38,13 @@ export class CoreCourseCourseIndexComponent implements OnInit {
@ViewChild(IonContent) content?: IonContent;
@Input() sections?: CourseIndexSection[];
@Input() sections: CoreCourseSection[] = [];
@Input() selectedId?: number;
@Input() course?: CoreCourseAnyCourseData;
stealthModulesSectionId = CoreCourseProvider.STEALTH_MODULES_SECTION_ID;
allSectionId = CoreCourseProvider.ALL_SECTIONS_ID;
highlighted?: string;
sectionsToRender: CourseIndexSection[] = [];
constructor(
protected elementRef: ElementRef,
@ -70,29 +69,47 @@ export class CoreCourseCourseIndexComponent implements OnInit {
return;
}
// Collapse all sections first.
this.sections.forEach((section) => section.expanded = false);
const currentSection = await CoreCourseFormatDelegate.getCurrentSection(this.course, this.sections);
currentSection.highlighted = true;
if (this.selectedId === undefined) {
currentSection.expanded = true;
// Highlight current section if none is selected.
this.selectedId = currentSection.id;
} else {
const selectedSection = this.sections.find((section) => section.id == this.selectedId);
if (selectedSection) {
selectedSection.expanded = true;
}
}
this.sections.forEach((section) => {
section.modules.forEach((module) => {
module.completionStatus = module.completiondata === undefined ||
module.completiondata.tracking == CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_NONE
? undefined
: module.completiondata.state;
// Clone sections to add information.
this.sectionsToRender = this.sections
.filter((section) => !section.hiddenbynumsections &&
section.id != CoreCourseProvider.STEALTH_MODULES_SECTION_ID &&
section.uservisible !== false)
.map((section) => {
const modules = section.modules
.filter((module) => module.visibleoncoursepage !== 0 && !module.noviewlink)
.map((module) => {
const completionStatus = module.completiondata === undefined ||
module.completiondata.tracking == CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_NONE
? undefined
: module.completiondata.state;
return {
id: module.id,
name: module.name,
course: module.course,
visible: !!module.visible,
uservisible: !!module.uservisible,
completionStatus,
};
});
return {
id: section.id,
name: section.name,
availabilityinfo: !!section.availabilityinfo,
expanded: section.id === this.selectedId,
highlighted: currentSection?.id === section.id,
hasVisibleModules: modules.length > 0,
modules: modules,
};
});
});
this.highlighted = CoreCourseFormatDelegate.getSectionHightlightedName(this.course);
@ -102,7 +119,7 @@ export class CoreCourseCourseIndexComponent implements OnInit {
this.content,
'.item.item-current',
);
}, 200);
}, 300);
}
/**
@ -128,39 +145,33 @@ export class CoreCourseCourseIndexComponent implements OnInit {
* Select a section.
*
* @param event Event.
* @param section Selected section object.
* @param sectionId Selected section id.
* @param moduleId Selected module id, if any.
*/
selectSection(event: Event, section: CoreCourseSection): void {
if (section.uservisible !== false) {
ModalController.dismiss({ event, section });
}
}
/**
* Select a section and open a module
*
* @param event Event.
* @param section Selected section object.
* @param module Selected module object.
*/
selectModule(event: Event,section: CoreCourseSection, module: CoreCourseModuleData): void {
if (module.uservisible !== false) {
ModalController.dismiss({ event, section, module });
}
selectSectionOrModule(event: Event, sectionId: number, moduleId?: number): void {
ModalController.dismiss({ event, sectionId, moduleId });
}
}
type CourseIndexSection = Omit<CoreCourseSection, 'modules'> & {
highlighted?: boolean;
expanded?: boolean;
modules: (CoreCourseModuleData & {
type CourseIndexSection = {
id: number;
name: string;
highlighted: boolean;
expanded: boolean;
hasVisibleModules: boolean;
availabilityinfo: boolean;
modules: {
id: number;
course: number;
visible: boolean;
uservisible: boolean;
completionStatus?: CoreCourseModuleCompletionStatus;
})[];
}[];
};
export type CoreCourseIndexSectionWithModule = {
event: Event;
section: CourseIndexSection;
module?: CoreCourseModuleData;
sectionId: number;
moduleId?: number;
};

View File

@ -69,7 +69,6 @@
class="section-wrapper" [id]="section.id">
<ion-item-divider class="course-section ion-text-wrap" color="light"
[class.item-dimmed]="section.visible === 0 || section.uservisible === false">
<ion-icon name="fas-folder" aria-label="hidden" slot="start"></ion-icon>
<ion-label>
<h2 *ngIf="section.name">
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course.id">

View File

@ -312,20 +312,33 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
},
});
if (data) {
this.sectionChanged(data.section);
if (data.module) {
if (!data.module.handlerData) {
data.module.handlerData =
await CoreCourseModuleDelegate.getModuleDataFor(data.module.modname, data.module, this.course.id);
}
if (data.module.uservisible !== false && data.module.handlerData?.action) {
data.module.handlerData.action(data.event, data.module, data.module.course);
}
this.moduleId = data.module.id;
}
if (!data) {
return;
}
const section = this.sections.find((section) => section.id == data.sectionId);
if (!section) {
return;
}
this.sectionChanged(section);
if (!data.moduleId) {
return;
}
const module = section.modules.find((module) => module.id == data.moduleId);
if (!module) {
return;
}
if (!module.handlerData) {
module.handlerData =
await CoreCourseModuleDelegate.getModuleDataFor(module.modname, module, this.course.id);
}
if (module.uservisible !== false && module.handlerData?.action) {
module.handlerData.action(data.event, module, module.course);
}
this.moduleId = data.moduleId;
}
/**