Skip to content

Commit 1527e31

Browse files
committed
MOBILE-4442 course: Open subsections as sections inside course
1 parent 95c4e0e commit 1527e31

File tree

8 files changed

+123
-19
lines changed

8 files changed

+123
-19
lines changed

src/addons/mod/subsection/services/handlers/index-link.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export class AddonModSubsectionIndexLinkHandlerService extends CoreContentLinksM
5151
// Get the module.
5252
const module = await CoreCourse.getModule(moduleId, courseId, undefined, true, false, siteId);
5353

54-
await AddonModSubsection.openSubsection(module, module.course, siteId);
54+
await AddonModSubsection.openSubsection(module.section, module.course, siteId);
5555
} catch (error) {
5656
CoreDomUtils.showErrorModalDefault(error, 'Error opening link.');
5757
} finally {

src/addons/mod/subsection/services/handlers/module.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ export class AddonModSubsectionModuleHandlerService extends CoreModuleHandlerBas
5959
a11yTitle: '',
6060
class: 'addon-mod-subsection-handler',
6161
hasCustomCmListItem: true,
62-
action: async(event, module, courseId) => {
62+
action: async(event, module) => {
6363
try {
64-
await AddonModSubsection.openSubsection(module, courseId);
64+
await AddonModSubsection.openSubsection(module.section, module.course);
6565
} catch (error) {
6666
CoreDomUtils.showErrorModalDefault(error, 'Error opening subsection.');
6767
}

src/addons/mod/subsection/services/subsection.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
import { Injectable } from '@angular/core';
1616
import { CoreCourse } from '@features/course/services/course';
17-
import { CoreCourseModuleData, CoreCourseHelper } from '@features/course/services/course-helper';
17+
import { CoreCourseHelper } from '@features/course/services/course-helper';
1818
import { CoreSites } from '@services/sites';
1919
import { makeSingleton } from '@singletons';
2020

@@ -26,14 +26,15 @@ export class AddonModSubsectionProvider {
2626

2727
/**
2828
* Open a subsection.
29+
*
30+
* @param sectionId Section ID.
31+
* @param courseId Course ID.
32+
* @param siteId Site ID. If not defined, current site.
33+
* @returns Promise resolved when done.
2934
*/
30-
async openSubsection(module: CoreCourseModuleData , courseId?: number, siteId?: string): Promise<void> {
31-
if (!courseId) {
32-
courseId = module.course;
33-
}
34-
35+
async openSubsection(sectionId: number, courseId: number, siteId?: string): Promise<void> {
3536
const pageParams = {
36-
sectionId: module.section,
37+
sectionId,
3738
};
3839

3940
if (

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
[value]="accordionMultipleValue">
1515
<core-course-section *ngIf="!selectedSection.hiddenbynumsections && selectedSection.id !== stealthModulesSectionId &&
1616
!selectedSection.component" [course]="course" [section]="selectedSection" [lastModuleViewed]="lastModuleViewed"
17-
[viewedModules]="viewedModules" [collapsible]="false" [sections]="subSections" />
17+
[viewedModules]="viewedModules" [collapsible]="false" [subSections]="subSections" />
1818
</ion-accordion-group>
1919
<core-empty-box *ngIf="!selectedSection.hasContent" icon="fas-table-cells-large"
2020
[message]="'core.course.nocontentavailable' | translate" />
@@ -31,7 +31,7 @@
3131
section.id !== stealthModulesSectionId && !section.component) {
3232
<core-course-section
3333
[course]="course" [section]="section" [lastModuleViewed]="lastModuleViewed" [viewedModules]="viewedModules"
34-
[collapsible]="true" [sections]="subSections" />
34+
[collapsible]="true" [subSections]="subSections" />
3535
}
3636
}
3737
</ion-accordion-group>

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

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
8888

8989
@Input({ required: true }) course!: CoreCourseAnyCourseData; // The course to render.
9090
@Input() sections: CoreCourseSectionToDisplay[] = []; // List of course sections.
91-
@Input() subSections: CoreCourseSectionToDisplay[] = []; // List of course subsections.
9291
@Input() initialSectionId?: number; // The section to load first (by ID).
9392
@Input() initialSectionNumber?: number; // The section to load first (by number).
9493
@Input() initialBlockInstanceId?: number; // The instance to focus.
@@ -125,6 +124,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
125124
displayCourseIndex = false;
126125
displayBlocks = false;
127126
hasBlocks = false;
127+
subSections: CoreCourseSectionToDisplay[] = []; // List of course subsections.
128128
selectedSection?: CoreCourseSectionToDisplay;
129129
previousSection?: CoreCourseSectionToDisplay;
130130
nextSection?: CoreCourseSectionToDisplay;
@@ -326,6 +326,26 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
326326
this.loaded = true;
327327
this.sectionChanged(sections[0]);
328328
} else if (this.initialSectionId || this.initialSectionNumber !== undefined) {
329+
const subSection = this.subSections.find((section) => section.id === this.initialSectionId ||
330+
(section.section !== undefined && section.section === this.initialSectionNumber));
331+
if (subSection) {
332+
// The section is a subsection, load the parent section.
333+
this.sections.some((section) => {
334+
const module = section.modules.find((module) =>
335+
subSection.itemid === module.instance && module.modname === 'subsection');
336+
if (module) {
337+
this.initialSectionId = module.section;
338+
this.initialSectionNumber = undefined;
339+
340+
return true;
341+
}
342+
343+
return false;
344+
});
345+
346+
this.setInputData();
347+
}
348+
329349
// We have an input indicating the section ID to load. Search the section.
330350
const section = sections.find((section) =>
331351
section.id === this.initialSectionId ||

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

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
<ion-accordion *ngIf="!section.hiddenbynumsections" class="core-course-module-list-wrapper" [id]="section.id"
2-
[attr.aria-labelledby]="section.name ? 'core-section-name-' + section.id : null" [value]="collapsible ? section.id : 'non-collapsible'"
3-
toggleIconSlot="start">
1+
<ion-accordion *ngIf="collapsible" class="core-course-module-list-wrapper" [id]="section.id"
2+
[attr.aria-labelledby]="section.name ? 'core-section-name-' + section.id : null" [value]="section.id" toggleIconSlot="start">
3+
44
<ion-item class="course-section divider" [class.item-dimmed]="section.visible === 0 || section.uservisible === false" slot="header">
55
<ion-label class="ion-text-wrap">
66
<h2 *ngIf="section.name" class="big" [id]="'core-section-name-' + section.id">
@@ -36,13 +36,69 @@ <h2 *ngIf="section.name" class="big" [id]="'core-section-name-' + section.id">
3636
</ion-label>
3737
</ion-item>
3838

39-
<ng-container *ngFor="let module of section.modules">
39+
<ng-container *ngFor="let module of modules">
40+
@if (module.subsection) {
41+
<core-course-section [course]="course" [section]="module.subsection" [lastModuleViewed]="lastModuleViewed"
42+
[viewedModules]="viewedModules" [collapsible]="true" />
43+
} @else {
4044
<core-course-module *ngIf="module.visibleoncoursepage !== 0" [module]="module" [section]="section"
4145
[showActivityDates]="course.showactivitydates" [showCompletionConditions]="course.showcompletionconditions"
4246
[isLastViewed]="lastModuleViewed && lastModuleViewed.cmId === module.id" [class.core-course-module-not-viewed]="
4347
!viewedModules[module.id] &&
4448
(!module.completiondata || module.completiondata.state === completionStatusIncomplete)" />
49+
}
4550
</ng-container>
4651
</ng-container>
4752
</div>
4853
</ion-accordion>
54+
55+
56+
<div *ngIf="!collapsible" class="core-course-module-list-wrapper" [id]="section.id"
57+
[attr.aria-labelledby]="section.name ? 'core-section-name-' + section.id : null">
58+
59+
<ion-item class="course-section divider" [class.item-dimmed]="section.visible === 0 || section.uservisible === false">
60+
<ion-label class="ion-text-wrap">
61+
<h2 *ngIf="section.name" class="big" [id]="'core-section-name-' + section.id">
62+
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course.id" />
63+
</h2>
64+
<div *ngIf="section.visible === 0 && section.uservisible !== false">
65+
<ion-badge color="warning">
66+
{{ 'core.course.hiddenfromstudents' | translate }}
67+
</ion-badge>
68+
</div>
69+
<div *ngIf="section.visible === 0 && section.uservisible === false">
70+
<ion-badge color="warning">
71+
{{ 'core.notavailable' | translate }}
72+
</ion-badge>
73+
</div>
74+
<div *ngIf="section.availabilityinfo">
75+
<ion-chip class="clickable">
76+
<ion-icon name="fas-lock" [attr.aria-label]="'core.restricted' | translate" />
77+
<ion-label>
78+
<core-format-text [text]=" section.availabilityinfo" contextLevel="course" [contextInstanceId]="course.id" />
79+
</ion-label>
80+
</ion-chip>
81+
</div>
82+
</ion-label>
83+
<ion-badge *ngIf="section.highlighted && highlightedName" slot="end">{{highlightedName}}</ion-badge>
84+
</ion-item>
85+
86+
<ion-item class="ion-text-wrap section-summary" *ngIf="section.summary">
87+
<ion-label>
88+
<core-format-text [text]="section.summary" contextLevel="course" [contextInstanceId]="course.id" />
89+
</ion-label>
90+
</ion-item>
91+
92+
<ng-container *ngFor="let module of modules">
93+
@if (module.subsection) {
94+
<core-course-section [course]="course" [section]="module.subsection" [lastModuleViewed]="lastModuleViewed"
95+
[viewedModules]="viewedModules" [collapsible]="true" />
96+
} @else {
97+
<core-course-module *ngIf="module.visibleoncoursepage !== 0" [module]="module" [section]="section"
98+
[showActivityDates]="course.showactivitydates" [showCompletionConditions]="course.showcompletionconditions"
99+
[isLastViewed]="lastModuleViewed && lastModuleViewed.cmId === module.id" [class.core-course-module-not-viewed]="
100+
!viewedModules[module.id] &&
101+
(!module.completiondata || module.completiondata.state === completionStatusIncomplete)" />
102+
}
103+
</ng-container>
104+
</div>

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ import {
1515
Component,
1616
HostBinding,
1717
Input,
18+
OnChanges,
1819
OnInit,
20+
SimpleChange,
1921
} from '@angular/core';
2022
import {
23+
CoreCourseModuleData,
2124
CoreCourseSection,
2225
} from '@features/course/services/course-helper';
2326
import { CoreSharedModule } from '@/core/shared.module';
@@ -41,10 +44,11 @@ import { CoreCourseFormatDelegate } from '@features/course/services/format-deleg
4144
CoreCourseComponentsModule,
4245
],
4346
})
44-
export class CoreCourseSectionComponent implements OnInit {
47+
export class CoreCourseSectionComponent implements OnInit, OnChanges {
4548

4649
@Input({ required: true }) course!: CoreCourseAnyCourseData; // The course to render.
4750
@Input({ required: true }) section!: CoreCourseSectionToDisplay;
51+
@Input() subSections: CoreCourseSectionToDisplay[] = []; // List of subsections in the course.
4852
@Input({ transform: toBoolean }) collapsible = true; // Whether the section can be collapsed.
4953
@Input() lastModuleViewed?: CoreCourseViewedModulesDBRecord;
5054
@Input() viewedModules: Record<number, boolean> = {};
@@ -54,6 +58,7 @@ export class CoreCourseSectionComponent implements OnInit {
5458
return this.collapsible ? 'collapsible' : 'non-collapsible';
5559
}
5660

61+
modules: CoreCourseModuleToDisplay[] = [];
5762
completionStatusIncomplete = CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE;
5863
highlightedName?: string; // Name to highlight.
5964

@@ -66,8 +71,28 @@ export class CoreCourseSectionComponent implements OnInit {
6671
: undefined;
6772
}
6873

74+
/**
75+
* @inheritdoc
76+
*/
77+
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
78+
if (changes.section && this.section) {
79+
this.modules = this.section.modules;
80+
81+
this.modules.forEach((module) => {
82+
if (module.modname === 'subsection') {
83+
module.subSection = this.subSections.find((section) =>
84+
section.component === 'mod_subsection' && section.itemid === module.instance);
85+
}
86+
});
87+
}
88+
}
89+
6990
}
7091

92+
type CoreCourseModuleToDisplay = CoreCourseModuleData & {
93+
subSection?: CoreCourseSectionToDisplay;
94+
};
95+
7196
export type CoreCourseSectionToDisplay = CoreCourseSection & {
7297
highlighted?: boolean;
7398
expanded?: boolean; // The aim of this property is to avoid DOM overloading.

src/core/features/course/services/course.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -990,7 +990,7 @@ export class CoreCourseProvider {
990990
map(sections => {
991991
const siteHomeId = site.getSiteHomeId();
992992
let showSections = true;
993-
if (courseId == siteHomeId) {
993+
if (courseId === siteHomeId) {
994994
const storedNumSections = site.getStoredConfig('numsections');
995995
showSections = storedNumSections !== undefined && !!storedNumSections;
996996
}
@@ -1770,6 +1770,8 @@ type CoreCourseGetContentsWSSection = {
17701770
uservisible?: boolean; // Is the section visible for the user?.
17711771
availabilityinfo?: string; // Availability information.
17721772
modules: CoreCourseGetContentsWSModule[]; // List of module.
1773+
component?: string; // @since 4.5 The delegate component of this section if any.
1774+
itemid?: number; // @since 4.5 The optional item id delegate component can use to identify its instance.
17731775
};
17741776

17751777
/**

0 commit comments

Comments
 (0)