diff --git a/scripts/langindex.json b/scripts/langindex.json index df6d554e3..cc6776547 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1507,6 +1507,10 @@ "core.course.errordownloadingcourse": "local_moodlemobileapp", "core.course.errordownloadingsection": "local_moodlemobileapp", "core.course.errorgetmodule": "local_moodlemobileapp", + "core.course.gotonextactivity": "local_moodlemobileapp", + "core.course.gotonextactivitynotfound": "local_moodlemobileapp", + "core.course.gotopreviousactivity": "local_moodlemobileapp", + "core.course.gotopreviousactivitynotfound": "local_moodlemobileapp", "core.course.hiddenfromstudents": "moodle", "core.course.hiddenoncoursepage": "moodle", "core.course.insufficientavailablequota": "local_moodlemobileapp", diff --git a/src/addons/mod/assign/components/index/addon-mod-assign-index.html b/src/addons/mod/assign/components/index/addon-mod-assign-index.html index fd835cefd..2382ff0e7 100644 --- a/src/addons/mod/assign/components/index/addon-mod-assign-index.html +++ b/src/addons/mod/assign/components/index/addon-mod-assign-index.html @@ -147,3 +147,5 @@ [moduleId]="module.id"> + + diff --git a/src/addons/mod/book/components/index/addon-mod-book-index.html b/src/addons/mod/book/components/index/addon-mod-book-index.html index f6f090647..dc1e2dcda 100644 --- a/src/addons/mod/book/components/index/addon-mod-book-index.html +++ b/src/addons/mod/book/components/index/addon-mod-book-index.html @@ -52,3 +52,6 @@ + + + diff --git a/src/addons/mod/chat/components/components.module.ts b/src/addons/mod/chat/components/components.module.ts index 954e3a8b4..c0bdb4ad4 100644 --- a/src/addons/mod/chat/components/components.module.ts +++ b/src/addons/mod/chat/components/components.module.ts @@ -27,8 +27,6 @@ import { AddonModChatUsersModalComponent } from './users-modal/users-modal'; CoreSharedModule, CoreCourseComponentsModule, ], - providers: [ - ], exports: [ AddonModChatIndexComponent, AddonModChatUsersModalComponent, diff --git a/src/addons/mod/chat/components/index/addon-mod-chat-index.html b/src/addons/mod/chat/components/index/addon-mod-chat-index.html index 93265acea..046853afd 100644 --- a/src/addons/mod/chat/components/index/addon-mod-chat-index.html +++ b/src/addons/mod/chat/components/index/addon-mod-chat-index.html @@ -47,3 +47,6 @@ + + + diff --git a/src/addons/mod/choice/components/index/addon-mod-choice-index.html b/src/addons/mod/choice/components/index/addon-mod-choice-index.html index a767fe487..4d291227e 100644 --- a/src/addons/mod/choice/components/index/addon-mod-choice-index.html +++ b/src/addons/mod/choice/components/index/addon-mod-choice-index.html @@ -155,6 +155,9 @@ + + +

diff --git a/src/addons/mod/data/components/index/addon-mod-data-index.html b/src/addons/mod/data/components/index/addon-mod-data-index.html index 8a1f45d10..1bb70403b 100644 --- a/src/addons/mod/data/components/index/addon-mod-data-index.html +++ b/src/addons/mod/data/components/index/addon-mod-data-index.html @@ -138,6 +138,9 @@ + + + diff --git a/src/addons/mod/feedback/components/index/addon-mod-feedback-index.html b/src/addons/mod/feedback/components/index/addon-mod-feedback-index.html index aacd8344e..c9cb04c87 100644 --- a/src/addons/mod/feedback/components/index/addon-mod-feedback-index.html +++ b/src/addons/mod/feedback/components/index/addon-mod-feedback-index.html @@ -55,6 +55,9 @@ + + + diff --git a/src/addons/mod/folder/components/index/addon-mod-folder-index.html b/src/addons/mod/folder/components/index/addon-mod-folder-index.html index cb1093a10..6c8d1aaeb 100644 --- a/src/addons/mod/folder/components/index/addon-mod-folder-index.html +++ b/src/addons/mod/folder/components/index/addon-mod-folder-index.html @@ -48,3 +48,6 @@ [message]=" 'addon.mod_folder.emptyfilelist' | translate"> + + + diff --git a/src/addons/mod/forum/components/index/index.html b/src/addons/mod/forum/components/index/index.html index d282b828e..63cd1b7b6 100644 --- a/src/addons/mod/forum/components/index/index.html +++ b/src/addons/mod/forum/components/index/index.html @@ -140,6 +140,9 @@ + + + diff --git a/src/addons/mod/glossary/components/index/addon-mod-glossary-index.html b/src/addons/mod/glossary/components/index/addon-mod-glossary-index.html index c8a816a25..d6f12a61e 100644 --- a/src/addons/mod/glossary/components/index/addon-mod-glossary-index.html +++ b/src/addons/mod/glossary/components/index/addon-mod-glossary-index.html @@ -96,6 +96,9 @@ + + + diff --git a/src/addons/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html b/src/addons/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html index 4c7b7cd88..300b5feb7 100644 --- a/src/addons/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html +++ b/src/addons/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html @@ -84,3 +84,6 @@ [trackComponent]="trackComponent" [contextId]="h5pActivity?.context"> + + + diff --git a/src/addons/mod/h5pactivity/components/index/index.ts b/src/addons/mod/h5pactivity/components/index/index.ts index cf7b0b60b..762c6fefe 100644 --- a/src/addons/mod/h5pactivity/components/index/index.ts +++ b/src/addons/mod/h5pactivity/components/index/index.ts @@ -45,7 +45,6 @@ import { } from '../../services/h5pactivity-sync'; import { CoreFileHelper } from '@services/file-helper'; import { AddonModH5PActivityModuleHandlerService } from '../../services/handlers/module'; -import { CoreMainMenuPage } from '@features/mainmenu/pages/menu/menu'; /** * Component that displays an H5P activity entry page. @@ -87,7 +86,6 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv protected messageListenerFunction: (event: MessageEvent) => Promise; constructor( - protected mainMenuPage: CoreMainMenuPage, protected content?: IonContent, @Optional() courseContentsPage?: CoreCourseContentsPage, ) { diff --git a/src/addons/mod/imscp/components/index/addon-mod-imscp-index.html b/src/addons/mod/imscp/components/index/addon-mod-imscp-index.html index 4f15b3bc9..52a6d1eb9 100644 --- a/src/addons/mod/imscp/components/index/addon-mod-imscp-index.html +++ b/src/addons/mod/imscp/components/index/addon-mod-imscp-index.html @@ -47,3 +47,6 @@ + + + diff --git a/src/addons/mod/lesson/components/index/addon-mod-lesson-index.html b/src/addons/mod/lesson/components/index/addon-mod-lesson-index.html index a9eeb2a60..0219bef8c 100644 --- a/src/addons/mod/lesson/components/index/addon-mod-lesson-index.html +++ b/src/addons/mod/lesson/components/index/addon-mod-lesson-index.html @@ -297,3 +297,6 @@ + + + diff --git a/src/addons/mod/lti/components/index/addon-mod-lti-index.html b/src/addons/mod/lti/components/index/addon-mod-lti-index.html index ef10e579e..20cf1a86e 100644 --- a/src/addons/mod/lti/components/index/addon-mod-lti-index.html +++ b/src/addons/mod/lti/components/index/addon-mod-lti-index.html @@ -32,3 +32,6 @@ + + + diff --git a/src/addons/mod/page/components/index/addon-mod-page-index.html b/src/addons/mod/page/components/index/addon-mod-page-index.html index 2dcb76d66..078f5c5c9 100644 --- a/src/addons/mod/page/components/index/addon-mod-page-index.html +++ b/src/addons/mod/page/components/index/addon-mod-page-index.html @@ -48,3 +48,6 @@ + + + diff --git a/src/addons/mod/quiz/components/index/addon-mod-quiz-index.html b/src/addons/mod/quiz/components/index/addon-mod-quiz-index.html index cf49f5b7b..5d39ece10 100644 --- a/src/addons/mod/quiz/components/index/addon-mod-quiz-index.html +++ b/src/addons/mod/quiz/components/index/addon-mod-quiz-index.html @@ -226,3 +226,6 @@ + + + diff --git a/src/addons/mod/resource/components/index/addon-mod-resource-index.html b/src/addons/mod/resource/components/index/addon-mod-resource-index.html index 4c8e7daf6..9638db04e 100644 --- a/src/addons/mod/resource/components/index/addon-mod-resource-index.html +++ b/src/addons/mod/resource/components/index/addon-mod-resource-index.html @@ -18,7 +18,7 @@ - + - + + + diff --git a/src/addons/mod/scorm/components/index/addon-mod-scorm-index.html b/src/addons/mod/scorm/components/index/addon-mod-scorm-index.html index f184db40a..37ae91bfa 100644 --- a/src/addons/mod/scorm/components/index/addon-mod-scorm-index.html +++ b/src/addons/mod/scorm/components/index/addon-mod-scorm-index.html @@ -236,3 +236,6 @@ + + + diff --git a/src/addons/mod/survey/components/index/addon-mod-survey-index.html b/src/addons/mod/survey/components/index/addon-mod-survey-index.html index b5042fbf5..345c10b5a 100644 --- a/src/addons/mod/survey/components/index/addon-mod-survey-index.html +++ b/src/addons/mod/survey/components/index/addon-mod-survey-index.html @@ -147,3 +147,6 @@ + + + diff --git a/src/addons/mod/url/components/index/addon-mod-url-index.html b/src/addons/mod/url/components/index/addon-mod-url-index.html index 491e186eb..7026fddf3 100644 --- a/src/addons/mod/url/components/index/addon-mod-url-index.html +++ b/src/addons/mod/url/components/index/addon-mod-url-index.html @@ -13,7 +13,7 @@ - + + + + diff --git a/src/addons/mod/wiki/components/index/addon-mod-wiki-index.html b/src/addons/mod/wiki/components/index/addon-mod-wiki-index.html index e6628246f..ee8fec538 100644 --- a/src/addons/mod/wiki/components/index/addon-mod-wiki-index.html +++ b/src/addons/mod/wiki/components/index/addon-mod-wiki-index.html @@ -89,6 +89,9 @@ + + + diff --git a/src/addons/mod/workshop/components/index/addon-mod-workshop-index.html b/src/addons/mod/workshop/components/index/addon-mod-workshop-index.html index 1aedc639d..8a5e5543d 100644 --- a/src/addons/mod/workshop/components/index/addon-mod-workshop-index.html +++ b/src/addons/mod/workshop/components/index/addon-mod-workshop-index.html @@ -253,3 +253,6 @@ + + + diff --git a/src/core/features/course/classes/module-base-handler.ts b/src/core/features/course/classes/module-base-handler.ts index f45aa6a53..474fb231c 100644 --- a/src/core/features/course/classes/module-base-handler.ts +++ b/src/core/features/course/classes/module-base-handler.ts @@ -45,13 +45,18 @@ export class CoreModuleHandlerBase implements Partial { title: module.name, class: 'addon-mod_' + module.modname + '-handler', showDownloadButton: true, - action: (event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions): void => { + action: async ( + event: Event, + module: CoreCourseModule, + courseId: number, + options?: CoreNavigationOptions, + ): Promise => { options = options || {}; options.params = options.params || {}; Object.assign(options.params, { module }); const routeParams = '/' + courseId + '/' + module.id; - CoreNavigator.navigateToSitePath(this.pageName + routeParams, options); + await CoreNavigator.navigateToSitePath(this.pageName + routeParams, options); }, }; } diff --git a/src/core/features/course/components/components.module.ts b/src/core/features/course/components/components.module.ts index 3680f6f36..516a43111 100644 --- a/src/core/features/course/components/components.module.ts +++ b/src/core/features/course/components/components.module.ts @@ -26,6 +26,7 @@ import { CoreCourseUnsupportedModuleComponent } from './unsupported-module/unsup import { CoreCourseModuleCompletionLegacyComponent } from './module-completion-legacy/module-completion-legacy'; import { CoreCourseModuleInfoComponent } from './module-info/module-info'; import { CoreCourseModuleManualCompletionComponent } from './module-manual-completion/module-manual-completion'; +import { CoreCourseModuleNavigationComponent } from './module-navigation/module-navigation'; @NgModule({ declarations: [ @@ -39,6 +40,7 @@ import { CoreCourseModuleManualCompletionComponent } from './module-manual-compl CoreCourseSectionSelectorComponent, CoreCourseTagAreaComponent, CoreCourseUnsupportedModuleComponent, + CoreCourseModuleNavigationComponent, ], imports: [ CoreBlockComponentsModule, @@ -55,6 +57,7 @@ import { CoreCourseModuleManualCompletionComponent } from './module-manual-compl CoreCourseSectionSelectorComponent, CoreCourseTagAreaComponent, CoreCourseUnsupportedModuleComponent, + CoreCourseModuleNavigationComponent, ], }) export class CoreCourseComponentsModule {} diff --git a/src/core/features/course/components/module-navigation/core-course-module-navigation.html b/src/core/features/course/components/module-navigation/core-course-module-navigation.html new file mode 100644 index 000000000..74fc6f584 --- /dev/null +++ b/src/core/features/course/components/module-navigation/core-course-module-navigation.html @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/src/core/features/course/components/module-navigation/module-navigation.scss b/src/core/features/course/components/module-navigation/module-navigation.scss new file mode 100644 index 000000000..2a8b74f54 --- /dev/null +++ b/src/core/features/course/components/module-navigation/module-navigation.scss @@ -0,0 +1,43 @@ +@import "~theme/globals"; + +:host { + --height: var(--core-course-module-navigation-height, var(--core-course-module-navigation-max-height)); + --background: var(--core-course-module-navigation-background); + + height: var(--height); + width: 100%; + background-color: var(--background); + display: block; + bottom: 0; + z-index: 3; + box-shadow: 0px -3px 3px rgba(var(--drop-shadow)); + + @include core-transition(all, 200ms); + + ion-col { + padding: 2px; + } + + core-loading { + text-align: center; + } + + ion-buttom { + margin-top: 5px; + margin-bottom: 5px; + } + + core-loading { + --loading-inline-min-height: var(--height); + } +} + +:host-context(.core-iframe-fullscreen) { + opacity: 0 !important; + height: 0 !important; +} + +:host-context(core-course-format.core-course-format-singleactivity) { + opacity: 0 !important; + height: 0 !important; +} diff --git a/src/core/features/course/components/module-navigation/module-navigation.ts b/src/core/features/course/components/module-navigation/module-navigation.ts new file mode 100644 index 000000000..627f0bbd5 --- /dev/null +++ b/src/core/features/course/components/module-navigation/module-navigation.ts @@ -0,0 +1,335 @@ +// (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 { Component, ElementRef, Input, OnDestroy, OnInit } from '@angular/core'; +import { CoreCourse, CoreCourseProvider, CoreCourseWSSection } from '@features/course/services/course'; +import { CoreCourseModule } from '@features/course/services/course-helper'; +import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; +import { IonContent } from '@ionic/angular'; +import { ScrollDetail } from '@ionic/core'; +import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreUtils } from '@services/utils/utils'; +import { CoreEventObserver, CoreEvents } from '@singletons/events'; + +/** + * Component to show a button to go to the next resource/activity. + * + * Example usage: + * + */ +@Component({ + selector: 'core-course-module-navigation', + templateUrl: 'core-course-module-navigation.html', + styleUrls: ['module-navigation.scss'], +}) +export class CoreCourseModuleNavigationComponent implements OnInit, OnDestroy { + + @Input() courseId!: number; // Course ID. + @Input() currentModuleId!: number; // Current module ID. + + nextModule?: CoreCourseModule; + previousModule?: CoreCourseModule; + loaded = false; + + protected element: HTMLElement; + protected initialHeight = 0; + protected initialPaddingBottom = 0; + protected previousTop = 0; + protected content?: HTMLIonContentElement | null; + protected completionObserver: CoreEventObserver; + + constructor(el: ElementRef, protected ionContent: IonContent) { + const siteId = CoreSites.getCurrentSiteId(); + + this.element = el.nativeElement; + this.element.setAttribute('slot', 'fixed'); + + this.completionObserver = CoreEvents.on(CoreEvents.COMPLETION_MODULE_VIEWED, async (data) => { + if (data && data.courseId == this.courseId) { + // Check if now there's a next module. + await this.setNextAndPreviousModules( + CoreSitesReadingStrategy.PREFER_NETWORK, + !this.nextModule, + !this.previousModule, + ); + } + }, siteId); + } + + /** + * @inheritdoc + */ + async ngOnInit(): Promise { + try { + await this.setNextAndPreviousModules(CoreSitesReadingStrategy.PREFER_CACHE); + } finally { + this.loaded = true; + + await CoreUtils.nextTicks(50); + this.listenScrollEvents(); + } + } + + /** + * Setup scroll event listener. + * + * @param retries Number of retries left. + */ + protected async listenScrollEvents(retries = 3): Promise { + this.initialHeight = this.element.getBoundingClientRect().height; + + if (this.initialHeight == 0 && retries > 0) { + await CoreUtils.nextTicks(50); + + this.listenScrollEvents(retries - 1); + + return; + } + // Set a minimum height value. + this.initialHeight = this.initialHeight || 56; + + this.content = this.element.closest('ion-content'); + + if (!this.content) { + return; + } + + // Special case where there's no navigation. + const courseFormat = this.element.closest('core-course-format.core-course-format-singleactivity'); + if (courseFormat) { + this.element.remove(); + this.ngOnDestroy(); + + return; + } + + // Move element to the nearest ion-content if it's not the parent. + if (this.element.parentElement?.nodeName != 'ION-CONTENT') { + this.content.appendChild(this.element); + } + + // Set a padding to not overlap elements. + this.initialPaddingBottom = parseFloat(this.content.style.getPropertyValue('--padding-bottom') || '0'); + this.content.style.setProperty('--padding-bottom', this.initialPaddingBottom + this.initialHeight + 'px'); + const scroll = await this.content.getScrollElement(); + this.content.scrollEvents = true; + + this.setBarHeight(this.initialHeight); + this.content.addEventListener('ionScroll', (e: CustomEvent): void => { + if (!this.content) { + return; + } + + this.onScroll(e.detail.scrollTop, scroll.scrollHeight - scroll.offsetHeight); + }); + + } + + /** + * @inheritdoc + */ + async ngOnDestroy(): Promise { + this.completionObserver.off(); + this.content?.style.setProperty('--padding-bottom', this.initialPaddingBottom + 'px'); + } + + /** + * Set previous and next modules. + * + * @param readingStrategy Reading strategy. + * @param checkNext Check next module. + * @param checkPrevious Check previous module. + * @return Promise resolved when done. + */ + protected async setNextAndPreviousModules( + readingStrategy: CoreSitesReadingStrategy, + checkNext = true, + checkPrevious = true, + ): Promise { + if (!checkNext && !checkPrevious) { + return; + } + + const preSets = CoreSites.getReadingStrategyPreSets(readingStrategy); + + const sections = await CoreCourse.getSections(this.courseId, false, true, preSets); + + // Search the next module. + let currentModuleIndex = -1; + + const currentSectionIndex = sections.findIndex((section) => { + if (!this.isSectionAvailable(section)) { + // User cannot view the section, skip it. + return false; + } + + currentModuleIndex = section.modules.findIndex((module: CoreCourseModule) => module.id == this.currentModuleId); + + return currentModuleIndex >= 0; + }); + + if (currentSectionIndex < 0) { + // Nothing found. Return. + + return; + } + + if (checkNext) { + // Find next Module. + this.nextModule = undefined; + for (let i = currentSectionIndex; i < sections.length && this.nextModule == undefined; i++) { + const section = sections[i]; + + if (!this.isSectionAvailable(section)) { + // User cannot view the section, skip it. + continue; + } + + const startModule = i == currentSectionIndex ? currentModuleIndex + 1 : 0; + for (let j = startModule; j < section.modules.length && this.nextModule == undefined; j++) { + const module = section.modules[j]; + + const found = await this.isModuleAvailable(module, section.id); + if (found) { + this.nextModule = module; + } + } + } + } + + if (checkPrevious) { + // Find previous Module. + this.previousModule = undefined; + for (let i = currentSectionIndex; i >= 0 && this.previousModule == undefined; i--) { + const section = sections[i]; + + if (!this.isSectionAvailable(section)) { + // User cannot view the section, skip it. + continue; + } + + const startModule = i == currentSectionIndex ? currentModuleIndex - 1 : section.modules.length - 1; + for (let j = startModule; j >= 0 && this.previousModule == undefined; j--) { + const module = section.modules[j]; + + const found = await this.isModuleAvailable(module, section.id); + if (found) { + this.previousModule = module; + } + } + } + } + } + + /** + * Module is visible by the user and it has a specific view (e.g. not a label). + * + * @param module Module to check. + * @param sectionId Section ID the module belongs to. + * @return Wether the module is available to the user or not. + */ + protected async isModuleAvailable(module: CoreCourseModule, sectionId: number): Promise { + if (module.uservisible === false || !CoreCourse.instance.moduleHasView(module)) { + return false; + } + + if (!module.handlerData) { + module.handlerData = + await CoreCourseModuleDelegate.getModuleDataFor(module.modname, module, this.courseId, sectionId); + } + + return !!module.handlerData?.action; + } + + /** + * Section is visible by the user and its not stealth + * + * @param section Section to check. + * @return Wether the module is available to the user or not. + */ + protected isSectionAvailable(section: CoreCourseWSSection): boolean { + return section.uservisible !== false && section.id != CoreCourseProvider.STEALTH_MODULES_SECTION_ID; + } + + /** + * Go to next/previous module. + * + * @return Promise resolved when done. + */ + async goToActivity(next = true): Promise { + if (!this.loaded) { + return; + } + + const modal = await CoreDomUtils.showModalLoading(); + + // Re-calculate module in case a new module was made visible. + await CoreUtils.ignoreErrors(this.setNextAndPreviousModules(CoreSitesReadingStrategy.PREFER_NETWORK, next, !next)); + + modal.dismiss(); + + const module = next ? this.nextModule : this.previousModule; + if (!module) { + // It seems the module was hidden. Show a message. + CoreDomUtils.instance.showErrorModal( + next ? 'core.course.gotonextactivitynotfound' : 'core.course.gotopreviousactivitynotfound', + true, + ); + + return; + } + + if (!module.handlerData?.action) { + return; + } + + module.handlerData.action(new Event('click'), module, this.courseId, { replace: true }); + } + + /** + * On scroll function. + * + * @param top Scroll top measure. + * @param maxScroll Scroll height. + */ + protected onScroll(top: number, maxScroll: number): void { + if (top == 0 || top == maxScroll) { + // Reset. + this.setBarHeight(this.initialHeight); + } else { + const diffHeight = this.element.clientHeight - (top - this.previousTop); + this.setBarHeight(diffHeight); + } + + this.previousTop = top; + } + + /** + * Sets the bar height. + * + * @param height The new bar height. + */ + protected setBarHeight(height: number): void { + if (height <= 0) { + height = 0; + } else if (height > this.initialHeight) { + height = this.initialHeight; + } + + this.element.style.opacity = height == 0 ? '0' : '1'; + this.content?.style.setProperty('--core-course-module-navigation-height', height + 'px'); + } + +} diff --git a/src/core/features/course/components/module/module.ts b/src/core/features/course/components/module/module.ts index 5431e5114..b077bd66f 100644 --- a/src/core/features/course/components/module/module.ts +++ b/src/core/features/course/components/module/module.ts @@ -161,7 +161,7 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy { event.preventDefault(); event.stopPropagation(); - button.action(event, this.module!, this.courseId!); + button.action(event, this.module, this.courseId!); } /** diff --git a/src/core/features/course/lang.json b/src/core/features/course/lang.json index 7afa1a068..bba7ba758 100644 --- a/src/core/features/course/lang.json +++ b/src/core/features/course/lang.json @@ -27,6 +27,10 @@ "confirmpartialdownloadsize": "You are about to download at least {{size}}.{{availableSpace}} Are you sure you want to continue?", "confirmlimiteddownload": "You are not currently connected to Wi-Fi. ", "contents": "Contents", + "gotonextactivity": "Continue to next activity", + "gotonextactivitynotfound": "Next activity not found. It's possible that it has been hidden or deleted.", + "gotopreviousactivity": "Continue to previous activity", + "gotopreviousactivitynotfound": "Previous activity not found. It's possible that it has been hidden or deleted.", "couldnotloadsectioncontent": "Could not load the section content. Please try again later.", "couldnotloadsections": "Could not load the sections. Please try again later.", "coursesummary": "Course summary", diff --git a/src/core/features/course/services/module-delegate.ts b/src/core/features/course/services/module-delegate.ts index e5f918d1f..66dec4184 100644 --- a/src/core/features/course/services/module-delegate.ts +++ b/src/core/features/course/services/module-delegate.ts @@ -167,8 +167,9 @@ export interface CoreCourseModuleHandlerData { * @param module The module object. * @param courseId The course ID. * @param options Options for the navigation. + * @return Promise resolved when done. */ - action?(event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions): void; + action?(event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions): Promise | void; /** * Updates the status of the module. @@ -236,8 +237,10 @@ export interface CoreCourseModuleHandlerButton { * @param event The click event. * @param module The module object. * @param courseId The course ID. + * @param options Options for the navigation. + * @return Promise resolved when done. */ - action(event: Event, module: CoreCourseModule, courseId: number): void; + action(event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions): Promise | void; } /** diff --git a/src/core/features/siteplugins/components/components.module.ts b/src/core/features/siteplugins/components/components.module.ts index 2969000e5..b9482e300 100644 --- a/src/core/features/siteplugins/components/components.module.ts +++ b/src/core/features/siteplugins/components/components.module.ts @@ -28,6 +28,7 @@ import { CoreSitePluginsAssignSubmissionComponent } from './assign-submission/as import { CoreSitePluginsWorkshopAssessmentStrategyComponent } from './workshop-assessment-strategy/workshop-assessment-strategy'; import { CoreSitePluginsBlockComponent } from './block/block'; import { CoreSitePluginsOnlyTitleBlockComponent } from './only-title-block/only-title-block'; +import { CoreCourseComponentsModule } from '@features/course/components/components.module'; @NgModule({ declarations: [ @@ -47,6 +48,7 @@ import { CoreSitePluginsOnlyTitleBlockComponent } from './only-title-block/only- imports: [ CoreSharedModule, CoreCompileHtmlComponentModule, + CoreCourseComponentsModule, ], exports: [ CoreSitePluginsPluginContentComponent, diff --git a/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html b/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html index 930a6e0a0..ac5c6b837 100644 --- a/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html +++ b/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html @@ -11,8 +11,7 @@ + [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false"> + + diff --git a/src/theme/theme.light.scss b/src/theme/theme.light.scss index fa001efb9..a4b0b935e 100644 --- a/src/theme/theme.light.scss +++ b/src/theme/theme.light.scss @@ -257,6 +257,9 @@ --core-courseimage-on-course-height: 150px; + --core-course-module-navigation-max-height: 56px; + --core-course-module-navigation-background: var(--contrast-background); + --addon-calendar-event-category-color: var(--purple); --addon-calendar-event-course-color: var(--red); --addon-calendar-event-group-color: var(--yellow);