Skip to content

Commit cb9580b

Browse files
committed
MOBILE-4442 course: Manage subsections on course index page
1 parent aad9899 commit cb9580b

File tree

5 files changed

+193
-151
lines changed

5 files changed

+193
-151
lines changed

src/core/features/course/components/course-format/course-format.ts

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -488,19 +488,33 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
488488
componentProps: {
489489
course: this.course,
490490
sections: this.sections,
491+
subSections: this.subSections,
491492
selectedId: selectedId,
492493
},
493494
});
494495

495496
if (!data) {
496497
return;
497498
}
498-
const section = this.sections.find((section) => section.id === data.sectionId);
499+
let section = this.sections.find((section) => section.id === data.sectionId);
499500
if (!section) {
500501
return;
501502
}
502503
this.sectionChanged(section);
503504

505+
if (data.subSectionId) {
506+
section = this.subSections.find((section) => section.id === data.subSectionId);
507+
if (!section) {
508+
return;
509+
}
510+
511+
// Use this section to find the module.
512+
this.setSectionExpanded(section);
513+
514+
// Scroll to the subsection (later it may be scrolled to the module).
515+
this.scrollInCourse(section.id, true);
516+
}
517+
504518
if (!data.moduleId) {
505519
return;
506520
}
@@ -515,7 +529,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
515529
}
516530

517531
if (CoreCourseHelper.canUserViewModule(module, section)) {
518-
this.scrollToModule(module.id);
532+
this.scrollInCourse(module.id);
519533

520534
module.handlerData?.action?.(data.event, module, module.course);
521535
}
@@ -585,7 +599,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
585599
// Scroll to module if needed. Give more priority to the input.
586600
const moduleIdToScroll = this.moduleId && previousValue === undefined ? this.moduleId : moduleId;
587601
if (moduleIdToScroll) {
588-
this.scrollToModule(moduleIdToScroll);
602+
this.scrollInCourse(moduleIdToScroll);
589603
}
590604

591605
if (!previousValue || previousValue.id !== newSection.id) {
@@ -600,16 +614,14 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
600614
}
601615

602616
/**
603-
* Scroll to a certain module.
617+
* Scroll to a certain module or section.
604618
*
605-
* @param moduleId Module ID.
619+
* @param id ID of the module or section to scroll to.
620+
* @param isSection Whether to scroll to a module or a subsection.
606621
*/
607-
protected scrollToModule(moduleId: number): void {
608-
CoreDom.scrollToElement(
609-
this.elementRef.nativeElement,
610-
'#core-course-module-' + moduleId,
611-
{ addYAxis: -10 },
612-
);
622+
protected scrollInCourse(id: number, isSection = false): void {
623+
const elementId = isSection ? `#core-section-name-${id}` : `#core-course-module-${id}`;
624+
CoreDom.scrollToElement(this.elementRef.nativeElement, elementId,{ addYAxis: -10 });
613625
}
614626

615627
/**

src/core/features/course/components/course-index/course-index.html

Lines changed: 58 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -24,59 +24,66 @@ <h2>
2424
</ion-label>
2525
</ion-item>
2626
<ng-container *ngIf="allSectionId !== section.id">
27-
<ion-item class="divider section" (click)="selectSectionOrModule($event, section.id)" button
28-
[class.item-current]="selectedId === section.id" [class.item-dimmed]="!section.visible"
29-
[class.item-hightlighted]="section.highlighted" [detail]="false">
30-
<ion-icon *ngIf="section.hasVisibleModules" name="fas-chevron-right" flip-rtl slot="start"
31-
class="expandable-status-icon" (ariaButtonClick)="toggleExpand($event, section)"
32-
[attr.aria-label]="(section.expanded ? 'core.collapse' : 'core.expand') | translate"
33-
[attr.aria-expanded]="section.expanded" [attr.aria-controls]="'core-course-index-section-' + section.id"
34-
[class.expandable-status-icon-expanded]="section.expanded" />
35-
<ion-icon *ngIf="!section.hasVisibleModules" name="" slot="start" aria-hidden="true"
36-
class="expandable-status-icon" />
37-
<ion-label>
38-
<h2>
39-
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id" />
40-
</h2>
41-
</ion-label>
42-
<ion-badge *ngIf="section.highlighted && highlighted" slot="end">{{highlighted}}</ion-badge>
43-
<ion-icon name="fas-lock" *ngIf="section.availabilityinfo" slot="end" class="restricted"
44-
[attr.aria-label]="'core.restricted' | translate" />
45-
<ion-icon name="fas-eye-slash" *ngIf="!section.visible && !section.uservisible" slot="end" class="restricted"
46-
[attr.aria-label]="'core.notavailable' | translate" />
47-
<ion-icon name="fas-eye-slash" *ngIf="!section.visible && section.uservisible" slot="end" class="restricted"
48-
[attr.aria-label]="'core.course.hiddenfromstudents' | translate" />
49-
</ion-item>
50-
<div id="core-course-index-section-{{section.id}}">
51-
<ng-container *ngIf="section.expanded">
52-
<ng-container *ngFor="let module of section.modules">
53-
<ion-item class="module" [class.item-dimmed]="!module.visible" [class.indented]="module.indented"
54-
[class.item-hightlighted]="section.highlighted"
55-
(click)="selectSectionOrModule($event, section.id, module.id)" button>
56-
<ion-icon class="completioninfo completion_none" name="" *ngIf="module.completionStatus === undefined"
57-
slot="start" aria-hidden="true" />
58-
<ion-icon class="completioninfo completion_incomplete" name="far-circle"
59-
*ngIf="module.completionStatus === 0" slot="start"
60-
[attr.aria-label]="'core.course.todo' | translate" />
61-
<ion-icon class="completioninfo completion_complete" name="fas-circle"
62-
*ngIf="module.completionStatus === 1 || module.completionStatus === 2" color="success" slot="start"
63-
[attr.aria-label]="'core.course.done' | translate" />
64-
<ion-icon class="completioninfo completion_fail" name="fas-xmark" *ngIf="module.completionStatus === 3"
65-
color="danger" slot="start" [attr.aria-label]="'core.course.failed' | translate" />
66-
<ion-label>
67-
<p class="item-heading">
68-
<core-format-text [text]="module.name" contextLevel="module" [contextInstanceId]="module.id"
69-
[courseId]="module.course" />
70-
</p>
71-
</ion-label>
72-
<ion-icon name="fas-lock" *ngIf="!module.uservisible" slot="end" class="restricted"
73-
[attr.aria-label]="'core.restricted' | translate" />
74-
</ion-item>
75-
</ng-container>
76-
</ng-container>
77-
</div>
27+
<ng-container *ngTemplateOutlet="sectionTemplate; context: {section}" />
7828
</ng-container>
7929
</ng-container>
8030
</ion-list>
8131
</core-loading>
8232
</ion-content>
33+
34+
35+
<ng-template #sectionTemplate let-section="section">
36+
<ion-item class="divider section" (click)="selectSectionOrModule($event, section.id)" button
37+
[class.item-current]="selectedId === section.id" [class.item-dimmed]="!section.visible"
38+
[class.item-hightlighted]="section.highlighted" [detail]="false">
39+
<ion-icon *ngIf="section.hasVisibleModules" name="fas-chevron-right" flip-rtl slot="start" class="expandable-status-icon"
40+
(ariaButtonClick)="toggleExpand($event, section)"
41+
[attr.aria-label]="(section.expanded ? 'core.collapse' : 'core.expand') | translate" [attr.aria-expanded]="section.expanded"
42+
[attr.aria-controls]="'core-course-index-section-' + section.id" [class.expandable-status-icon-expanded]="section.expanded" />
43+
<ion-icon *ngIf="!section.hasVisibleModules" name="" slot="start" aria-hidden="true" class="expandable-status-icon" />
44+
<ion-label>
45+
<h2>
46+
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id" />
47+
</h2>
48+
</ion-label>
49+
<ion-badge *ngIf="section.highlighted && highlighted" slot="end">{{highlighted}}</ion-badge>
50+
<ion-icon name="fas-lock" *ngIf="section.availabilityinfo" slot="end" class="restricted"
51+
[attr.aria-label]="'core.restricted' | translate" />
52+
<ion-icon name="fas-eye-slash" *ngIf="!section.visible && !section.uservisible" slot="end" class="restricted"
53+
[attr.aria-label]="'core.notavailable' | translate" />
54+
<ion-icon name="fas-eye-slash" *ngIf="!section.visible && section.uservisible" slot="end" class="restricted"
55+
[attr.aria-label]="'core.course.hiddenfromstudents' | translate" />
56+
</ion-item>
57+
<div id="core-course-index-section-{{section.id}}" class="core-course-index-section-content">
58+
<ng-container *ngIf="section.expanded">
59+
<ng-container *ngFor="let module of section.modules">
60+
@if (module.subSection) {
61+
<div class="core-course-index-subsection">
62+
<ng-container *ngTemplateOutlet="sectionTemplate; context: {section: module.subSection}" />
63+
</div>
64+
} @else {
65+
<ion-item class="module" [class.item-dimmed]="!module.visible" [class.indented]="module.indented"
66+
[class.item-hightlighted]="section.highlighted" (click)="selectSectionOrModule($event, section.id, module.id)" button>
67+
<ion-icon class="completioninfo completion_none" name="" *ngIf="module.completionStatus === undefined" slot="start"
68+
aria-hidden="true" />
69+
<ion-icon class="completioninfo completion_incomplete" name="far-circle" *ngIf="module.completionStatus === 0"
70+
slot="start" [attr.aria-label]="'core.course.todo' | translate" />
71+
<ion-icon class="completioninfo completion_complete" name="fas-circle"
72+
*ngIf="module.completionStatus === 1 || module.completionStatus === 2" color="success" slot="start"
73+
[attr.aria-label]="'core.course.done' | translate" />
74+
<ion-icon class="completioninfo completion_fail" name="fas-xmark" *ngIf="module.completionStatus === 3" color="danger"
75+
slot="start" [attr.aria-label]="'core.course.failed' | translate" />
76+
<ion-label>
77+
<p class="item-heading">
78+
<core-format-text [text]="module.name" contextLevel="module" [contextInstanceId]="module.id"
79+
[courseId]="module.course" />
80+
</p>
81+
</ion-label>
82+
<ion-icon name="fas-lock" *ngIf="!module.uservisible" slot="end" class="restricted"
83+
[attr.aria-label]="'core.restricted' | translate" />
84+
</ion-item>
85+
}
86+
</ng-container>
87+
</ng-container>
88+
</div>
89+
</ng-template>

src/core/features/course/components/course-index/course-index.scss

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
@use "theme/globals" as *;
22
core-progress-bar {
3-
--bar-margin: 8px 0 4px 0;
3+
--bar-margin: 8px 0px 4px 0px;
44
--line-height: 20px;
55
--background: var(--contrast-background);
66
}
@@ -19,7 +19,7 @@ ion-item.item {
1919
&.item-current {
2020
--background: var(--primary-tint);
2121
--color: var(--gray-900);
22-
border: 0;
22+
border: 0px;
2323
}
2424

2525
&.item-hightlighted {
@@ -56,7 +56,7 @@ ion-item.item {
5656

5757
&.module {
5858
&::part(native) {
59-
--padding-start: 0;
59+
--padding-start: 0px;
6060
}
6161

6262
&.item-hightlighted ion-icon.completioninfo {
@@ -70,7 +70,7 @@ ion-item.item {
7070
}
7171

7272
ion-icon {
73-
margin: 0;
73+
margin: 0px;
7474
padding: 12px 16px;
7575

7676
&.completioninfo {
@@ -87,3 +87,7 @@ ion-item.item {
8787
}
8888
}
8989
}
90+
91+
div.core-course-index-subsection {
92+
@include padding-horizontal(16px, null);
93+
}

0 commit comments

Comments
 (0)