diff --git a/src/core/features/course/components/course-format/course-format.ts b/src/core/features/course/components/course-format/course-format.ts index a0f28742c..ca9c989f6 100644 --- a/src/core/features/course/components/course-format/course-format.ts +++ b/src/core/features/course/components/course-format/course-format.ts @@ -488,6 +488,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { componentProps: { course: this.course, sections: this.sections, + subSections: this.subSections, selectedId: selectedId, }, }); @@ -495,12 +496,25 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { if (!data) { return; } - const section = this.sections.find((section) => section.id === data.sectionId); + let section = this.sections.find((section) => section.id === data.sectionId); if (!section) { return; } this.sectionChanged(section); + if (data.subSectionId) { + section = this.subSections.find((section) => section.id === data.subSectionId); + if (!section) { + return; + } + + // Use this section to find the module. + this.setSectionExpanded(section); + + // Scroll to the subsection (later it may be scrolled to the module). + this.scrollInCourse(section.id, true); + } + if (!data.moduleId) { return; } @@ -515,7 +529,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { } if (CoreCourseHelper.canUserViewModule(module, section)) { - this.scrollToModule(module.id); + this.scrollInCourse(module.id); module.handlerData?.action?.(data.event, module, module.course); } @@ -585,7 +599,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { // Scroll to module if needed. Give more priority to the input. const moduleIdToScroll = this.moduleId && previousValue === undefined ? this.moduleId : moduleId; if (moduleIdToScroll) { - this.scrollToModule(moduleIdToScroll); + this.scrollInCourse(moduleIdToScroll); } if (!previousValue || previousValue.id !== newSection.id) { @@ -600,16 +614,14 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { } /** - * Scroll to a certain module. + * Scroll to a certain module or section. * - * @param moduleId Module ID. + * @param id ID of the module or section to scroll to. + * @param isSection Whether to scroll to a module or a subsection. */ - protected scrollToModule(moduleId: number): void { - CoreDom.scrollToElement( - this.elementRef.nativeElement, - '#core-course-module-' + moduleId, - { addYAxis: -10 }, - ); + protected scrollInCourse(id: number, isSection = false): void { + const elementId = isSection ? `#core-section-name-${id}` : `#core-course-module-${id}`; + CoreDom.scrollToElement(this.elementRef.nativeElement, elementId,{ addYAxis: -10 }); } /** diff --git a/src/core/features/course/components/course-index/course-index.html b/src/core/features/course/components/course-index/course-index.html index 546a70079..8c46c32c8 100644 --- a/src/core/features/course/components/course-index/course-index.html +++ b/src/core/features/course/components/course-index/course-index.html @@ -24,59 +24,66 @@ - - - - - - - - - {{highlighted}} - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + {{highlighted}} + + + + + + + + @if (module.subSection) { + + + + } @else { + + + + + + + + + + + + + } + + + + diff --git a/src/core/features/course/components/course-index/course-index.scss b/src/core/features/course/components/course-index/course-index.scss index 881955cb7..0ee128263 100644 --- a/src/core/features/course/components/course-index/course-index.scss +++ b/src/core/features/course/components/course-index/course-index.scss @@ -1,6 +1,6 @@ @use "theme/globals" as *; core-progress-bar { - --bar-margin: 8px 0 4px 0; + --bar-margin: 8px 0px 4px 0px; --line-height: 20px; --background: var(--contrast-background); } @@ -19,7 +19,7 @@ ion-item.item { &.item-current { --background: var(--primary-tint); --color: var(--gray-900); - border: 0; + border: 0px; } &.item-hightlighted { @@ -56,7 +56,7 @@ ion-item.item { &.module { &::part(native) { - --padding-start: 0; + --padding-start: 0px; } &.item-hightlighted ion-icon.completioninfo { @@ -70,7 +70,7 @@ ion-item.item { } ion-icon { - margin: 0; + margin: 0px; padding: 12px 16px; &.completioninfo { @@ -87,3 +87,7 @@ ion-item.item { } } } + +div.core-course-index-subsection { + @include padding-horizontal(16px, null); +} diff --git a/src/core/features/course/components/course-index/course-index.ts b/src/core/features/course/components/course-index/course-index.ts index 6fb8c8ca3..4de09de50 100644 --- a/src/core/features/course/components/course-index/course-index.ts +++ b/src/core/features/course/components/course-index/course-index.ts @@ -20,7 +20,7 @@ import { CoreCourseProvider, } from '@features/course/services/course'; import { CoreCourseHelper, CoreCourseModuleData, CoreCourseSection } from '@features/course/services/course-helper'; -import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate'; +import { CoreCourseFormatCurrentSectionData, CoreCourseFormatDelegate } from '@features/course/services/format-delegate'; import { CoreCourseAnyCourseData } from '@features/courses/services/courses'; import { CoreCoursesHelper } from '@features/courses/services/courses-helper'; import { CoreSites } from '@services/sites'; @@ -43,6 +43,7 @@ import { CoreDom } from '@singletons/dom'; export class CoreCourseCourseIndexComponent implements OnInit { @Input() sections: CoreCourseSection[] = []; + @Input() subSections: CoreCourseSection[] = []; @Input() selectedId?: number; @Input() course?: CoreCourseAnyCourseData; @@ -87,38 +88,8 @@ export class CoreCourseCourseIndexComponent implements OnInit { const enableIndentation = await CoreCourse.isCourseIndentationEnabled(site, this.course.id); this.sectionsToRender = this.sections - .filter((section) => !CoreCourseHelper.isSectionStealth(section)) - .map((section) => { - const modules = section.modules - .filter((module) => this.renderModule(section, module)) - .map((module) => { - const completionStatus = completionEnabled - ? CoreCourseHelper.getCompletionStatus(module.completiondata) - : undefined; - - return { - id: module.id, - name: module.name, - course: module.course, - visible: !!module.visible, - uservisible: CoreCourseHelper.canUserViewModule(module, section), - indented: enableIndentation && module.indent > 0, - completionStatus, - }; - }); - - return { - id: section.id, - name: section.name, - availabilityinfo: !!section.availabilityinfo, - visible: !!section.visible, - uservisible: CoreCourseHelper.canUserViewSection(section), - expanded: section.id === this.selectedId, - highlighted: currentSectionData.section.id === section.id, - hasVisibleModules: modules.length > 0, - modules: modules, - }; - }); + .filter((section) => section.component !== 'mod_subsection' && !CoreCourseHelper.isSectionStealth(section)) + .map((section) => this.mapSectionToRender(section, completionEnabled, enableIndentation, currentSectionData)); this.highlighted = CoreCourseFormatDelegate.getSectionHightlightedName(this.course); @@ -163,7 +134,26 @@ export class CoreCourseCourseIndexComponent implements OnInit { * @param moduleId Selected module id, if any. */ selectSectionOrModule(event: Event, sectionId: number, moduleId?: number): void { - ModalController.dismiss({ event, sectionId, moduleId }); + let subSectionId: number | undefined; + this.sectionsToRender.some((section) => { + if (section.id === sectionId) { + return true; + } + + return section.modules.some((module) => { + if (module.subSection?.id === sectionId) { + // Always use the parent section. + subSectionId = sectionId; + sectionId = section.id; + + return true; + } + + return false; + }); + }); + + ModalController.dismiss({ event, sectionId, subSectionId, moduleId }); } /** @@ -187,6 +177,69 @@ export class CoreCourseCourseIndexComponent implements OnInit { return !module.noviewlink; } + /** + * Map a section to the format needed to render it. + * + * @param section Section to map. + * @param completionEnabled Whether completion is enabled. + * @param enableIndentation Whether indentation is enabled. + * @param currentSectionData Current section data. + * @returns Mapped section. + */ + protected mapSectionToRender( + section: CoreCourseSection, + completionEnabled: boolean, + enableIndentation: boolean, + currentSectionData?: CoreCourseFormatCurrentSectionData, + ): CourseIndexSection { + const modules = section.modules + .filter((module) => module.modname === 'subsection' || this.renderModule(section, module)) + .map((module) => { + if (module.modname === 'subsection') { + const subSectionFound = this.subSections.find((subSection) => subSection.itemid === module.instance); + const subSection = subSectionFound + ? this.mapSectionToRender(subSectionFound, completionEnabled, enableIndentation) + : undefined; + + return { + id: module.id, + name: module.name, + course: module.course, + visible: !!module.visible, + uservisible: CoreCourseHelper.canUserViewModule(module, section), + indented: true, + subSection, + }; + } + + const completionStatus = completionEnabled + ? CoreCourseHelper.getCompletionStatus(module.completiondata) + : undefined; + + return { + id: module.id, + name: module.name, + course: module.course, + visible: !!module.visible, + uservisible: CoreCourseHelper.canUserViewModule(module, section), + indented: enableIndentation && module.indent > 0, + completionStatus, + }; + }); + + return { + id: section.id, + name: section.name, + availabilityinfo: !!section.availabilityinfo, + visible: !!section.visible, + uservisible: CoreCourseHelper.canUserViewSection(section), + expanded: section.id === this.selectedId, + highlighted: currentSectionData?.section.id === section.id, + hasVisibleModules: modules.length > 0, + modules, + }; + } + } type CourseIndexSection = { @@ -205,11 +258,13 @@ type CourseIndexSection = { indented: boolean; uservisible: boolean; completionStatus?: CoreCourseModuleCompletionStatus; + subSection?: CourseIndexSection; }[]; }; export type CoreCourseIndexSectionWithModule = { event: Event; sectionId: number; + subSectionId?: number; moduleId?: number; }; diff --git a/src/core/features/course/components/course-section/course-section.html b/src/core/features/course/components/course-section/course-section.html index 8d4bebb08..cddd0db66 100644 --- a/src/core/features/course/components/course-section/course-section.html +++ b/src/core/features/course/components/course-section/course-section.html @@ -1,6 +1,23 @@ - + + + + + + + +} @else { + + + + +} + + @@ -27,62 +44,9 @@ {{highlightedName}} + - - - - - - - - - - @if (module.subsection) { - - } @else { - - } - - - - - - - - - - - - - - - - {{ 'core.course.hiddenfromstudents' | translate }} - - - - - {{ 'core.notavailable' | translate }} - - - - - - - - - - - - {{highlightedName}} - - + @@ -101,4 +65,4 @@ (!module.completiondata || module.completiondata.state === completionStatusIncomplete)" /> } - +
- -
+ +