MOBILE-4442 course: Open subsections as sections inside course
parent
95c4e0e225
commit
1527e31cd6
|
@ -51,7 +51,7 @@ export class AddonModSubsectionIndexLinkHandlerService extends CoreContentLinksM
|
||||||
// Get the module.
|
// Get the module.
|
||||||
const module = await CoreCourse.getModule(moduleId, courseId, undefined, true, false, siteId);
|
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) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'Error opening link.');
|
CoreDomUtils.showErrorModalDefault(error, 'Error opening link.');
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -59,9 +59,9 @@ export class AddonModSubsectionModuleHandlerService extends CoreModuleHandlerBas
|
||||||
a11yTitle: '',
|
a11yTitle: '',
|
||||||
class: 'addon-mod-subsection-handler',
|
class: 'addon-mod-subsection-handler',
|
||||||
hasCustomCmListItem: true,
|
hasCustomCmListItem: true,
|
||||||
action: async(event, module, courseId) => {
|
action: async(event, module) => {
|
||||||
try {
|
try {
|
||||||
await AddonModSubsection.openSubsection(module, courseId);
|
await AddonModSubsection.openSubsection(module.section, module.course);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'Error opening subsection.');
|
CoreDomUtils.showErrorModalDefault(error, 'Error opening subsection.');
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { CoreCourse } from '@features/course/services/course';
|
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 { CoreSites } from '@services/sites';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
|
|
||||||
|
@ -26,14 +26,15 @@ export class AddonModSubsectionProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a subsection.
|
* 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<void> {
|
async openSubsection(sectionId: number, courseId: number, siteId?: string): Promise<void> {
|
||||||
if (!courseId) {
|
|
||||||
courseId = module.course;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pageParams = {
|
const pageParams = {
|
||||||
sectionId: module.section,
|
sectionId,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
[value]="accordionMultipleValue">
|
[value]="accordionMultipleValue">
|
||||||
<core-course-section *ngIf="!selectedSection.hiddenbynumsections && selectedSection.id !== stealthModulesSectionId &&
|
<core-course-section *ngIf="!selectedSection.hiddenbynumsections && selectedSection.id !== stealthModulesSectionId &&
|
||||||
!selectedSection.component" [course]="course" [section]="selectedSection" [lastModuleViewed]="lastModuleViewed"
|
!selectedSection.component" [course]="course" [section]="selectedSection" [lastModuleViewed]="lastModuleViewed"
|
||||||
[viewedModules]="viewedModules" [collapsible]="false" [sections]="subSections" />
|
[viewedModules]="viewedModules" [collapsible]="false" [subSections]="subSections" />
|
||||||
</ion-accordion-group>
|
</ion-accordion-group>
|
||||||
<core-empty-box *ngIf="!selectedSection.hasContent" icon="fas-table-cells-large"
|
<core-empty-box *ngIf="!selectedSection.hasContent" icon="fas-table-cells-large"
|
||||||
[message]="'core.course.nocontentavailable' | translate" />
|
[message]="'core.course.nocontentavailable' | translate" />
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
section.id !== stealthModulesSectionId && !section.component) {
|
section.id !== stealthModulesSectionId && !section.component) {
|
||||||
<core-course-section
|
<core-course-section
|
||||||
[course]="course" [section]="section" [lastModuleViewed]="lastModuleViewed" [viewedModules]="viewedModules"
|
[course]="course" [section]="section" [lastModuleViewed]="lastModuleViewed" [viewedModules]="viewedModules"
|
||||||
[collapsible]="true" [sections]="subSections" />
|
[collapsible]="true" [subSections]="subSections" />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</ion-accordion-group>
|
</ion-accordion-group>
|
||||||
|
|
|
@ -88,7 +88,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
|
|
||||||
@Input({ required: true }) course!: CoreCourseAnyCourseData; // The course to render.
|
@Input({ required: true }) course!: CoreCourseAnyCourseData; // The course to render.
|
||||||
@Input() sections: CoreCourseSectionToDisplay[] = []; // List of course sections.
|
@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() initialSectionId?: number; // The section to load first (by ID).
|
||||||
@Input() initialSectionNumber?: number; // The section to load first (by number).
|
@Input() initialSectionNumber?: number; // The section to load first (by number).
|
||||||
@Input() initialBlockInstanceId?: number; // The instance to focus.
|
@Input() initialBlockInstanceId?: number; // The instance to focus.
|
||||||
|
@ -125,6 +124,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
displayCourseIndex = false;
|
displayCourseIndex = false;
|
||||||
displayBlocks = false;
|
displayBlocks = false;
|
||||||
hasBlocks = false;
|
hasBlocks = false;
|
||||||
|
subSections: CoreCourseSectionToDisplay[] = []; // List of course subsections.
|
||||||
selectedSection?: CoreCourseSectionToDisplay;
|
selectedSection?: CoreCourseSectionToDisplay;
|
||||||
previousSection?: CoreCourseSectionToDisplay;
|
previousSection?: CoreCourseSectionToDisplay;
|
||||||
nextSection?: CoreCourseSectionToDisplay;
|
nextSection?: CoreCourseSectionToDisplay;
|
||||||
|
@ -326,6 +326,26 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
this.sectionChanged(sections[0]);
|
this.sectionChanged(sections[0]);
|
||||||
} else if (this.initialSectionId || this.initialSectionNumber !== undefined) {
|
} 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.
|
// We have an input indicating the section ID to load. Search the section.
|
||||||
const section = sections.find((section) =>
|
const section = sections.find((section) =>
|
||||||
section.id === this.initialSectionId ||
|
section.id === this.initialSectionId ||
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<ion-accordion *ngIf="!section.hiddenbynumsections" class="core-course-module-list-wrapper" [id]="section.id"
|
<ion-accordion *ngIf="collapsible" class="core-course-module-list-wrapper" [id]="section.id"
|
||||||
[attr.aria-labelledby]="section.name ? 'core-section-name-' + section.id : null" [value]="collapsible ? section.id : 'non-collapsible'"
|
[attr.aria-labelledby]="section.name ? 'core-section-name-' + section.id : null" [value]="section.id" toggleIconSlot="start">
|
||||||
toggleIconSlot="start">
|
|
||||||
<ion-item class="course-section divider" [class.item-dimmed]="section.visible === 0 || section.uservisible === false" slot="header">
|
<ion-item class="course-section divider" [class.item-dimmed]="section.visible === 0 || section.uservisible === false" slot="header">
|
||||||
<ion-label class="ion-text-wrap">
|
<ion-label class="ion-text-wrap">
|
||||||
<h2 *ngIf="section.name" class="big" [id]="'core-section-name-' + section.id">
|
<h2 *ngIf="section.name" class="big" [id]="'core-section-name-' + section.id">
|
||||||
|
@ -36,13 +36,69 @@
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ng-container *ngFor="let module of section.modules">
|
<ng-container *ngFor="let module of modules">
|
||||||
|
@if (module.subsection) {
|
||||||
|
<core-course-section [course]="course" [section]="module.subsection" [lastModuleViewed]="lastModuleViewed"
|
||||||
|
[viewedModules]="viewedModules" [collapsible]="true" />
|
||||||
|
} @else {
|
||||||
<core-course-module *ngIf="module.visibleoncoursepage !== 0" [module]="module" [section]="section"
|
<core-course-module *ngIf="module.visibleoncoursepage !== 0" [module]="module" [section]="section"
|
||||||
[showActivityDates]="course.showactivitydates" [showCompletionConditions]="course.showcompletionconditions"
|
[showActivityDates]="course.showactivitydates" [showCompletionConditions]="course.showcompletionconditions"
|
||||||
[isLastViewed]="lastModuleViewed && lastModuleViewed.cmId === module.id" [class.core-course-module-not-viewed]="
|
[isLastViewed]="lastModuleViewed && lastModuleViewed.cmId === module.id" [class.core-course-module-not-viewed]="
|
||||||
!viewedModules[module.id] &&
|
!viewedModules[module.id] &&
|
||||||
(!module.completiondata || module.completiondata.state === completionStatusIncomplete)" />
|
(!module.completiondata || module.completiondata.state === completionStatusIncomplete)" />
|
||||||
|
}
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</ion-accordion>
|
</ion-accordion>
|
||||||
|
|
||||||
|
|
||||||
|
<div *ngIf="!collapsible" class="core-course-module-list-wrapper" [id]="section.id"
|
||||||
|
[attr.aria-labelledby]="section.name ? 'core-section-name-' + section.id : null">
|
||||||
|
|
||||||
|
<ion-item class="course-section divider" [class.item-dimmed]="section.visible === 0 || section.uservisible === false">
|
||||||
|
<ion-label class="ion-text-wrap">
|
||||||
|
<h2 *ngIf="section.name" class="big" [id]="'core-section-name-' + section.id">
|
||||||
|
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course.id" />
|
||||||
|
</h2>
|
||||||
|
<div *ngIf="section.visible === 0 && section.uservisible !== false">
|
||||||
|
<ion-badge color="warning">
|
||||||
|
{{ 'core.course.hiddenfromstudents' | translate }}
|
||||||
|
</ion-badge>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="section.visible === 0 && section.uservisible === false">
|
||||||
|
<ion-badge color="warning">
|
||||||
|
{{ 'core.notavailable' | translate }}
|
||||||
|
</ion-badge>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="section.availabilityinfo">
|
||||||
|
<ion-chip class="clickable">
|
||||||
|
<ion-icon name="fas-lock" [attr.aria-label]="'core.restricted' | translate" />
|
||||||
|
<ion-label>
|
||||||
|
<core-format-text [text]=" section.availabilityinfo" contextLevel="course" [contextInstanceId]="course.id" />
|
||||||
|
</ion-label>
|
||||||
|
</ion-chip>
|
||||||
|
</div>
|
||||||
|
</ion-label>
|
||||||
|
<ion-badge *ngIf="section.highlighted && highlightedName" slot="end">{{highlightedName}}</ion-badge>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item class="ion-text-wrap section-summary" *ngIf="section.summary">
|
||||||
|
<ion-label>
|
||||||
|
<core-format-text [text]="section.summary" contextLevel="course" [contextInstanceId]="course.id" />
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ng-container *ngFor="let module of modules">
|
||||||
|
@if (module.subsection) {
|
||||||
|
<core-course-section [course]="course" [section]="module.subsection" [lastModuleViewed]="lastModuleViewed"
|
||||||
|
[viewedModules]="viewedModules" [collapsible]="true" />
|
||||||
|
} @else {
|
||||||
|
<core-course-module *ngIf="module.visibleoncoursepage !== 0" [module]="module" [section]="section"
|
||||||
|
[showActivityDates]="course.showactivitydates" [showCompletionConditions]="course.showcompletionconditions"
|
||||||
|
[isLastViewed]="lastModuleViewed && lastModuleViewed.cmId === module.id" [class.core-course-module-not-viewed]="
|
||||||
|
!viewedModules[module.id] &&
|
||||||
|
(!module.completiondata || module.completiondata.state === completionStatusIncomplete)" />
|
||||||
|
}
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
|
|
@ -15,9 +15,12 @@ import {
|
||||||
Component,
|
Component,
|
||||||
HostBinding,
|
HostBinding,
|
||||||
Input,
|
Input,
|
||||||
|
OnChanges,
|
||||||
OnInit,
|
OnInit,
|
||||||
|
SimpleChange,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {
|
import {
|
||||||
|
CoreCourseModuleData,
|
||||||
CoreCourseSection,
|
CoreCourseSection,
|
||||||
} from '@features/course/services/course-helper';
|
} from '@features/course/services/course-helper';
|
||||||
import { CoreSharedModule } from '@/core/shared.module';
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
|
@ -41,10 +44,11 @@ import { CoreCourseFormatDelegate } from '@features/course/services/format-deleg
|
||||||
CoreCourseComponentsModule,
|
CoreCourseComponentsModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class CoreCourseSectionComponent implements OnInit {
|
export class CoreCourseSectionComponent implements OnInit, OnChanges {
|
||||||
|
|
||||||
@Input({ required: true }) course!: CoreCourseAnyCourseData; // The course to render.
|
@Input({ required: true }) course!: CoreCourseAnyCourseData; // The course to render.
|
||||||
@Input({ required: true }) section!: CoreCourseSectionToDisplay;
|
@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({ transform: toBoolean }) collapsible = true; // Whether the section can be collapsed.
|
||||||
@Input() lastModuleViewed?: CoreCourseViewedModulesDBRecord;
|
@Input() lastModuleViewed?: CoreCourseViewedModulesDBRecord;
|
||||||
@Input() viewedModules: Record<number, boolean> = {};
|
@Input() viewedModules: Record<number, boolean> = {};
|
||||||
|
@ -54,6 +58,7 @@ export class CoreCourseSectionComponent implements OnInit {
|
||||||
return this.collapsible ? 'collapsible' : 'non-collapsible';
|
return this.collapsible ? 'collapsible' : 'non-collapsible';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
modules: CoreCourseModuleToDisplay[] = [];
|
||||||
completionStatusIncomplete = CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE;
|
completionStatusIncomplete = CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE;
|
||||||
highlightedName?: string; // Name to highlight.
|
highlightedName?: string; // Name to highlight.
|
||||||
|
|
||||||
|
@ -66,8 +71,28 @@ export class CoreCourseSectionComponent implements OnInit {
|
||||||
: undefined;
|
: 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 & {
|
export type CoreCourseSectionToDisplay = CoreCourseSection & {
|
||||||
highlighted?: boolean;
|
highlighted?: boolean;
|
||||||
expanded?: boolean; // The aim of this property is to avoid DOM overloading.
|
expanded?: boolean; // The aim of this property is to avoid DOM overloading.
|
||||||
|
|
|
@ -990,7 +990,7 @@ export class CoreCourseProvider {
|
||||||
map(sections => {
|
map(sections => {
|
||||||
const siteHomeId = site.getSiteHomeId();
|
const siteHomeId = site.getSiteHomeId();
|
||||||
let showSections = true;
|
let showSections = true;
|
||||||
if (courseId == siteHomeId) {
|
if (courseId === siteHomeId) {
|
||||||
const storedNumSections = site.getStoredConfig('numsections');
|
const storedNumSections = site.getStoredConfig('numsections');
|
||||||
showSections = storedNumSections !== undefined && !!storedNumSections;
|
showSections = storedNumSections !== undefined && !!storedNumSections;
|
||||||
}
|
}
|
||||||
|
@ -1770,6 +1770,8 @@ type CoreCourseGetContentsWSSection = {
|
||||||
uservisible?: boolean; // Is the section visible for the user?.
|
uservisible?: boolean; // Is the section visible for the user?.
|
||||||
availabilityinfo?: string; // Availability information.
|
availabilityinfo?: string; // Availability information.
|
||||||
modules: CoreCourseGetContentsWSModule[]; // List of module.
|
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.
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue