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 { AddonModResourceModule } from './resource/resource.module';
|
||||
import { AddonModScormModule } from './scorm/scorm.module';
|
||||
import { AddonModSubsectionModule } from './subsection/subsection.module';
|
||||
import { AddonModSurveyModule } from './survey/survey.module';
|
||||
import { AddonModUrlModule } from './url/url.module';
|
||||
import { AddonModWikiModule } from './wiki/wiki.module';
|
||||
|
@ -59,6 +60,7 @@ import { AddonModWorkshopModule } from './workshop/workshop.module';
|
|||
AddonModQuizModule,
|
||||
AddonModResourceModule,
|
||||
AddonModScormModule,
|
||||
AddonModSubsectionModule,
|
||||
AddonModSurveyModule,
|
||||
AddonModUrlModule,
|
||||
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. -->
|
||||
<div *ngIf="selectedSection && selectedSection.id !== allSectionsId" class="list-item-limited-width">
|
||||
<core-dynamic-component [component]="singleSectionComponent" [data]="data">
|
||||
<ion-accordion-group [readonly]="true" value="non-collapsible">
|
||||
<core-course-section *ngIf="!selectedSection.hiddenbynumsections && selectedSection.id !== allSectionsId &&
|
||||
selectedSection.id !== stealthModulesSectionId" [course]="course" [section]="selectedSection"
|
||||
[lastModuleViewed]="lastModuleViewed" [viewedModules]="viewedModules" [collapsible]="false" />
|
||||
<ion-accordion-group [multiple]="true" (ionChange)="accordionMultipleChange($event.detail)"
|
||||
[value]="accordionMultipleValue">
|
||||
<core-course-section *ngIf="!selectedSection.hiddenbynumsections && selectedSection.id !== stealthModulesSectionId &&
|
||||
!selectedSection.component" [course]="course" [section]="selectedSection" [lastModuleViewed]="lastModuleViewed"
|
||||
[viewedModules]="viewedModules" [collapsible]="false" [sections]="subSections" />
|
||||
</ion-accordion-group>
|
||||
<core-empty-box *ngIf="!selectedSection.hasContent" icon="fas-table-cells-large"
|
||||
[message]="'core.course.nocontentavailable' | translate" />
|
||||
|
@ -23,14 +24,14 @@
|
|||
<!-- Multiple sections. -->
|
||||
<div *ngIf="selectedSection && selectedSection.id === allSectionsId" class="list-item-limited-width">
|
||||
<core-dynamic-component [component]="allSectionsComponent" [data]="data">
|
||||
<ion-accordion-group [multiple]="true" (ionChange)="accordionMultipleChange($event.detail)" [value]="accordionMultipleValue"
|
||||
#accordionMultiple>
|
||||
<ion-accordion-group [multiple]="true" (ionChange)="accordionMultipleChange($event.detail)"
|
||||
[value]="accordionMultipleValue">
|
||||
@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
|
||||
*ngIf="!section.hiddenbynumsections && section.id !== allSectionsId && section.id !== stealthModulesSectionId"
|
||||
[course]="course" [section]="section" [lastModuleViewed]="lastModuleViewed" [viewedModules]="viewedModules"
|
||||
[collapsible]="true" />
|
||||
[course]="course" [section]="section" [lastModuleViewed]="lastModuleViewed" [viewedModules]="viewedModules"
|
||||
[collapsible]="true" [sections]="subSections" />
|
||||
}
|
||||
}
|
||||
</ion-accordion-group>
|
||||
|
|
|
@ -88,6 +88,7 @@ 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.
|
||||
|
@ -225,6 +226,9 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
}
|
||||
|
||||
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.changeDetectorRef.markForCheck();
|
||||
|
@ -746,9 +750,14 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
* Save expanded sections for the course.
|
||||
*/
|
||||
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.subSections.forEach((section) => {
|
||||
section.expanded = true;
|
||||
this.accordionMultipleValue.push(section.id.toString());
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -774,6 +788,10 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
|||
this.sections.forEach((section) => {
|
||||
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;
|
||||
});
|
||||
|
||||
this.subSections.forEach((section) => {
|
||||
section.expanded = false;
|
||||
});
|
||||
|
||||
sectionIds?.forEach((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) {
|
||||
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