Skip to content

Commit 0b4c3dc

Browse files
committed
MOBILE-4442 course: Create a separate component to manage sections
1 parent 647fdf8 commit 0b4c3dc

File tree

6 files changed

+169
-101
lines changed

6 files changed

+169
-101
lines changed

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

Lines changed: 10 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,29 @@
88
<core-loading [hideUntil]="loaded">
99

1010
<!-- Single section. -->
11-
<div *ngIf="selectedSection && selectedSection.id !== allSectionsId" class="single-section list-item-limited-width">
11+
<div *ngIf="selectedSection && selectedSection.id !== allSectionsId" class="list-item-limited-width">
1212
<core-dynamic-component [component]="singleSectionComponent" [data]="data">
13-
<ion-accordion-group [readonly]="true" value="single">
14-
<ng-container *ngTemplateOutlet="sectionTemplate; context: {section: selectedSection, sectionId: 'single'}" />
13+
<ion-accordion-group [readonly]="true" value="non-collapsible">
14+
<core-course-section *ngIf="!selectedSection.hiddenbynumsections && selectedSection.id !== allSectionsId &&
15+
selectedSection.id !== stealthModulesSectionId" [course]="course" [section]="selectedSection"
16+
[lastModuleViewed]="lastModuleViewed" [viewedModules]="viewedModules" [collapsible]="false" />
1517
</ion-accordion-group>
1618
<core-empty-box *ngIf="!selectedSection.hasContent" icon="fas-table-cells-large"
1719
[message]="'core.course.nocontentavailable' | translate" />
1820
</core-dynamic-component>
1921
</div>
2022

2123
<!-- Multiple sections. -->
22-
<div *ngIf="selectedSection && selectedSection.id === allSectionsId" class="multiple-sections list-item-limited-width">
24+
<div *ngIf="selectedSection && selectedSection.id === allSectionsId" class="list-item-limited-width">
2325
<core-dynamic-component [component]="allSectionsComponent" [data]="data">
2426
<ion-accordion-group [multiple]="true" (ionChange)="accordionMultipleChange($event.detail)" [value]="accordionMultipleValue"
2527
#accordionMultiple>
2628
@for (section of sections; track section.id) {
2729
@if ($index <= lastShownSectionIndex) {
28-
<ng-container *ngTemplateOutlet="sectionTemplate; context: {section: section, sectionId: section.id}" />
30+
<core-course-section
31+
*ngIf="!section.hiddenbynumsections && section.id !== allSectionsId && section.id !== stealthModulesSectionId"
32+
[course]="course" [section]="section" [lastModuleViewed]="lastModuleViewed" [viewedModules]="viewedModules"
33+
[collapsible]="true" />
2934
}
3035
}
3136
</ion-accordion-group>
@@ -65,55 +70,3 @@
6570
<ion-icon name="fas-list-ul" aria-hidden="true" />
6671
</ion-fab-button>
6772
</ion-fab>
68-
69-
<!-- Template to render a section. -->
70-
<ng-template #sectionTemplate let-section="section" let-sectionId="sectionId">
71-
<ion-accordion *ngIf="!section.hiddenbynumsections && section.id !== allSectionsId && section.id !== stealthModulesSectionId"
72-
class="core-course-module-list-wrapper" [id]="section.id"
73-
[attr.aria-labelledby]="section.name ? 'core-section-name-' + section.id : null" [value]="''+sectionId" toggleIconSlot="start">
74-
<ion-item class="course-section divider" [class.item-dimmed]="section.visible === 0 || section.uservisible === false" slot="header">
75-
<ion-label class="ion-text-wrap">
76-
<h2 *ngIf="section.name" class="big" [id]="'core-section-name-' + section.id">
77-
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course.id" />
78-
</h2>
79-
<div *ngIf="section.visible === 0 && section.uservisible !== false">
80-
<ion-badge color="warning">
81-
{{ 'core.course.hiddenfromstudents' | translate }}
82-
</ion-badge>
83-
</div>
84-
<div *ngIf="section.visible === 0 && section.uservisible === false">
85-
<ion-badge color="warning">
86-
{{ 'core.notavailable' | translate }}
87-
</ion-badge>
88-
</div>
89-
<div *ngIf="section.availabilityinfo">
90-
<ion-chip class="clickable">
91-
<ion-icon name="fas-lock" [attr.aria-label]="'core.restricted' | translate" />
92-
<ion-label>
93-
<core-format-text [text]=" section.availabilityinfo" contextLevel="course" [contextInstanceId]="course.id" />
94-
</ion-label>
95-
</ion-chip>
96-
</div>
97-
</ion-label>
98-
<ion-badge *ngIf="section.highlighted && highlighted" slot="end">{{highlighted}}</ion-badge>
99-
</ion-item>
100-
101-
<div slot="content">
102-
<ng-container *ngIf="section.expanded">
103-
<ion-item class="ion-text-wrap section-summary" *ngIf="section.summary">
104-
<ion-label>
105-
<core-format-text [text]="section.summary" contextLevel="course" [contextInstanceId]="course.id" />
106-
</ion-label>
107-
</ion-item>
108-
109-
<ng-container *ngFor="let module of section.modules">
110-
<core-course-module *ngIf="module.visibleoncoursepage !== 0" [module]="module" [section]="section"
111-
[showActivityDates]="course.showactivitydates" [showCompletionConditions]="course.showcompletionconditions"
112-
[isLastViewed]="lastModuleViewed && lastModuleViewed.cmId === module.id"
113-
[class.core-course-module-not-viewed]="
114-
!viewedModules[module.id] && (!module.completiondata || module.completiondata.state === completionStatusIncomplete)" />
115-
</ng-container>
116-
</ng-container>
117-
</div>
118-
</ion-accordion>
119-
</ng-template>

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

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,30 +10,3 @@
1010
margin-right: 4px;
1111
}
1212
}
13-
14-
.course-section {
15-
--inner-padding-end: 12px;
16-
}
17-
18-
.multiple-sections .core-course-module-list-wrapper {
19-
border: var(--ion-card-border-width) solid var(--ion-card-border-color);
20-
border-radius: var(--ion-card-radius);
21-
margin: 8px 4px;
22-
width: calc(100% - 8px);
23-
24-
ion-card {
25-
--ion-card-background: transparent;
26-
}
27-
28-
ion-item.divider.course-section {
29-
--background: transparent;
30-
}
31-
}
32-
33-
.single-section ::ng-deep {
34-
ion-item.divider.course-section {
35-
ion-icon.ion-accordion-toggle-icon {
36-
display: none;
37-
}
38-
}
39-
}

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

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
14-
1514
import {
1615
Component,
1716
Input,
@@ -31,7 +30,6 @@ import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-comp
3130
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
3231
import {
3332
CoreCourse,
34-
CoreCourseModuleCompletionStatus,
3533
CoreCourseProvider,
3634
} from '@features/course/services/course';
3735
import {
@@ -56,12 +54,12 @@ import { ContextLevel } from '@/core/constants';
5654
import { CoreModals } from '@services/modals';
5755
import { CoreSharedModule } from '@/core/shared.module';
5856
import { CoreBlockComponentsModule } from '@features/block/components/components.module';
59-
import { CoreCourseComponentsModule } from '../components.module';
6057
import { CoreSites } from '@services/sites';
6158
import { COURSE_ALL_SECTIONS_PREFERRED_PREFIX, COURSE_EXPANDED_SECTIONS_PREFIX } from '@features/course/constants';
6259
import { toBoolean } from '@/core/transforms/boolean';
6360
import { CoreInfiniteLoadingComponent } from '@components/infinite-loading/infinite-loading';
6461
import { CoreSite } from '@classes/sites/site';
62+
import { CoreCourseSectionComponent, CoreCourseSectionToDisplay } from '../course-section/course-section';
6563

6664
/**
6765
* Component to display course contents using a certain format. If the format isn't found, use default one.
@@ -76,12 +74,12 @@ import { CoreSite } from '@classes/sites/site';
7674
@Component({
7775
selector: 'core-course-format',
7876
templateUrl: 'course-format.html',
79-
styleUrls: ['course-format.scss'],
77+
styleUrl: 'course-format.scss',
8078
standalone: true,
8179
imports: [
8280
CoreSharedModule,
81+
CoreCourseSectionComponent,
8382
CoreBlockComponentsModule,
84-
CoreCourseComponentsModule,
8583
],
8684
})
8785
export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
@@ -129,13 +127,11 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
129127
selectedSection?: CoreCourseSectionToDisplay;
130128
previousSection?: CoreCourseSectionToDisplay;
131129
nextSection?: CoreCourseSectionToDisplay;
132-
allSectionsId: number = CoreCourseProvider.ALL_SECTIONS_ID;
133-
stealthModulesSectionId: number = CoreCourseProvider.STEALTH_MODULES_SECTION_ID;
130+
allSectionsId = CoreCourseProvider.ALL_SECTIONS_ID;
131+
stealthModulesSectionId = CoreCourseProvider.STEALTH_MODULES_SECTION_ID;
134132
loaded = false;
135-
highlighted?: string;
136133
lastModuleViewed?: CoreCourseViewedModulesDBRecord;
137134
viewedModules: Record<number, boolean> = {};
138-
completionStatusIncomplete = CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE;
139135

140136
communicationRoomUrl?: string;
141137

@@ -256,7 +252,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
256252
// Format has changed or it's the first time, load all the components.
257253
this.lastCourseFormat = this.course.format;
258254

259-
this.highlighted = CoreCourseFormatDelegate.getSectionHightlightedName(this.course);
260255
const currentSectionData = await CoreCourseFormatDelegate.getCurrentSection(this.course, this.sections);
261256
currentSectionData.section.highlighted = true;
262257

@@ -301,8 +296,8 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
301296
* @param sections Sections to treat.
302297
*/
303298
protected async treatSections(sections: CoreCourseSectionToDisplay[]): Promise<void> {
304-
const hasAllSections = sections[0].id == CoreCourseProvider.ALL_SECTIONS_ID;
305-
const hasSeveralSections = sections.length > 2 || (sections.length == 2 && !hasAllSections);
299+
const hasAllSections = sections[0].id === CoreCourseProvider.ALL_SECTIONS_ID;
300+
const hasSeveralSections = sections.length > 2 || (sections.length === 2 && !hasAllSections);
306301

307302
await this.initializeViewedModules();
308303
if (this.selectedSection) {
@@ -820,8 +815,3 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
820815
}
821816

822817
}
823-
824-
type CoreCourseSectionToDisplay = CoreCourseSection & {
825-
highlighted?: boolean;
826-
expanded?: boolean; // The aim of this property is to avoid DOM overloading.
827-
};
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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">
4+
<ion-item class="course-section divider" [class.item-dimmed]="section.visible === 0 || section.uservisible === false" slot="header">
5+
<ion-label class="ion-text-wrap">
6+
<h2 *ngIf="section.name" class="big" [id]="'core-section-name-' + section.id">
7+
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course.id" />
8+
</h2>
9+
<div *ngIf="section.visible === 0 && section.uservisible !== false">
10+
<ion-badge color="warning">
11+
{{ 'core.course.hiddenfromstudents' | translate }}
12+
</ion-badge>
13+
</div>
14+
<div *ngIf="section.visible === 0 && section.uservisible === false">
15+
<ion-badge color="warning">
16+
{{ 'core.notavailable' | translate }}
17+
</ion-badge>
18+
</div>
19+
<div *ngIf="section.availabilityinfo">
20+
<ion-chip class="clickable">
21+
<ion-icon name="fas-lock" [attr.aria-label]="'core.restricted' | translate" />
22+
<ion-label>
23+
<core-format-text [text]=" section.availabilityinfo" contextLevel="course" [contextInstanceId]="course.id" />
24+
</ion-label>
25+
</ion-chip>
26+
</div>
27+
</ion-label>
28+
<ion-badge *ngIf="section.highlighted && highlightedName" slot="end">{{highlightedName}}</ion-badge>
29+
</ion-item>
30+
31+
<div slot="content">
32+
<ng-container *ngIf="section.expanded">
33+
<ion-item class="ion-text-wrap section-summary" *ngIf="section.summary">
34+
<ion-label>
35+
<core-format-text [text]="section.summary" contextLevel="course" [contextInstanceId]="course.id" />
36+
</ion-label>
37+
</ion-item>
38+
39+
<ng-container *ngFor="let module of section.modules">
40+
<core-course-module *ngIf="module.visibleoncoursepage !== 0" [module]="module" [section]="section"
41+
[showActivityDates]="course.showactivitydates" [showCompletionConditions]="course.showcompletionconditions"
42+
[isLastViewed]="lastModuleViewed && lastModuleViewed.cmId === module.id" [class.core-course-module-not-viewed]="
43+
!viewedModules[module.id] &&
44+
(!module.completiondata || module.completiondata.state === completionStatusIncomplete)" />
45+
</ng-container>
46+
</ng-container>
47+
</div>
48+
</ion-accordion>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
:host {
2+
.course-section {
3+
--inner-padding-end: 12px;
4+
}
5+
6+
&.collapsible {
7+
.core-course-module-list-wrapper {
8+
border: var(--ion-card-border-width) solid var(--ion-card-border-color);
9+
border-radius: var(--ion-card-radius);
10+
margin: 8px 4px;
11+
width: calc(100% - 8px);
12+
13+
ion-card {
14+
--ion-card-background: transparent;
15+
}
16+
17+
ion-item.divider.course-section {
18+
--background: transparent;
19+
}
20+
}
21+
}
22+
23+
&.non-collapsible ::ng-deep {
24+
ion-item.divider.course-section {
25+
ion-icon.ion-accordion-toggle-icon {
26+
display: none;
27+
}
28+
}
29+
}
30+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// (C) Copyright 2015 Moodle Pty Ltd.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
import {
15+
Component,
16+
HostBinding,
17+
Input,
18+
OnInit,
19+
} from '@angular/core';
20+
import {
21+
CoreCourseSection,
22+
} from '@features/course/services/course-helper';
23+
import { CoreSharedModule } from '@/core/shared.module';
24+
import { CoreCourseComponentsModule } from '../components.module';
25+
import { toBoolean } from '@/core/transforms/boolean';
26+
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
27+
import { CoreCourseViewedModulesDBRecord } from '@features/course/services/database/course';
28+
import { CoreCourseModuleCompletionStatus } from '@features/course/services/course';
29+
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
30+
31+
/**
32+
* Component to display course section.
33+
*/
34+
@Component({
35+
selector: 'core-course-section',
36+
templateUrl: 'course-section.html',
37+
styleUrl: 'course-section.scss',
38+
standalone: true,
39+
imports: [
40+
CoreSharedModule,
41+
CoreCourseComponentsModule,
42+
],
43+
})
44+
export class CoreCourseSectionComponent implements OnInit {
45+
46+
@Input({ required: true }) course!: CoreCourseAnyCourseData; // The course to render.
47+
@Input({ required: true }) section!: CoreCourseSectionToDisplay;
48+
@Input({ transform: toBoolean }) collapsible = true; // Whether the section can be collapsed.
49+
@Input() lastModuleViewed?: CoreCourseViewedModulesDBRecord;
50+
@Input() viewedModules: Record<number, boolean> = {};
51+
52+
@HostBinding('class')
53+
get collapsibleClass(): string {
54+
return this.collapsible ? 'collapsible' : 'non-collapsible';
55+
}
56+
57+
completionStatusIncomplete = CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE;
58+
highlightedName?: string; // Name to highlight.
59+
60+
/**
61+
* @inheritdoc
62+
*/
63+
ngOnInit(): void {
64+
this.highlightedName = this.section.highlighted && this.highlightedName === undefined
65+
? CoreCourseFormatDelegate.getSectionHightlightedName(this.course)
66+
: undefined;
67+
}
68+
69+
}
70+
71+
export type CoreCourseSectionToDisplay = CoreCourseSection & {
72+
highlighted?: boolean;
73+
expanded?: boolean; // The aim of this property is to avoid DOM overloading.
74+
};

0 commit comments

Comments
 (0)