Skip to content

Commit 61cb042

Browse files
authored
Merge pull request #4180 from crazyserver/MOBILE-4442
Mobile 4442
2 parents 1ef4aa6 + cb9580b commit 61cb042

23 files changed

+773
-268
lines changed
1.04 KB
Loading

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.section, 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) => {
63+
try {
64+
await AddonModSubsection.openSubsection(module.section, module.course);
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: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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 { 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+
* @param sectionId Section ID.
31+
* @param courseId Course ID.
32+
* @param siteId Site ID. If not defined, current site.
33+
* @returns Promise resolved when done.
34+
*/
35+
async openSubsection(sectionId: number, courseId: number, siteId?: string): Promise<void> {
36+
const pageParams = {
37+
sectionId,
38+
};
39+
40+
if (
41+
(!siteId || siteId === CoreSites.getCurrentSiteId()) &&
42+
CoreCourse.currentViewIsCourse(courseId)
43+
) {
44+
CoreCourse.selectCourseTab('', pageParams);
45+
} else {
46+
await CoreCourseHelper.getAndOpenCourse(courseId, pageParams, siteId);
47+
}
48+
}
49+
50+
}
51+
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/components/message/message.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,9 @@ export class CoreMessageComponent implements OnInit {
4646

4747
protected deleted = false; // Needed to fix animation to void in Behat tests.
4848

49-
// @TODO Recover the animation using native css or wait for Angular 13.1
50-
// where the bug https://github.com/angular/angular/issues/30693 is solved.
51-
// @HostBinding('@coreSlideInOut') get animation(): string {
52-
// return this.isMine ? '' : 'fromLeft';
53-
// }
49+
@HostBinding('@coreSlideInOut') get animation(): string {
50+
return this.isMine ? '' : 'fromLeft';
51+
}
5452

5553
@HostBinding('class.is-mine') isMine = false;
5654

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

Lines changed: 14 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,30 @@
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 [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" [subSections]="subSections" />
1518
</ion-accordion-group>
1619
<core-empty-box *ngIf="!selectedSection.hasContent" icon="fas-table-cells-large"
1720
[message]="'core.course.nocontentavailable' | translate" />
1821
</core-dynamic-component>
1922
</div>
2023

2124
<!-- Multiple sections. -->
22-
<div *ngIf="selectedSection && selectedSection.id === allSectionsId" class="multiple-sections list-item-limited-width">
25+
<div *ngIf="selectedSection && selectedSection.id === allSectionsId" class="list-item-limited-width">
2326
<core-dynamic-component [component]="allSectionsComponent" [data]="data">
24-
<ion-accordion-group [multiple]="true" (ionChange)="accordionMultipleChange($event.detail)" [value]="accordionMultipleValue"
25-
#accordionMultiple>
27+
<ion-accordion-group [multiple]="true" (ionChange)="accordionMultipleChange($event.detail)"
28+
[value]="accordionMultipleValue">
2629
@for (section of sections; track section.id) {
27-
@if ($index <= lastShownSectionIndex) {
28-
<ng-container *ngTemplateOutlet="sectionTemplate; context: {section: section, sectionId: section.id}" />
30+
@if ($index <= lastShownSectionIndex && !section.hiddenbynumsections && section.id !== allSectionsId &&
31+
section.id !== stealthModulesSectionId && !section.component) {
32+
<core-course-section
33+
[course]="course" [section]="section" [lastModuleViewed]="lastModuleViewed" [viewedModules]="viewedModules"
34+
[collapsible]="true" [subSections]="subSections" />
2935
}
3036
}
3137
</ion-accordion-group>
@@ -65,55 +71,3 @@
6571
<ion-icon name="fas-list-ul" aria-hidden="true" />
6672
</ion-fab-button>
6773
</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-
}

0 commit comments

Comments
 (0)