Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mobile 4442 #4180

Merged
merged 6 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/addons/mod/mod.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -59,6 +60,7 @@ import { AddonModWorkshopModule } from './workshop/workshop.module';
AddonModQuizModule,
AddonModResourceModule,
AddonModScormModule,
AddonModSubsectionModule,
AddonModSurveyModule,
AddonModUrlModule,
AddonModWikiModule,
Expand Down
65 changes: 65 additions & 0 deletions src/addons/mod/subsection/services/handlers/index-link.ts
Original file line number Diff line number Diff line change
@@ -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.section, module.course, siteId);
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error opening link.');
} finally {
modal.dismiss();
}
},
}];
}

}
export const AddonModSubsectionIndexLinkHandler = makeSingleton(AddonModSubsectionIndexLinkHandlerService);
88 changes: 88 additions & 0 deletions src/addons/mod/subsection/services/handlers/module.ts
Original file line number Diff line number Diff line change
@@ -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) => {
try {
await AddonModSubsection.openSubsection(module.section, module.course);
} 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);
51 changes: 51 additions & 0 deletions src/addons/mod/subsection/services/subsection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// (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 { 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.
*
* @param sectionId Section ID.
* @param courseId Course ID.
* @param siteId Site ID. If not defined, current site.
* @returns Promise resolved when done.
*/
async openSubsection(sectionId: number, courseId: number, siteId?: string): Promise<void> {
const pageParams = {
sectionId,
};

if (
(!siteId || siteId === CoreSites.getCurrentSiteId()) &&
CoreCourse.currentViewIsCourse(courseId)
) {
CoreCourse.selectCourseTab('', pageParams);
} else {
await CoreCourseHelper.getAndOpenCourse(courseId, pageParams, siteId);
}
}

}
export const AddonModSubsection = makeSingleton(AddonModSubsectionProvider);
33 changes: 33 additions & 0 deletions src/addons/mod/subsection/subsection.module.ts
Original file line number Diff line number Diff line change
@@ -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 {}
8 changes: 3 additions & 5 deletions src/core/components/message/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,9 @@ export class CoreMessageComponent implements OnInit {

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

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,30 @@
<core-loading [hideUntil]="loaded">

<!-- Single section. -->
<div *ngIf="selectedSection && selectedSection.id !== allSectionsId" class="single-section list-item-limited-width">
<div *ngIf="selectedSection && selectedSection.id !== allSectionsId" class="list-item-limited-width">
<core-dynamic-component [component]="singleSectionComponent" [data]="data">
<ion-accordion-group [readonly]="true" value="single">
<ng-container *ngTemplateOutlet="sectionTemplate; context: {section: selectedSection, sectionId: 'single'}" />
<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" [subSections]="subSections" />
</ion-accordion-group>
<core-empty-box *ngIf="!selectedSection.hasContent" icon="fas-table-cells-large"
[message]="'core.course.nocontentavailable' | translate" />
</core-dynamic-component>
</div>

<!-- Multiple sections. -->
<div *ngIf="selectedSection && selectedSection.id === allSectionsId" class="multiple-sections list-item-limited-width">
<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) {
<ng-container *ngTemplateOutlet="sectionTemplate; context: {section: section, sectionId: section.id}" />
@if ($index <= lastShownSectionIndex && !section.hiddenbynumsections && section.id !== allSectionsId &&
section.id !== stealthModulesSectionId && !section.component) {
<core-course-section
[course]="course" [section]="section" [lastModuleViewed]="lastModuleViewed" [viewedModules]="viewedModules"
[collapsible]="true" [subSections]="subSections" />
}
}
</ion-accordion-group>
Expand Down Expand Up @@ -65,55 +71,3 @@
<ion-icon name="fas-list-ul" aria-hidden="true" />
</ion-fab-button>
</ion-fab>

<!-- Template to render a section. -->
<ng-template #sectionTemplate let-section="section" let-sectionId="sectionId">
<ion-accordion *ngIf="!section.hiddenbynumsections && section.id !== allSectionsId && section.id !== stealthModulesSectionId"
class="core-course-module-list-wrapper" [id]="section.id"
[attr.aria-labelledby]="section.name ? 'core-section-name-' + section.id : null" [value]="''+sectionId" toggleIconSlot="start">
<ion-item class="course-section divider" [class.item-dimmed]="section.visible === 0 || section.uservisible === false" slot="header">
<ion-label class="ion-text-wrap">
<h2 *ngIf="section.name" class="big" [id]="'core-section-name-' + section.id">
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course.id" />
</h2>
<div *ngIf="section.visible === 0 && section.uservisible !== false">
<ion-badge color="warning">
{{ 'core.course.hiddenfromstudents' | translate }}
</ion-badge>
</div>
<div *ngIf="section.visible === 0 && section.uservisible === false">
<ion-badge color="warning">
{{ 'core.notavailable' | translate }}
</ion-badge>
</div>
<div *ngIf="section.availabilityinfo">
<ion-chip class="clickable">
<ion-icon name="fas-lock" [attr.aria-label]="'core.restricted' | translate" />
<ion-label>
<core-format-text [text]=" section.availabilityinfo" contextLevel="course" [contextInstanceId]="course.id" />
</ion-label>
</ion-chip>
</div>
</ion-label>
<ion-badge *ngIf="section.highlighted && highlighted" slot="end">{{highlighted}}</ion-badge>
</ion-item>

<div slot="content">
<ng-container *ngIf="section.expanded">
<ion-item class="ion-text-wrap section-summary" *ngIf="section.summary">
<ion-label>
<core-format-text [text]="section.summary" contextLevel="course" [contextInstanceId]="course.id" />
</ion-label>
</ion-item>

<ng-container *ngFor="let module of section.modules">
<core-course-module *ngIf="module.visibleoncoursepage !== 0" [module]="module" [section]="section"
[showActivityDates]="course.showactivitydates" [showCompletionConditions]="course.showcompletionconditions"
[isLastViewed]="lastModuleViewed && lastModuleViewed.cmId === module.id"
[class.core-course-module-not-viewed]="
!viewedModules[module.id] && (!module.completiondata || module.completiondata.state === completionStatusIncomplete)" />
</ng-container>
</ng-container>
</div>
</ion-accordion>
</ng-template>
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,3 @@
margin-right: 4px;
}
}

.course-section {
--inner-padding-end: 12px;
}

.multiple-sections .core-course-module-list-wrapper {
border: var(--ion-card-border-width) solid var(--ion-card-border-color);
border-radius: var(--ion-card-radius);
margin: 8px 4px;
width: calc(100% - 8px);

ion-card {
--ion-card-background: transparent;
}

ion-item.divider.course-section {
--background: transparent;
}
}

.single-section ::ng-deep {
ion-item.divider.course-section {
ion-icon.ion-accordion-toggle-icon {
display: none;
}
}
}
Loading