Skip to content

Commit 95c4e0e

Browse files
committed
MOBILE-4442 subsection: Create subsection activity module
1 parent 0b4c3dc commit 95c4e0e

File tree

8 files changed

+278
-21
lines changed

8 files changed

+278
-21
lines changed

src/addons/mod/mod.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { AddonModPageModule } from './page/page.module';
3333
import { AddonModQuizModule } from './quiz/quiz.module';
3434
import { AddonModResourceModule } from './resource/resource.module';
3535
import { AddonModScormModule } from './scorm/scorm.module';
36+
import { AddonModSubsectionModule } from './subsection/subsection.module';
3637
import { AddonModSurveyModule } from './survey/survey.module';
3738
import { AddonModUrlModule } from './url/url.module';
3839
import { AddonModWikiModule } from './wiki/wiki.module';
@@ -59,6 +60,7 @@ import { AddonModWorkshopModule } from './workshop/workshop.module';
5960
AddonModQuizModule,
6061
AddonModResourceModule,
6162
AddonModScormModule,
63+
AddonModSubsectionModule,
6264
AddonModSurveyModule,
6365
AddonModUrlModule,
6466
AddonModWikiModule,
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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+
15+
import { Injectable } from '@angular/core';
16+
import { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler';
17+
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
18+
import { CoreCourse } from '@features/course/services/course';
19+
import { CoreLoadings } from '@services/loadings';
20+
import { CoreDomUtils } from '@services/utils/dom';
21+
import { makeSingleton } from '@singletons';
22+
import { AddonModSubsection } from '../subsection';
23+
24+
/**
25+
* Handler to treat links to subsection.
26+
*/
27+
@Injectable({ providedIn: 'root' })
28+
export class AddonModSubsectionIndexLinkHandlerService extends CoreContentLinksModuleIndexHandler {
29+
30+
name = 'AddonModSubsectionLinkHandler';
31+
32+
constructor() {
33+
super('AddonModSubsection', 'subsection', 'id');
34+
}
35+
36+
/**
37+
* @inheritdoc
38+
*/
39+
getActions(
40+
siteIds: string[],
41+
url: string,
42+
params: Record<string, string>,
43+
courseId?: number,
44+
): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
45+
return [{
46+
action: async(siteId) => {
47+
const modal = await CoreLoadings.show();
48+
const moduleId = Number(params.id);
49+
50+
try {
51+
// Get the module.
52+
const module = await CoreCourse.getModule(moduleId, courseId, undefined, true, false, siteId);
53+
54+
await AddonModSubsection.openSubsection(module, module.course, siteId);
55+
} catch (error) {
56+
CoreDomUtils.showErrorModalDefault(error, 'Error opening link.');
57+
} finally {
58+
modal.dismiss();
59+
}
60+
},
61+
}];
62+
}
63+
64+
}
65+
export const AddonModSubsectionIndexLinkHandler = makeSingleton(AddonModSubsectionIndexLinkHandlerService);
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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+
15+
import { CoreConstants, ModPurpose } from '@/core/constants';
16+
import { Injectable } from '@angular/core';
17+
import { CoreModuleHandlerBase } from '@features/course/classes/module-base-handler';
18+
import { CoreCourseModuleData } from '@features/course/services/course-helper';
19+
import {
20+
CoreCourseModuleDelegate,
21+
CoreCourseModuleHandler,
22+
CoreCourseModuleHandlerData,
23+
} from '@features/course/services/module-delegate';
24+
import { CoreDomUtils } from '@services/utils/dom';
25+
import { makeSingleton } from '@singletons';
26+
import { AddonModSubsection } from '../subsection';
27+
28+
/**
29+
* Handler to support subsection modules.
30+
*
31+
* This is merely to disable the siteplugin.
32+
*/
33+
@Injectable({ providedIn: 'root' })
34+
export class AddonModSubsectionModuleHandlerService extends CoreModuleHandlerBase implements CoreCourseModuleHandler {
35+
36+
name = 'AddonModSubsection';
37+
modName = 'subsection';
38+
39+
supportedFeatures = {
40+
[CoreConstants.FEATURE_MOD_ARCHETYPE]: CoreConstants.MOD_ARCHETYPE_RESOURCE,
41+
[CoreConstants.FEATURE_GROUPS]: false,
42+
[CoreConstants.FEATURE_GROUPINGS]: false,
43+
[CoreConstants.FEATURE_MOD_INTRO]: false,
44+
[CoreConstants.FEATURE_COMPLETION_TRACKS_VIEWS]: true,
45+
[CoreConstants.FEATURE_GRADE_HAS_GRADE]: false,
46+
[CoreConstants.FEATURE_GRADE_OUTCOMES]: false,
47+
[CoreConstants.FEATURE_BACKUP_MOODLE2]: true,
48+
[CoreConstants.FEATURE_SHOW_DESCRIPTION]: false,
49+
[CoreConstants.FEATURE_MOD_PURPOSE]: ModPurpose.MOD_PURPOSE_CONTENT,
50+
};
51+
52+
/**
53+
* @inheritdoc
54+
*/
55+
getData(module: CoreCourseModuleData): CoreCourseModuleHandlerData {
56+
return {
57+
icon: CoreCourseModuleDelegate.getModuleIconSrc(module.modname, module.modicon),
58+
title: module.name,
59+
a11yTitle: '',
60+
class: 'addon-mod-subsection-handler',
61+
hasCustomCmListItem: true,
62+
action: async(event, module, courseId) => {
63+
try {
64+
await AddonModSubsection.openSubsection(module, courseId);
65+
} catch (error) {
66+
CoreDomUtils.showErrorModalDefault(error, 'Error opening subsection.');
67+
}
68+
},
69+
};
70+
}
71+
72+
/**
73+
* @inheritdoc
74+
*/
75+
async getMainComponent(): Promise<undefined> {
76+
// There's no need to implement this because subsection cannot be used in singleactivity course format.
77+
return;
78+
}
79+
80+
/**
81+
* @inheritdoc
82+
*/
83+
getIconSrc(): string {
84+
return '';
85+
}
86+
87+
}
88+
export const AddonModSubsectionModuleHandler = makeSingleton(AddonModSubsectionModuleHandlerService);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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+
15+
import { Injectable } from '@angular/core';
16+
import { CoreCourse } from '@features/course/services/course';
17+
import { CoreCourseModuleData, CoreCourseHelper } from '@features/course/services/course-helper';
18+
import { CoreSites } from '@services/sites';
19+
import { makeSingleton } from '@singletons';
20+
21+
/**
22+
* Service that provides some features for subsections.
23+
*/
24+
@Injectable({ providedIn: 'root' })
25+
export class AddonModSubsectionProvider {
26+
27+
/**
28+
* Open a subsection.
29+
*/
30+
async openSubsection(module: CoreCourseModuleData , courseId?: number, siteId?: string): Promise<void> {
31+
if (!courseId) {
32+
courseId = module.course;
33+
}
34+
35+
const pageParams = {
36+
sectionId: module.section,
37+
};
38+
39+
if (
40+
(!siteId || siteId === CoreSites.getCurrentSiteId()) &&
41+
CoreCourse.currentViewIsCourse(courseId)
42+
) {
43+
CoreCourse.selectCourseTab('', pageParams);
44+
} else {
45+
await CoreCourseHelper.getAndOpenCourse(courseId, pageParams, siteId);
46+
}
47+
}
48+
49+
}
50+
export const AddonModSubsection = makeSingleton(AddonModSubsectionProvider);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
15+
import { APP_INITIALIZER, NgModule } from '@angular/core';
16+
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
17+
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
18+
import { AddonModSubsectionIndexLinkHandler } from './services/handlers/index-link';
19+
import { AddonModSubsectionModuleHandler } from './services/handlers/module';
20+
21+
@NgModule({
22+
providers: [
23+
{
24+
provide: APP_INITIALIZER,
25+
multi: true,
26+
useValue: () => {
27+
CoreCourseModuleDelegate.registerHandler(AddonModSubsectionModuleHandler.instance);
28+
CoreContentLinksDelegate.registerHandler(AddonModSubsectionIndexLinkHandler.instance);
29+
},
30+
},
31+
],
32+
})
33+
export class AddonModSubsectionModule {}

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

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@
1010
<!-- Single section. -->
1111
<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="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" />
13+
<ion-accordion-group [multiple]="true" (ionChange)="accordionMultipleChange($event.detail)"
14+
[value]="accordionMultipleValue">
15+
<core-course-section *ngIf="!selectedSection.hiddenbynumsections && selectedSection.id !== stealthModulesSectionId &&
16+
!selectedSection.component" [course]="course" [section]="selectedSection" [lastModuleViewed]="lastModuleViewed"
17+
[viewedModules]="viewedModules" [collapsible]="false" [sections]="subSections" />
1718
</ion-accordion-group>
1819
<core-empty-box *ngIf="!selectedSection.hasContent" icon="fas-table-cells-large"
1920
[message]="'core.course.nocontentavailable' | translate" />
@@ -23,14 +24,14 @@
2324
<!-- Multiple sections. -->
2425
<div *ngIf="selectedSection && selectedSection.id === allSectionsId" class="list-item-limited-width">
2526
<core-dynamic-component [component]="allSectionsComponent" [data]="data">
26-
<ion-accordion-group [multiple]="true" (ionChange)="accordionMultipleChange($event.detail)" [value]="accordionMultipleValue"
27-
#accordionMultiple>
27+
<ion-accordion-group [multiple]="true" (ionChange)="accordionMultipleChange($event.detail)"
28+
[value]="accordionMultipleValue">
2829
@for (section of sections; track section.id) {
29-
@if ($index <= lastShownSectionIndex) {
30+
@if ($index <= lastShownSectionIndex && !section.hiddenbynumsections && section.id !== allSectionsId &&
31+
section.id !== stealthModulesSectionId && !section.component) {
3032
<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" />
33+
[course]="course" [section]="section" [lastModuleViewed]="lastModuleViewed" [viewedModules]="viewedModules"
34+
[collapsible]="true" [sections]="subSections" />
3435
}
3536
}
3637
</ion-accordion-group>

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

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ 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.
9192
@Input() initialSectionId?: number; // The section to load first (by ID).
9293
@Input() initialSectionNumber?: number; // The section to load first (by number).
9394
@Input() initialBlockInstanceId?: number; // The instance to focus.
@@ -225,6 +226,9 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
225226
}
226227

227228
if (changes.sections && this.sections) {
229+
this.subSections = this.sections.filter((section) => section.component === 'mod_subsection');
230+
this.sections = this.sections.filter((section) => section.component !== 'mod_subsection');
231+
228232
this.treatSections(this.sections);
229233
}
230234
this.changeDetectorRef.markForCheck();
@@ -746,9 +750,14 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
746750
* Save expanded sections for the course.
747751
*/
748752
protected async saveExpandedSections(): Promise<void> {
749-
const expandedSections = this.sections.filter((section) => section.expanded).map((section) => section.id).join(',');
753+
let expandedSections = this.sections.filter((section) => section.expanded && section.id > 0).map((section) => section.id);
754+
expandedSections =
755+
expandedSections.concat(this.subSections.filter((section) => section.expanded).map((section) => section.id));
750756

751-
await this.currentSite?.setLocalSiteConfig(`${COURSE_EXPANDED_SECTIONS_PREFIX}${this.course.id}`, expandedSections);
757+
await this.currentSite?.setLocalSiteConfig(
758+
`${COURSE_EXPANDED_SECTIONS_PREFIX}${this.course.id}`,
759+
expandedSections.join(','),
760+
);
752761
}
753762

754763
/**
@@ -766,6 +775,11 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
766775
this.accordionMultipleValue.push(section.id.toString());
767776
});
768777

778+
this.subSections.forEach((section) => {
779+
section.expanded = true;
780+
this.accordionMultipleValue.push(section.id.toString());
781+
});
782+
769783
return;
770784
}
771785

@@ -774,6 +788,10 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
774788
this.sections.forEach((section) => {
775789
section.expanded = this.accordionMultipleValue.includes(section.id.toString());
776790
});
791+
792+
this.subSections.forEach((section) => {
793+
section.expanded = this.accordionMultipleValue.includes(section.id.toString());
794+
});
777795
}
778796

779797
/**
@@ -787,9 +805,17 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
787805
section.expanded = false;
788806
});
789807

808+
this.subSections.forEach((section) => {
809+
section.expanded = false;
810+
});
811+
790812
sectionIds?.forEach((sectionId) => {
791813
const sId = Number(sectionId);
792-
const section = this.sections.find((section) => section.id === sId);
814+
let section = this.sections.find((section) => section.id === sId);
815+
if (!section) {
816+
section = this.subSections.find((section) => section.id === sId);
817+
}
818+
793819
if (section) {
794820
section.expanded = true;
795821
}

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,4 @@
1919
}
2020
}
2121
}
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-
}
3022
}

0 commit comments

Comments
 (0)