MOBILE-3970 course: Open course summary in a lateral modal

main
Pau Ferrer Ocaña 2022-02-11 15:37:26 +01:00
parent 69d36803fc
commit 19b3a394d2
7 changed files with 109 additions and 113 deletions

View File

@ -19,6 +19,7 @@ import { resolveModuleRoutes } from '@/app/app-routing.module';
import { CoreSharedModule } from '@/core/shared.module'; import { CoreSharedModule } from '@/core/shared.module';
import { CoreCourseIndexPage } from '.'; import { CoreCourseIndexPage } from '.';
import { COURSE_INDEX_ROUTES } from './index-routing.module'; import { COURSE_INDEX_ROUTES } from './index-routing.module';
import { CoreCoursePreviewPageComponentModule } from '../preview/preview.module';
function buildRoutes(injector: Injector): Routes { function buildRoutes(injector: Injector): Routes {
const routes = resolveModuleRoutes(injector, COURSE_INDEX_ROUTES); const routes = resolveModuleRoutes(injector, COURSE_INDEX_ROUTES);
@ -42,6 +43,7 @@ function buildRoutes(injector: Injector): Routes {
], ],
imports: [ imports: [
CoreSharedModule, CoreSharedModule,
CoreCoursePreviewPageComponentModule,
], ],
declarations: [ declarations: [
CoreCourseIndexPage, CoreCourseIndexPage,

View File

@ -28,6 +28,7 @@ import { CoreNavigator } from '@services/navigator';
import { CONTENTS_PAGE_NAME } from '@features/course/course.module'; import { CONTENTS_PAGE_NAME } from '@features/course/course.module';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { CoreCollapsibleHeaderDirective } from '@directives/collapsible-header'; import { CoreCollapsibleHeaderDirective } from '@directives/collapsible-header';
import { CoreCoursePreviewPage } from '../preview/preview.page';
/** /**
* Page that displays the list of courses the user is enrolled in. * Page that displays the list of courses the user is enrolled in.
@ -279,10 +280,13 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
return; return;
} }
CoreNavigator.navigateToSitePath( CoreDomUtils.openSideModal<void>({
`/course/${this.course.id}/preview`, component: CoreCoursePreviewPage,
{ params: { course: this.course, avoidOpenCourse: true } }, componentProps: {
); courseId: this.course.id,
course: this.course,
},
});
} }
/** /**

View File

@ -8,6 +8,11 @@
{{'core.course.coursesummary' | translate}} {{'core.course.coursesummary' | translate}}
</h1> </h1>
</ion-title> </ion-title>
<ion-buttons slot="end" *ngIf="avoidOpenCourse">
<ion-button fill="clear" (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
<ion-icon slot="icon-only" name="fas-times" aria-hidden="true"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content> <ion-content>
@ -15,12 +20,10 @@
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher> </ion-refresher>
<core-loading [hideUntil]="dataLoaded"> <core-loading [hideUntil]="dataLoaded">
<div *ngIf="courseImageUrl" class="core-course-thumb-parallax"> <div *ngIf="courseImageUrl" class="core-course-thumb">
<div class="core-course-thumb">
<img [src]="courseImageUrl" core-external-content alt="" /> <img [src]="courseImageUrl" core-external-content alt="" />
</div> </div>
</div> <div *ngIf="course">
<div class="core-course-thumb-parallax-content" *ngIf="course">
<ion-item class="ion-text-wrap"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<p *ngIf="course.categoryname"> <p *ngIf="course.categoryname">
@ -40,6 +43,10 @@
</core-progress-bar> </core-progress-bar>
</div> </div>
</ion-label> </ion-label>
<ion-button fill="clear" [href]="courseUrl" core-link [showBrowserWarning]="false" color="dark"
[attr.aria-label]="'core.openinbrowser' | translate" slot="end">
<ion-icon name="fas-external-link-alt" slot="start" aria-hidden="true"></ion-icon>
</ion-button>
</ion-item> </ion-item>
<ion-item class="ion-text-wrap" *ngIf="course.summary" detail="false"> <ion-item class="ion-text-wrap" *ngIf="course.summary" detail="false">
@ -87,7 +94,11 @@
</ng-container> </ng-container>
</ion-label> </ion-label>
</ion-item> </ion-item>
</div>
</core-loading>
</ion-content>
<ion-footer>
<!-- Enrol --> <!-- Enrol -->
<ng-container *ngIf="!isEnrolled"> <ng-container *ngIf="!isEnrolled">
<ion-item class="ion-text-wrap" *ngFor="let instance of selfEnrolInstances"> <ion-item class="ion-text-wrap" *ngFor="let instance of selfEnrolInstances">
@ -115,8 +126,8 @@
<ion-button class="ion-margin" *ngIf="canAccessCourse && downloadCourseEnabled" (click)="prefetchCourse()" expand="block" <ion-button class="ion-margin" *ngIf="canAccessCourse && downloadCourseEnabled" (click)="prefetchCourse()" expand="block"
[attr.aria-label]="prefetchCourseData.statusTranslatable | translate"> [attr.aria-label]="prefetchCourseData.statusTranslatable | translate">
<ion-icon *ngIf="(prefetchCourseData.status != statusDownloaded) && !prefetchCourseData.loading" <ion-icon *ngIf="(prefetchCourseData.status != statusDownloaded) && !prefetchCourseData.loading" [name]="prefetchCourseData.icon"
[name]="prefetchCourseData.icon" slot="start" aria-hidden="true"> slot="start" aria-hidden="true">
</ion-icon> </ion-icon>
<ion-icon *ngIf="(prefetchCourseData.status == statusDownloaded) && !prefetchCourseData.loading" slot="start" <ion-icon *ngIf="(prefetchCourseData.status == statusDownloaded) && !prefetchCourseData.loading" slot="start"
[name]="prefetchCourseData.icon" color="success" aria-hidden="true" role="status"> [name]="prefetchCourseData.icon" color="success" aria-hidden="true" role="status">
@ -132,14 +143,4 @@
{{ 'core.course' | translate }} {{ 'core.course' | translate }}
</ion-label> </ion-label>
</ion-button> </ion-button>
</ion-footer>
<ion-button class="ion-margin" [href]="courseUrl" core-link [showBrowserWarning]="false" expand="block">
<ion-icon name="fas-external-link-alt" slot="start" aria-hidden="true"></ion-icon>
<ion-label>
{{ 'core.openinbrowser' | translate }}
</ion-label>
</ion-button>
</div>
</core-loading>
</ion-content>

View File

@ -24,15 +24,22 @@ const routes: Routes = [
component: CoreCoursePreviewPage, component: CoreCoursePreviewPage,
}, },
]; ];
@NgModule({ @NgModule({
imports: [ imports: [
RouterModule.forChild(routes),
CoreSharedModule, CoreSharedModule,
], ],
declarations: [ declarations: [
CoreCoursePreviewPage, CoreCoursePreviewPage,
], ],
})
export class CoreCoursePreviewPageComponentModule { }
@NgModule({
imports: [
RouterModule.forChild(routes),
CoreSharedModule,
CoreCoursePreviewPageComponentModule,
],
exports: [RouterModule], exports: [RouterModule],
}) })
export class CoreCoursePreviewPageModule { } export class CoreCoursePreviewPageModule { }

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // 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 { IonRefresher } from '@ionic/angular';
import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreSites } from '@services/sites'; import { CoreSites } from '@services/sites';
@ -29,7 +29,7 @@ import {
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate'; import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
import { CoreCourse, CoreCourseProvider } from '@features/course/services/course'; import { CoreCourse, CoreCourseProvider } from '@features/course/services/course';
import { CoreCourseHelper, CorePrefetchStatusInfo } from '@features/course/services/course-helper'; import { CoreCourseHelper, CorePrefetchStatusInfo } from '@features/course/services/course-helper';
import { NgZone, Platform, Translate } from '@singletons'; import { ModalController, NgZone, Platform, Translate } from '@singletons';
import { CoreConstants } from '@/core/constants'; import { CoreConstants } from '@/core/constants';
import { CoreCoursesSelfEnrolPasswordComponent } from '../../../courses/components/self-enrol-password/self-enrol-password'; import { CoreCoursesSelfEnrolPasswordComponent } from '../../../courses/components/self-enrol-password/self-enrol-password';
import { CoreNavigator } from '@services/navigator'; import { CoreNavigator } from '@services/navigator';
@ -47,7 +47,9 @@ import { Subscription } from 'rxjs';
}) })
export class CoreCoursePreviewPage implements OnInit, OnDestroy { export class CoreCoursePreviewPage implements OnInit, OnDestroy {
course?: CoreCourseSummaryData; @Input() course?: CoreCourseSummaryData;
@Input() courseId = 0;
isEnrolled = false; isEnrolled = false;
canAccessCourse = true; canAccessCourse = true;
selfEnrolInstances: CoreCourseEnrolmentMethod[] = []; selfEnrolInstances: CoreCourseEnrolmentMethod[] = [];
@ -76,7 +78,6 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
protected enrolUrl = ''; protected enrolUrl = '';
protected pageDestroyed = false; protected pageDestroyed = false;
protected courseStatusObserver?: CoreEventObserver; protected courseStatusObserver?: CoreEventObserver;
protected courseId!: number;
protected appResumeSubscription: Subscription; protected appResumeSubscription: Subscription;
protected waitingForBrowserEnrol = false; protected waitingForBrowserEnrol = false;
@ -111,17 +112,23 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
* @inheritdoc * @inheritdoc
*/ */
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
if (!this.courseId) {
// Opened as a page.
try { try {
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModal(error); CoreDomUtils.showErrorModal(error);
CoreNavigator.back(); CoreNavigator.back();
this.closeModal(); // Just in case.
return; return;
} }
this.avoidOpenCourse = !!CoreNavigator.getRouteBooleanParam('avoidOpenCourse');
this.course = CoreNavigator.getRouteParam('course'); this.course = CoreNavigator.getRouteParam('course');
} else {
// Opened as a modal.
this.avoidOpenCourse = true;
}
const currentSiteUrl = CoreSites.getRequiredCurrentSite().getURL(); const currentSiteUrl = CoreSites.getRequiredCurrentSite().getURL();
this.enrolUrl = CoreTextUtils.concatenatePaths(currentSiteUrl, 'enrol/index.php?id=' + this.courseId); this.enrolUrl = CoreTextUtils.concatenatePaths(currentSiteUrl, 'enrol/index.php?id=' + this.courseId);
@ -452,6 +459,13 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
} }
} }
/**
* Close the modal.
*/
closeModal(): void {
ModalController.dismiss();
}
/** /**
* Prefetch the course. * Prefetch the course.
*/ */

View File

@ -1,43 +1,12 @@
:host { :host {
ion-content:not(.animating) {
&::part(scroll) {
perspective: 1px;
perspective-origin: center top;
transform-style: preserve-3d;
}
.core-course-thumb { .core-course-thumb {
transform-origin: center top; overflow: hidden;
text-align: center;
--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; height: 40vw;
max-height: 35vh; max-height: 35vh;
z-index: -1; z-index: -1;
overflow: hidden; overflow: hidden;
} border-bottom: 1px solid var(--stroke);
.core-course-thumb {
overflow: hidden;
text-align: center;
} }

View File

@ -58,7 +58,6 @@ export class CoreMainMenuUserButtonComponent implements OnInit {
CoreDomUtils.openSideModal<void>({ CoreDomUtils.openSideModal<void>({
component: CoreMainMenuUserMenuComponent, component: CoreMainMenuUserMenuComponent,
cssClass: 'core-modal-lateral',
}); });
} }