MOBILE-4442 subsection: Create subsection activity module
parent
0b4c3dc88e
commit
95c4e0e225
|
@ -33,6 +33,7 @@ import { AddonModPageModule } from './page/page.module';
|
||||||
import { AddonModQuizModule } from './quiz/quiz.module';
|
import { AddonModQuizModule } from './quiz/quiz.module';
|
||||||
import { AddonModResourceModule } from './resource/resource.module';
|
import { AddonModResourceModule } from './resource/resource.module';
|
||||||
import { AddonModScormModule } from './scorm/scorm.module';
|
import { AddonModScormModule } from './scorm/scorm.module';
|
||||||
|
import { AddonModSubsectionModule } from './subsection/subsection.module';
|
||||||
import { AddonModSurveyModule } from './survey/survey.module';
|
import { AddonModSurveyModule } from './survey/survey.module';
|
||||||
import { AddonModUrlModule } from './url/url.module';
|
import { AddonModUrlModule } from './url/url.module';
|
||||||
import { AddonModWikiModule } from './wiki/wiki.module';
|
import { AddonModWikiModule } from './wiki/wiki.module';
|
||||||
|
@ -59,6 +60,7 @@ import { AddonModWorkshopModule } from './workshop/workshop.module';
|
||||||
AddonModQuizModule,
|
AddonModQuizModule,
|
||||||
AddonModResourceModule,
|
AddonModResourceModule,
|
||||||
AddonModScormModule,
|
AddonModScormModule,
|
||||||
|
AddonModSubsectionModule,
|
||||||
AddonModSurveyModule,
|
AddonModSurveyModule,
|
||||||
AddonModUrlModule,
|
AddonModUrlModule,
|
||||||
AddonModWikiModule,
|
AddonModWikiModule,
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler';
|
||||||
|
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
|
||||||
|
import { CoreCourse } from '@features/course/services/course';
|
||||||
|
import { CoreLoadings } from '@services/loadings';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { AddonModSubsection } from '../subsection';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to treat links to subsection.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class AddonModSubsectionIndexLinkHandlerService extends CoreContentLinksModuleIndexHandler {
|
||||||
|
|
||||||
|
name = 'AddonModSubsectionLinkHandler';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('AddonModSubsection', 'subsection', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getActions(
|
||||||
|
siteIds: string[],
|
||||||
|
url: string,
|
||||||
|
params: Record<string, string>,
|
||||||
|
courseId?: number,
|
||||||
|
): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||||
|
return [{
|
||||||
|
action: async(siteId) => {
|
||||||
|
const modal = await CoreLoadings.show();
|
||||||
|
const moduleId = Number(params.id);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get the module.
|
||||||
|
const module = await CoreCourse.getModule(moduleId, courseId, undefined, true, false, siteId);
|
||||||
|
|
||||||
|
await AddonModSubsection.openSubsection(module, module.course, siteId);
|
||||||
|
} catch (error) {
|
||||||
|
CoreDomUtils.showErrorModalDefault(error, 'Error opening link.');
|
||||||
|
} finally {
|
||||||
|
modal.dismiss();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export const AddonModSubsectionIndexLinkHandler = makeSingleton(AddonModSubsectionIndexLinkHandlerService);
|
|
@ -0,0 +1,88 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { CoreConstants, ModPurpose } from '@/core/constants';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { CoreModuleHandlerBase } from '@features/course/classes/module-base-handler';
|
||||||
|
import { CoreCourseModuleData } from '@features/course/services/course-helper';
|
||||||
|
import {
|
||||||
|
CoreCourseModuleDelegate,
|
||||||
|
CoreCourseModuleHandler,
|
||||||
|
CoreCourseModuleHandlerData,
|
||||||
|
} from '@features/course/services/module-delegate';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { AddonModSubsection } from '../subsection';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to support subsection modules.
|
||||||
|
*
|
||||||
|
* This is merely to disable the siteplugin.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class AddonModSubsectionModuleHandlerService extends CoreModuleHandlerBase implements CoreCourseModuleHandler {
|
||||||
|
|
||||||
|
name = 'AddonModSubsection';
|
||||||
|
modName = 'subsection';
|
||||||
|
|
||||||
|
supportedFeatures = {
|
||||||
|
[CoreConstants.FEATURE_MOD_ARCHETYPE]: CoreConstants.MOD_ARCHETYPE_RESOURCE,
|
||||||
|
[CoreConstants.FEATURE_GROUPS]: false,
|
||||||
|
[CoreConstants.FEATURE_GROUPINGS]: false,
|
||||||
|
[CoreConstants.FEATURE_MOD_INTRO]: false,
|
||||||
|
[CoreConstants.FEATURE_COMPLETION_TRACKS_VIEWS]: true,
|
||||||
|
[CoreConstants.FEATURE_GRADE_HAS_GRADE]: false,
|
||||||
|
[CoreConstants.FEATURE_GRADE_OUTCOMES]: false,
|
||||||
|
[CoreConstants.FEATURE_BACKUP_MOODLE2]: true,
|
||||||
|
[CoreConstants.FEATURE_SHOW_DESCRIPTION]: false,
|
||||||
|
[CoreConstants.FEATURE_MOD_PURPOSE]: ModPurpose.MOD_PURPOSE_CONTENT,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getData(module: CoreCourseModuleData): CoreCourseModuleHandlerData {
|
||||||
|
return {
|
||||||
|
icon: CoreCourseModuleDelegate.getModuleIconSrc(module.modname, module.modicon),
|
||||||
|
title: module.name,
|
||||||
|
a11yTitle: '',
|
||||||
|
class: 'addon-mod-subsection-handler',
|
||||||
|
hasCustomCmListItem: true,
|
||||||
|
action: async(event, module, courseId) => {
|
||||||
|
try {
|
||||||
|
await AddonModSubsection.openSubsection(module, courseId);
|
||||||
|
} catch (error) {
|
||||||
|
CoreDomUtils.showErrorModalDefault(error, 'Error opening subsection.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async getMainComponent(): Promise<undefined> {
|
||||||
|
// There's no need to implement this because subsection cannot be used in singleactivity course format.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getIconSrc(): string {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export const AddonModSubsectionModuleHandler = makeSingleton(AddonModSubsectionModuleHandlerService);
|
|
@ -0,0 +1,50 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { CoreCourse } from '@features/course/services/course';
|
||||||
|
import { CoreCourseModuleData, CoreCourseHelper } from '@features/course/services/course-helper';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service that provides some features for subsections.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class AddonModSubsectionProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a subsection.
|
||||||
|
*/
|
||||||
|
async openSubsection(module: CoreCourseModuleData , courseId?: number, siteId?: string): Promise<void> {
|
||||||
|
if (!courseId) {
|
||||||
|
courseId = module.course;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pageParams = {
|
||||||
|
sectionId: module.section,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
(!siteId || siteId === CoreSites.getCurrentSiteId()) &&
|
||||||
|
CoreCourse.currentViewIsCourse(courseId)
|
||||||
|
) {
|
||||||
|
CoreCourse.selectCourseTab('', pageParams);
|
||||||
|
} else {
|
||||||
|
await CoreCourseHelper.getAndOpenCourse(courseId, pageParams, siteId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export const AddonModSubsection = makeSingleton(AddonModSubsectionProvider);
|
|
@ -0,0 +1,33 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||||
|
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
|
||||||
|
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
|
||||||
|
import { AddonModSubsectionIndexLinkHandler } from './services/handlers/index-link';
|
||||||
|
import { AddonModSubsectionModuleHandler } from './services/handlers/module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: APP_INITIALIZER,
|
||||||
|
multi: true,
|
||||||
|
useValue: () => {
|
||||||
|
CoreCourseModuleDelegate.registerHandler(AddonModSubsectionModuleHandler.instance);
|
||||||
|
CoreContentLinksDelegate.registerHandler(AddonModSubsectionIndexLinkHandler.instance);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AddonModSubsectionModule {}
|
|
@ -10,10 +10,11 @@
|
||||||
<!-- Single section. -->
|
<!-- Single section. -->
|
||||||
<div *ngIf="selectedSection && selectedSection.id !== allSectionsId" class="list-item-limited-width">
|
<div *ngIf="selectedSection && selectedSection.id !== allSectionsId" class="list-item-limited-width">
|
||||||
<core-dynamic-component [component]="singleSectionComponent" [data]="data">
|
<core-dynamic-component [component]="singleSectionComponent" [data]="data">
|
||||||
<ion-accordion-group [readonly]="true" value="non-collapsible">
|
<ion-accordion-group [multiple]="true" (ionChange)="accordionMultipleChange($event.detail)"
|
||||||
<core-course-section *ngIf="!selectedSection.hiddenbynumsections && selectedSection.id !== allSectionsId &&
|
[value]="accordionMultipleValue">
|
||||||
selectedSection.id !== stealthModulesSectionId" [course]="course" [section]="selectedSection"
|
<core-course-section *ngIf="!selectedSection.hiddenbynumsections && selectedSection.id !== stealthModulesSectionId &&
|
||||||
[lastModuleViewed]="lastModuleViewed" [viewedModules]="viewedModules" [collapsible]="false" />
|
!selectedSection.component" [course]="course" [section]="selectedSection" [lastModuleViewed]="lastModuleViewed"
|
||||||
|
[viewedModules]="viewedModules" [collapsible]="false" [sections]="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" />
|
||||||
|
@ -23,14 +24,14 @@
|
||||||
<!-- Multiple sections. -->
|
<!-- Multiple sections. -->
|
||||||
<div *ngIf="selectedSection && selectedSection.id === allSectionsId" class="list-item-limited-width">
|
<div *ngIf="selectedSection && selectedSection.id === allSectionsId" class="list-item-limited-width">
|
||||||
<core-dynamic-component [component]="allSectionsComponent" [data]="data">
|
<core-dynamic-component [component]="allSectionsComponent" [data]="data">
|
||||||
<ion-accordion-group [multiple]="true" (ionChange)="accordionMultipleChange($event.detail)" [value]="accordionMultipleValue"
|
<ion-accordion-group [multiple]="true" (ionChange)="accordionMultipleChange($event.detail)"
|
||||||
#accordionMultiple>
|
[value]="accordionMultipleValue">
|
||||||
@for (section of sections; track section.id) {
|
@for (section of sections; track section.id) {
|
||||||
@if ($index <= lastShownSectionIndex) {
|
@if ($index <= lastShownSectionIndex && !section.hiddenbynumsections && section.id !== allSectionsId &&
|
||||||
|
section.id !== stealthModulesSectionId && !section.component) {
|
||||||
<core-course-section
|
<core-course-section
|
||||||
*ngIf="!section.hiddenbynumsections && section.id !== allSectionsId && section.id !== stealthModulesSectionId"
|
|
||||||
[course]="course" [section]="section" [lastModuleViewed]="lastModuleViewed" [viewedModules]="viewedModules"
|
[course]="course" [section]="section" [lastModuleViewed]="lastModuleViewed" [viewedModules]="viewedModules"
|
||||||
[collapsible]="true" />
|
[collapsible]="true" [sections]="subSections" />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</ion-accordion-group>
|
</ion-accordion-group>
|
||||||
|
|
|
@ -88,6 +88,7 @@ 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.
|
||||||
|
@ -225,6 +226,9 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changes.sections && this.sections) {
|
if (changes.sections && this.sections) {
|
||||||
|
this.subSections = this.sections.filter((section) => section.component === 'mod_subsection');
|
||||||
|
this.sections = this.sections.filter((section) => section.component !== 'mod_subsection');
|
||||||
|
|
||||||
this.treatSections(this.sections);
|
this.treatSections(this.sections);
|
||||||
}
|
}
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
|
@ -746,9 +750,14 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
* Save expanded sections for the course.
|
* Save expanded sections for the course.
|
||||||
*/
|
*/
|
||||||
protected async saveExpandedSections(): Promise<void> {
|
protected async saveExpandedSections(): Promise<void> {
|
||||||
const expandedSections = this.sections.filter((section) => section.expanded).map((section) => section.id).join(',');
|
let expandedSections = this.sections.filter((section) => section.expanded && section.id > 0).map((section) => section.id);
|
||||||
|
expandedSections =
|
||||||
|
expandedSections.concat(this.subSections.filter((section) => section.expanded).map((section) => section.id));
|
||||||
|
|
||||||
await this.currentSite?.setLocalSiteConfig(`${COURSE_EXPANDED_SECTIONS_PREFIX}${this.course.id}`, expandedSections);
|
await this.currentSite?.setLocalSiteConfig(
|
||||||
|
`${COURSE_EXPANDED_SECTIONS_PREFIX}${this.course.id}`,
|
||||||
|
expandedSections.join(','),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -766,6 +775,11 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
this.accordionMultipleValue.push(section.id.toString());
|
this.accordionMultipleValue.push(section.id.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.subSections.forEach((section) => {
|
||||||
|
section.expanded = true;
|
||||||
|
this.accordionMultipleValue.push(section.id.toString());
|
||||||
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -774,6 +788,10 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
this.sections.forEach((section) => {
|
this.sections.forEach((section) => {
|
||||||
section.expanded = this.accordionMultipleValue.includes(section.id.toString());
|
section.expanded = this.accordionMultipleValue.includes(section.id.toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.subSections.forEach((section) => {
|
||||||
|
section.expanded = this.accordionMultipleValue.includes(section.id.toString());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -787,9 +805,17 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
section.expanded = false;
|
section.expanded = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.subSections.forEach((section) => {
|
||||||
|
section.expanded = false;
|
||||||
|
});
|
||||||
|
|
||||||
sectionIds?.forEach((sectionId) => {
|
sectionIds?.forEach((sectionId) => {
|
||||||
const sId = Number(sectionId);
|
const sId = Number(sectionId);
|
||||||
const section = this.sections.find((section) => section.id === sId);
|
let section = this.sections.find((section) => section.id === sId);
|
||||||
|
if (!section) {
|
||||||
|
section = this.subSections.find((section) => section.id === sId);
|
||||||
|
}
|
||||||
|
|
||||||
if (section) {
|
if (section) {
|
||||||
section.expanded = true;
|
section.expanded = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,12 +19,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.non-collapsible ::ng-deep {
|
|
||||||
ion-item.divider.course-section {
|
|
||||||
ion-icon.ion-accordion-toggle-icon {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue