From 1527e31cd6e591bad356d01ea8543672e1b7d189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 13 Sep 2024 15:43:42 +0200 Subject: [PATCH] MOBILE-4442 course: Open subsections as sections inside course --- .../services/handlers/index-link.ts | 2 +- .../subsection/services/handlers/module.ts | 4 +- .../mod/subsection/services/subsection.ts | 15 +++-- .../course-format/course-format.html | 4 +- .../components/course-format/course-format.ts | 22 ++++++- .../course-section/course-section.html | 64 +++++++++++++++++-- .../course-section/course-section.ts | 27 +++++++- src/core/features/course/services/course.ts | 4 +- 8 files changed, 123 insertions(+), 19 deletions(-) diff --git a/src/addons/mod/subsection/services/handlers/index-link.ts b/src/addons/mod/subsection/services/handlers/index-link.ts index 240f55b7c..f8962eb5d 100644 --- a/src/addons/mod/subsection/services/handlers/index-link.ts +++ b/src/addons/mod/subsection/services/handlers/index-link.ts @@ -51,7 +51,7 @@ export class AddonModSubsectionIndexLinkHandlerService extends CoreContentLinksM // Get the module. const module = await CoreCourse.getModule(moduleId, courseId, undefined, true, false, siteId); - await AddonModSubsection.openSubsection(module, module.course, siteId); + await AddonModSubsection.openSubsection(module.section, module.course, siteId); } catch (error) { CoreDomUtils.showErrorModalDefault(error, 'Error opening link.'); } finally { diff --git a/src/addons/mod/subsection/services/handlers/module.ts b/src/addons/mod/subsection/services/handlers/module.ts index e4d9ed11f..c9266d0d5 100644 --- a/src/addons/mod/subsection/services/handlers/module.ts +++ b/src/addons/mod/subsection/services/handlers/module.ts @@ -59,9 +59,9 @@ export class AddonModSubsectionModuleHandlerService extends CoreModuleHandlerBas a11yTitle: '', class: 'addon-mod-subsection-handler', hasCustomCmListItem: true, - action: async(event, module, courseId) => { + action: async(event, module) => { try { - await AddonModSubsection.openSubsection(module, courseId); + await AddonModSubsection.openSubsection(module.section, module.course); } catch (error) { CoreDomUtils.showErrorModalDefault(error, 'Error opening subsection.'); } diff --git a/src/addons/mod/subsection/services/subsection.ts b/src/addons/mod/subsection/services/subsection.ts index 7ef0293fd..ac62b952f 100644 --- a/src/addons/mod/subsection/services/subsection.ts +++ b/src/addons/mod/subsection/services/subsection.ts @@ -14,7 +14,7 @@ import { Injectable } from '@angular/core'; import { CoreCourse } from '@features/course/services/course'; -import { CoreCourseModuleData, CoreCourseHelper } from '@features/course/services/course-helper'; +import { CoreCourseHelper } from '@features/course/services/course-helper'; import { CoreSites } from '@services/sites'; import { makeSingleton } from '@singletons'; @@ -26,14 +26,15 @@ export class AddonModSubsectionProvider { /** * Open a subsection. + * + * @param sectionId Section ID. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @returns Promise resolved when done. */ - async openSubsection(module: CoreCourseModuleData , courseId?: number, siteId?: string): Promise { - if (!courseId) { - courseId = module.course; - } - + async openSubsection(sectionId: number, courseId: number, siteId?: string): Promise { const pageParams = { - sectionId: module.section, + sectionId, }; if ( diff --git a/src/core/features/course/components/course-format/course-format.html b/src/core/features/course/components/course-format/course-format.html index 6d62db904..733821a0f 100644 --- a/src/core/features/course/components/course-format/course-format.html +++ b/src/core/features/course/components/course-format/course-format.html @@ -14,7 +14,7 @@ [value]="accordionMultipleValue"> + [viewedModules]="viewedModules" [collapsible]="false" [subSections]="subSections" /> @@ -31,7 +31,7 @@ section.id !== stealthModulesSectionId && !section.component) { + [collapsible]="true" [subSections]="subSections" /> } } 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 a029d20c3..a0f28742c 100644 --- a/src/core/features/course/components/course-format/course-format.ts +++ b/src/core/features/course/components/course-format/course-format.ts @@ -88,7 +88,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { @Input({ required: true }) course!: CoreCourseAnyCourseData; // The course to render. @Input() sections: CoreCourseSectionToDisplay[] = []; // List of course sections. - @Input() subSections: CoreCourseSectionToDisplay[] = []; // List of course subsections. @Input() initialSectionId?: number; // The section to load first (by ID). @Input() initialSectionNumber?: number; // The section to load first (by number). @Input() initialBlockInstanceId?: number; // The instance to focus. @@ -125,6 +124,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { displayCourseIndex = false; displayBlocks = false; hasBlocks = false; + subSections: CoreCourseSectionToDisplay[] = []; // List of course subsections. selectedSection?: CoreCourseSectionToDisplay; previousSection?: CoreCourseSectionToDisplay; nextSection?: CoreCourseSectionToDisplay; @@ -326,6 +326,26 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { this.loaded = true; this.sectionChanged(sections[0]); } else if (this.initialSectionId || this.initialSectionNumber !== undefined) { + const subSection = this.subSections.find((section) => section.id === this.initialSectionId || + (section.section !== undefined && section.section === this.initialSectionNumber)); + if (subSection) { + // The section is a subsection, load the parent section. + this.sections.some((section) => { + const module = section.modules.find((module) => + subSection.itemid === module.instance && module.modname === 'subsection'); + if (module) { + this.initialSectionId = module.section; + this.initialSectionNumber = undefined; + + return true; + } + + return false; + }); + + this.setInputData(); + } + // We have an input indicating the section ID to load. Search the section. const section = sections.find((section) => section.id === this.initialSectionId || 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 49071d178..8d4bebb08 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,6 @@ - + +

@@ -36,13 +36,69 @@ - + + @if (module.subsection) { + + } @else { + } + + +
+ + + +

+ +

+
+ + {{ 'core.course.hiddenfromstudents' | translate }} + +
+
+ + {{ 'core.notavailable' | translate }} + +
+
+ + + + + + +
+
+ {{highlightedName}} +
+ + + + + + + + + @if (module.subsection) { + + } @else { + + } + +
diff --git a/src/core/features/course/components/course-section/course-section.ts b/src/core/features/course/components/course-section/course-section.ts index 117133706..1c3b9ecae 100644 --- a/src/core/features/course/components/course-section/course-section.ts +++ b/src/core/features/course/components/course-section/course-section.ts @@ -15,9 +15,12 @@ import { Component, HostBinding, Input, + OnChanges, OnInit, + SimpleChange, } from '@angular/core'; import { + CoreCourseModuleData, CoreCourseSection, } from '@features/course/services/course-helper'; import { CoreSharedModule } from '@/core/shared.module'; @@ -41,10 +44,11 @@ import { CoreCourseFormatDelegate } from '@features/course/services/format-deleg CoreCourseComponentsModule, ], }) -export class CoreCourseSectionComponent implements OnInit { +export class CoreCourseSectionComponent implements OnInit, OnChanges { @Input({ required: true }) course!: CoreCourseAnyCourseData; // The course to render. @Input({ required: true }) section!: CoreCourseSectionToDisplay; + @Input() subSections: CoreCourseSectionToDisplay[] = []; // List of subsections in the course. @Input({ transform: toBoolean }) collapsible = true; // Whether the section can be collapsed. @Input() lastModuleViewed?: CoreCourseViewedModulesDBRecord; @Input() viewedModules: Record = {}; @@ -54,6 +58,7 @@ export class CoreCourseSectionComponent implements OnInit { return this.collapsible ? 'collapsible' : 'non-collapsible'; } + modules: CoreCourseModuleToDisplay[] = []; completionStatusIncomplete = CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE; highlightedName?: string; // Name to highlight. @@ -66,8 +71,28 @@ export class CoreCourseSectionComponent implements OnInit { : undefined; } + /** + * @inheritdoc + */ + ngOnChanges(changes: { [name: string]: SimpleChange }): void { + if (changes.section && this.section) { + this.modules = this.section.modules; + + this.modules.forEach((module) => { + if (module.modname === 'subsection') { + module.subSection = this.subSections.find((section) => + section.component === 'mod_subsection' && section.itemid === module.instance); + } + }); + } + } + } +type CoreCourseModuleToDisplay = CoreCourseModuleData & { + subSection?: CoreCourseSectionToDisplay; +}; + export type CoreCourseSectionToDisplay = CoreCourseSection & { highlighted?: boolean; expanded?: boolean; // The aim of this property is to avoid DOM overloading. diff --git a/src/core/features/course/services/course.ts b/src/core/features/course/services/course.ts index 902d253eb..34ae1d688 100644 --- a/src/core/features/course/services/course.ts +++ b/src/core/features/course/services/course.ts @@ -990,7 +990,7 @@ export class CoreCourseProvider { map(sections => { const siteHomeId = site.getSiteHomeId(); let showSections = true; - if (courseId == siteHomeId) { + if (courseId === siteHomeId) { const storedNumSections = site.getStoredConfig('numsections'); showSections = storedNumSections !== undefined && !!storedNumSections; } @@ -1770,6 +1770,8 @@ type CoreCourseGetContentsWSSection = { uservisible?: boolean; // Is the section visible for the user?. availabilityinfo?: string; // Availability information. modules: CoreCourseGetContentsWSModule[]; // List of module. + component?: string; // @since 4.5 The delegate component of this section if any. + itemid?: number; // @since 4.5 The optional item id delegate component can use to identify its instance. }; /**