-
-
-
+
+
-
+
@@ -31,15 +34,28 @@
-
- {{course.startdate * 1000 | coreFormatDate:"strftimedatefullshort" }}
- - {{course.enddate * 1000 | coreFormatDate:"strftimedatefullshort" }}
-
+
+
+
+
+ {{ 'core.course.startdate' | translate }}: {{ course.startdate * 1000 |
+ coreFormatDate:'strftimedatefullshort' }}
+
+
+
+ {{ 'core.course.enddate' | translate }}: {{ course.enddate * 1000 |
+ coreFormatDate:'strftimedatefullshort' }}
+
+
+
+
+
@@ -55,7 +71,7 @@
- {{ 'core.teachers' | translate }}
+ {{ 'core.teachers' | translate }}
-
-
-
-
-
- {{ instance.name }}
-
- {{ 'core.courses.enrolme' | translate }}
-
-
-
-
-
- {{ 'core.courses.otherenrolments' | translate }}
-
- {{ 'core.courses.completeenrolmentbrowser' | translate }}
-
-
-
-
-
- {{ 'core.courses.notenrollable' | translate }}
-
-
-
-
-
-
-
-
-
-
- {{ 'core.course.downloadcourse' | translate }}
- {{ 'core.course.refreshcourse' | translate }}
-
-
-
-
-
- {{ 'core.course' | translate }}
-
-
-
-
-
-
- {{ 'core.openinbrowser' | translate }}
-
-
-
-
+
+
+
+
+
+
+
+ {{ instance.name }}
+
+ {{ 'core.courses.enrolme' | translate }}
+
+
+
+
+
+ {{ 'core.courses.otherenrolments' | translate }}
+
+ {{ 'core.courses.completeenrolmentbrowser' | translate }}
+
+
+
+
+
+ {{ 'core.courses.notenrollable' | translate }}
+
+
+
+
+
+
+
+ {{item.data.title | translate }}
+
+
+
+
+
+
+ {{ 'core.course' | translate }}
+
+
+
diff --git a/src/core/features/course/pages/preview/preview.module.ts b/src/core/features/course/pages/course-summary/course-summary.module.ts
similarity index 74%
rename from src/core/features/course/pages/preview/preview.module.ts
rename to src/core/features/course/pages/course-summary/course-summary.module.ts
index 168aed845..daeb7f70c 100644
--- a/src/core/features/course/pages/preview/preview.module.ts
+++ b/src/core/features/course/pages/course-summary/course-summary.module.ts
@@ -16,23 +16,30 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CoreSharedModule } from '@/core/shared.module';
-import { CoreCoursePreviewPage } from './preview.page';
+import { CoreCourseSummaryPage } from './course-summary';
const routes: Routes = [
{
path: '',
- component: CoreCoursePreviewPage,
+ component: CoreCourseSummaryPage,
},
];
+@NgModule({
+ imports: [
+ CoreSharedModule,
+ ],
+ declarations: [
+ CoreCourseSummaryPage,
+ ],
+})
+export class CoreCoursePreviewPageComponentModule { }
@NgModule({
imports: [
RouterModule.forChild(routes),
CoreSharedModule,
- ],
- declarations: [
- CoreCoursePreviewPage,
+ CoreCoursePreviewPageComponentModule,
],
exports: [RouterModule],
})
-export class CoreCoursePreviewPageModule { }
+export class CoreCourseSummaryPageModule { }
diff --git a/src/core/features/course/pages/course-summary/course-summary.scss b/src/core/features/course/pages/course-summary/course-summary.scss
new file mode 100644
index 000000000..0386bcac6
--- /dev/null
+++ b/src/core/features/course/pages/course-summary/course-summary.scss
@@ -0,0 +1,28 @@
+@import '~theme/globals.scss';
+
+:host {
+ .core-course-thumb {
+ overflow: hidden;
+ text-align: center;
+ max-height: 35vh;
+ z-index: -1;
+ overflow: hidden;
+ border-bottom: 1px solid var(--stroke);
+ }
+
+
+ .core-customfieldvalue core-format-text {
+ display: inline;
+ }
+
+ .core-course-dates {
+ background: var(--light);
+ border-radius: var(--small-radius);
+ padding: 8px;
+
+ ion-icon {
+ @include margin-horizontal(null, 8px);
+ }
+ }
+
+}
diff --git a/src/core/features/course/pages/preview/preview.page.ts b/src/core/features/course/pages/course-summary/course-summary.ts
similarity index 76%
rename from src/core/features/course/pages/preview/preview.page.ts
rename to src/core/features/course/pages/course-summary/course-summary.ts
index 425bf962b..547e9a6cb 100644
--- a/src/core/features/course/pages/preview/preview.page.ts
+++ b/src/core/features/course/pages/course-summary/course-summary.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { Component, OnDestroy, OnInit } from '@angular/core';
+import { Component, OnDestroy, OnInit, Input } from '@angular/core';
import { IonRefresher } from '@ionic/angular';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreSites } from '@services/sites';
@@ -26,11 +26,12 @@ import {
CoreCoursesProvider,
CoreEnrolledCourseData,
} from '@features/courses/services/courses';
-import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
-import { CoreCourse, CoreCourseProvider } from '@features/course/services/course';
-import { CoreCourseHelper, CorePrefetchStatusInfo } from '@features/course/services/course-helper';
-import { NgZone, Platform, Translate } from '@singletons';
-import { CoreConstants } from '@/core/constants';
+import {
+ CoreCourseOptionsDelegate,
+ CoreCourseOptionsMenuHandlerToDisplay,
+} from '@features/course/services/course-options-delegate';
+import { CoreCourseHelper } from '@features/course/services/course-helper';
+import { ModalController, NgZone, Platform, Translate } from '@singletons';
import { CoreCoursesSelfEnrolPasswordComponent } from '../../../courses/components/self-enrol-password/self-enrol-password';
import { CoreNavigator } from '@services/navigator';
import { CoreUtils } from '@services/utils/utils';
@@ -38,36 +39,31 @@ import { CoreCourseWithImageAndColor } from '@features/courses/services/courses-
import { Subscription } from 'rxjs';
/**
- * Page that allows "previewing" a course and enrolling in it if enabled and not enrolled.
+ * Page that shows the summary of a course including buttons to enrol and other available options.
*/
@Component({
- selector: 'page-core-course-preview',
- templateUrl: 'preview.html',
- styleUrls: ['preview.scss'],
+ selector: 'page-core-course-summary',
+ templateUrl: 'course-summary.html',
+ styleUrls: ['course-summary.scss'],
})
-export class CoreCoursePreviewPage implements OnInit, OnDestroy {
+export class CoreCourseSummaryPage implements OnInit, OnDestroy {
+
+ @Input() course?: CoreCourseSummaryData;
+ @Input() courseId = 0;
- course?: CoreCourseSummaryData;
isEnrolled = false;
canAccessCourse = true;
selfEnrolInstances: CoreCourseEnrolmentMethod[] = [];
paypalEnabled = false;
dataLoaded = false;
- avoidOpenCourse = false;
- prefetchCourseData: CorePrefetchStatusInfo = {
- icon: '',
- statusTranslatable: 'core.loading',
- status: '',
- loading: true,
- };
+ isModal = false;
- statusDownloaded = CoreConstants.DOWNLOADED;
-
- downloadCourseEnabled: boolean;
courseUrl = '';
courseImageUrl?: string;
progress?: number;
+ courseMenuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = [];
+
protected isGuestEnabled = false;
protected useGuestAccess = false;
protected guestInstanceId?: number;
@@ -76,22 +72,10 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
protected enrolUrl = '';
protected pageDestroyed = false;
protected courseStatusObserver?: CoreEventObserver;
- protected courseId!: number;
protected appResumeSubscription: Subscription;
protected waitingForBrowserEnrol = false;
constructor() {
- this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
-
- if (this.downloadCourseEnabled) {
- // Listen for status change in course.
- this.courseStatusObserver = CoreEvents.on(CoreEvents.COURSE_STATUS_CHANGED, (data) => {
- if (data.courseId == this.courseId || data.courseId == CoreCourseProvider.ALL_COURSES_CLEARED) {
- this.updateCourseStatus(data.status);
- }
- }, CoreSites.getCurrentSiteId());
- }
-
// Refresh the view when the app is resumed.
this.appResumeSubscription = Platform.resume.subscribe(() => {
if (!this.waitingForBrowserEnrol || !this.dataLoaded) {
@@ -111,47 +95,29 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
* @inheritdoc
*/
async ngOnInit(): Promise
{
- try {
- this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
- } catch (error) {
- CoreDomUtils.showErrorModal(error);
- CoreNavigator.back();
+ if (!this.courseId) {
+ // Opened as a page.
+ try {
+ this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
+ } catch (error) {
+ CoreDomUtils.showErrorModal(error);
+ CoreNavigator.back();
+ this.closeModal(); // Just in case.
- return;
+ return;
+ }
+
+ this.course = CoreNavigator.getRouteParam('course');
+ } else {
+ // Opened as a modal.
+ this.isModal = true;
}
- this.avoidOpenCourse = !!CoreNavigator.getRouteBooleanParam('avoidOpenCourse');
- this.course = CoreNavigator.getRouteParam('course');
-
const currentSiteUrl = CoreSites.getRequiredCurrentSite().getURL();
this.enrolUrl = CoreTextUtils.concatenatePaths(currentSiteUrl, 'enrol/index.php?id=' + this.courseId);
this.courseUrl = CoreTextUtils.concatenatePaths(currentSiteUrl, 'course/view.php?id=' + this.courseId);
- try {
- await this.getCourse();
- } finally {
- if (this.downloadCourseEnabled) {
-
- // Determine course prefetch icon.
- this.prefetchCourseData = await CoreCourseHelper.getCourseStatusIconAndTitle(this.courseId);
-
- if (this.prefetchCourseData.loading) {
- // Course is being downloaded. Get the download promise.
- const promise = CoreCourseHelper.getCourseDownloadPromise(this.courseId);
- if (promise) {
- // There is a download promise. If it fails, show an error.
- promise.catch((error) => {
- if (!this.pageDestroyed) {
- CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
- }
- });
- } else {
- // No download, this probably means that the app was closed while downloading. Set previous status.
- CoreCourse.setCoursePreviousStatus(this.courseId);
- }
- }
- }
- }
+ await this.getCourse();
}
/**
@@ -184,8 +150,10 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
/**
* Convenience function to get course. We use this to determine if a user can see the course or not.
+ *
+ * @param refresh If it's refreshing content.
*/
- protected async getCourse(): Promise {
+ protected async getCourse(refresh = false): Promise {
// Get course enrolment methods.
this.selfEnrolInstances = [];
@@ -262,16 +230,33 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
this.progress = this.course.progress;
}
+ await this.loadMenuHandlers(refresh);
+
this.dataLoaded = true;
}
+ /**
+ * Load the course menu handlers.
+ *
+ * @param refresh If it's refreshing content.
+ * @return Promise resolved when done.
+ */
+ protected async loadMenuHandlers(refresh?: boolean): Promise {
+ if (!this.course) {
+ return;
+ }
+
+ this.courseMenuHandlers =
+ await CoreCourseOptionsDelegate.getMenuHandlersToDisplay(this.course, refresh, this.useGuestAccess);
+ }
+
/**
* Open the course.
*
* @param replaceCurrentPage If current place should be replaced in the navigation stack.
*/
openCourse(replaceCurrentPage = false): void {
- if (!this.canAccessCourse || !this.course || this.avoidOpenCourse) {
+ if (!this.canAccessCourse || !this.course || this.isModal) {
return;
}
@@ -403,20 +388,6 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
});
}
- /**
- * Update the course status icon and title.
- *
- * @param status Status to show.
- */
- protected updateCourseStatus(status: string): void {
- const statusData = CoreCourseHelper.getCoursePrefetchStatusInfo(status);
-
- this.prefetchCourseData.status = statusData.status;
- this.prefetchCourseData.icon = statusData.icon;
- this.prefetchCourseData.statusTranslatable = statusData.statusTranslatable;
- this.prefetchCourseData.loading = statusData.loading;
- }
-
/**
* Wait for the user to be enrolled in the course.
*
@@ -453,18 +424,20 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
}
/**
- * Prefetch the course.
+ * Opens a menu item registered to the delegate.
+ *
+ * @param item Item to open
*/
- async prefetchCourse(): Promise {
- try {
- await CoreCourseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course as CoreEnrolledCourseData, {
- isGuest: this.useGuestAccess,
- });
- } catch (error) {
- if (!this.pageDestroyed) {
- CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
- }
- }
+ openMenuItem(item: CoreCourseOptionsMenuHandlerToDisplay): void {
+ const params = Object.assign({ course: this.course }, item.data.pageParams);
+ CoreNavigator.navigateToSitePath(item.data.page, { params });
+ }
+
+ /**
+ * Close the modal.
+ */
+ closeModal(): void {
+ ModalController.dismiss();
}
/**
diff --git a/src/core/features/course/pages/index/index.module.ts b/src/core/features/course/pages/index/index.module.ts
index 314528baa..fafa96425 100644
--- a/src/core/features/course/pages/index/index.module.ts
+++ b/src/core/features/course/pages/index/index.module.ts
@@ -19,6 +19,7 @@ import { resolveModuleRoutes } from '@/app/app-routing.module';
import { CoreSharedModule } from '@/core/shared.module';
import { CoreCourseIndexPage } from '.';
import { COURSE_INDEX_ROUTES } from './index-routing.module';
+import { CoreCoursePreviewPageComponentModule } from '../course-summary/course-summary.module';
function buildRoutes(injector: Injector): Routes {
const routes = resolveModuleRoutes(injector, COURSE_INDEX_ROUTES);
@@ -42,6 +43,7 @@ function buildRoutes(injector: Injector): Routes {
],
imports: [
CoreSharedModule,
+ CoreCoursePreviewPageComponentModule,
],
declarations: [
CoreCourseIndexPage,
diff --git a/src/core/features/course/pages/index/index.ts b/src/core/features/course/pages/index/index.ts
index 6f0f32b25..449058e33 100644
--- a/src/core/features/course/pages/index/index.ts
+++ b/src/core/features/course/pages/index/index.ts
@@ -28,6 +28,7 @@ import { CoreNavigator } from '@services/navigator';
import { CONTENTS_PAGE_NAME } from '@features/course/course.module';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreCollapsibleHeaderDirective } from '@directives/collapsible-header';
+import { CoreCourseSummaryPage } from '../course-summary/course-summary';
/**
* Page that displays the list of courses the user is enrolled in.
@@ -279,10 +280,13 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
return;
}
- CoreNavigator.navigateToSitePath(
- `/course/${this.course.id}/preview`,
- { params: { course: this.course, avoidOpenCourse: true } },
- );
+ CoreDomUtils.openSideModal({
+ component: CoreCourseSummaryPage,
+ componentProps: {
+ courseId: this.course.id,
+ course: this.course,
+ },
+ });
}
/**
diff --git a/src/core/features/course/pages/preview/preview.scss b/src/core/features/course/pages/preview/preview.scss
deleted file mode 100644
index 8f159c333..000000000
--- a/src/core/features/course/pages/preview/preview.scss
+++ /dev/null
@@ -1,47 +0,0 @@
-:host {
- ion-content:not(.animating) {
- &::part(scroll) {
- perspective: 1px;
- perspective-origin: center top;
- transform-style: preserve-3d;
- }
-
- .core-course-thumb {
- transform-origin: center top;
-
- --scroll-factor: 0.5;
- --translate-z: calc(-2 * var(--scroll-factor))px;
- --scale: calc(1 + var(--scroll-factor) * 2);
-
- /**
- * Calculated with scroll-factor: 0.5;
- * translate-z: -2 * $scroll-factor px;
- * scale: 1 + $scroll-factor * 2;
- */
- transform: translateZ(-1px) scale(2);
- }
- }
-
- .core-course-thumb-parallax-content {
- transform: translateZ(0);
- -webkit-filter: drop-shadow(0px -3px 3px rgba(var(--drop-shadow)));
- filter: drop-shadow(0px -3px 3px rgba(var(--drop-shadow)));
- }
-
- .core-course-thumb-parallax {
- height: 40vw;
- max-height: 35vh;
- z-index: -1;
- overflow: hidden;
- }
-
- .core-course-thumb {
- overflow: hidden;
- text-align: center;
- }
-
-
- .core-customfieldvalue core-format-text {
- display: inline;
- }
-}
diff --git a/src/core/features/courses/components/course-list-item/course-list-item.ts b/src/core/features/courses/components/course-list-item/course-list-item.ts
index c44645ed9..c73a3dd3a 100644
--- a/src/core/features/courses/components/course-list-item/course-list-item.ts
+++ b/src/core/features/courses/components/course-list-item/course-list-item.ts
@@ -171,7 +171,7 @@ export class CoreCoursesCourseListItemComponent implements OnInit, OnDestroy, On
CoreCourseHelper.openCourse(this.course);
} else {
CoreNavigator.navigateToSitePath(
- `/course/${this.course.id}/preview`,
+ `/course/${this.course.id}/summary`,
{ params: { course: this.course } },
);
}
diff --git a/src/core/features/courses/services/handlers/enrol-push-click.ts b/src/core/features/courses/services/handlers/enrol-push-click.ts
index c606d3384..63408f432 100644
--- a/src/core/features/courses/services/handlers/enrol-push-click.ts
+++ b/src/core/features/courses/services/handlers/enrol-push-click.ts
@@ -66,7 +66,7 @@ export class CoreCoursesEnrolPushClickHandlerService implements CorePushNotifica
params.selectedTab = 'participants'; // @todo: Set this when participants is done.
} else if (!result.enrolled) {
// User not enrolled anymore, open the preview page.
- page += '/preview';
+ page += '/summary';
}
await CoreNavigator.navigateToSitePath(page, { params, siteId: notification.site });
diff --git a/src/core/features/courses/services/handlers/request-push-click.ts b/src/core/features/courses/services/handlers/request-push-click.ts
index 92fdea4fc..2eb12d9a9 100644
--- a/src/core/features/courses/services/handlers/request-push-click.ts
+++ b/src/core/features/courses/services/handlers/request-push-click.ts
@@ -79,7 +79,7 @@ export class CoreCoursesRequestPushClickHandlerService implements CorePushNotifi
if (!result.enrolled) {
// User not enrolled (shouldn't happen), open the preview page.
- page += '/preview';
+ page += '/summary';
}
await CoreNavigator.navigateToSitePath(page, { params, siteId: notification.site });
diff --git a/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts b/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts
index ce23461e3..f1140b4ec 100644
--- a/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts
+++ b/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts
@@ -58,7 +58,6 @@ export class CoreMainMenuUserButtonComponent implements OnInit {
CoreDomUtils.openSideModal({
component: CoreMainMenuUserMenuComponent,
- cssClass: 'core-modal-lateral',
});
}
diff --git a/upgrade.txt b/upgrade.txt
index a7549b1c3..e943d0f93 100644
--- a/upgrade.txt
+++ b/upgrade.txt
@@ -18,6 +18,7 @@ information provided here is intended especially for developers.
- CoreCourseHelperProvider.openCourse parameters changed, now it admits CoreNavigationOptions + siteId on the same object that includes Params passed to page.
- displaySectionSelector has been deprecated on CoreCourseFormatHandler, use displayCourseIndex instead.
- Most of the functions or callbacks that handle redirects/deeplinks have been modified to accept an object instead of just path + options. E.g.: CoreLoginHelper.isSiteLoggedOut, CoreLoginHelper.openBrowserForSSOLogin, CoreLoginHelper.openBrowserForOAuthLogin, CoreLoginHelper.prepareForSSOLogin, CoreApp.storeRedirect, CoreSites.loadSite.
+- Course preview page route has changed from course/:courseId/preview to course/:courseId/summary to match with the page name and characteristics.
=== 3.9.5 ===