forked from EVOgeek/Vmeda.Online
		
	MOBILE-3594 course: Improve course listing and add course image parallax
This commit is contained in:
		
							parent
							
								
									c3e59edf18
								
							
						
					
					
						commit
						add521a0e7
					
				| @ -1,11 +1,31 @@ | ||||
| <ion-item class="ion-text-wrap" (click)="openCourse()" [class.item-disabled]="course.visible == 0" | ||||
| [title]="course.displayname || course.fullname" detail> | ||||
|     <ion-icon name="fas-graduation-cap" slot="start"></ion-icon> | ||||
|     <ion-icon *ngIf="!course.courseImage" name="fas-graduation-cap" slot="start" class="course-icon" | ||||
|         [attr.course-color]="course.color ? null : course.colorNumber" [style.color]="course.color"></ion-icon> | ||||
|     <ion-avatar *ngIf="course.courseImage" slot="start"> | ||||
|         <img [src]="course.courseImage" core-external-content alt=""/> | ||||
|     </ion-avatar> | ||||
|     <ion-label> | ||||
|         <p *ngIf="course.categoryname || (course.displayname && course.shortname && course.fullname != course.displayname)" | ||||
|             class="core-course-additional-info"> | ||||
|             <span *ngIf="course.categoryname" class="core-course-category"> | ||||
|                 <core-format-text [text]="course.categoryname"></core-format-text> | ||||
|             </span> | ||||
|             <span *ngIf="course.categoryname && course.displayname && course.shortname && course.fullname != course.displayname" | ||||
|                 class="core-course-category"> | </span> | ||||
|             <span *ngIf="course.displayname && course.shortname && course.fullname != course.displayname" | ||||
|                 class="core-course-shortname"> | ||||
|                 <core-format-text [text]="course.shortname" contextLevel="course" [contextInstanceId]="course.id"> | ||||
|                 </core-format-text> | ||||
|             </span> | ||||
|         </p> | ||||
|         <h2> | ||||
|             <core-format-text [text]="course.displayname || course.fullname" contextLevel="course" [contextInstanceId]="course.id"> | ||||
|             </core-format-text> | ||||
|         </h2> | ||||
|         <p *ngIf="isEnrolled && course.progress != null && course.progress! >= 0 && course.completionusertracked !== false"> | ||||
|             <core-progress-bar [progress]="course.progress"></core-progress-bar> | ||||
|         </p> | ||||
|     </ion-label> | ||||
|     <ng-container *ngIf="!isEnrolled"> | ||||
|         <ion-icon *ngFor="let icon of icons" color="dark" size="small" | ||||
|  | ||||
| @ -0,0 +1,48 @@ | ||||
| :host { | ||||
|     .course-icon { | ||||
|         color: white; | ||||
|         background: var(--gray-light); | ||||
|         padding: 8px; | ||||
|         font-size: 24px; | ||||
|         border-radius: 50%; | ||||
|         margin-inline-end: 16px; | ||||
|         -webkit-transition: all 50ms ease-in-out; | ||||
|         transition: all 50ms ease-in-out; | ||||
|     } | ||||
| 
 | ||||
|     ion-icon[course-color="0"] { | ||||
|         color: var(--core-course-color-0); | ||||
|     } | ||||
|     ion-icon[course-color="1"] { | ||||
|         color: var(--core-course-color-1); | ||||
|     } | ||||
|     ion-icon[course-color="2"] { | ||||
|         color: var(--core-course-color-2); | ||||
|     } | ||||
|     ion-icon[course-color="3"] { | ||||
|         color: var(--core-course-color-3); | ||||
|     } | ||||
|     ion-icon[course-color="4"] { | ||||
|         color: var(--core-course-color-4); | ||||
|     } | ||||
|     ion-icon[course-color="5"] { | ||||
|         color: var(--core-course-color-5); | ||||
|     } | ||||
|     ion-icon[course-color="6"] { | ||||
|         color: var(--core-course-color-6); | ||||
|     } | ||||
|     ion-icon[course-color="7"] { | ||||
|         color: var(--core-course-color-7); | ||||
|     } | ||||
|     ion-icon[course-color="8"] { | ||||
|         color: var(--core-course-color-8); | ||||
|     } | ||||
|     ion-icon[course-color="9"] { | ||||
|         color: var(--core-course-color-9); | ||||
|     } | ||||
| 
 | ||||
|     ion-avatar { | ||||
|         -webkit-transition: all 50ms ease-in-out; | ||||
|         transition: all 50ms ease-in-out; | ||||
|     } | ||||
| } | ||||
| @ -14,8 +14,8 @@ | ||||
| 
 | ||||
| import { Component, Input, OnInit } from '@angular/core'; | ||||
| import { NavController } from '@ionic/angular'; | ||||
| import { CoreCourseHelper } from '@features/course/services/course.helper'; | ||||
| import { CoreCourses, CoreCourseSearchedData } from '@features/courses/services/courses'; | ||||
| import { CoreCourses, CoreCourseSearchedData } from '../../services/courses'; | ||||
| import { CoreCoursesHelper, CoreCourseWithImageAndColor } from '../../services/courses.helper'; | ||||
| 
 | ||||
| /** | ||||
|  * This directive is meant to display an item for a list of courses. | ||||
| @ -27,10 +27,14 @@ import { CoreCourses, CoreCourseSearchedData } from '@features/courses/services/ | ||||
| @Component({ | ||||
|     selector: 'core-courses-course-list-item', | ||||
|     templateUrl: 'core-courses-course-list-item.html', | ||||
|     styleUrls: ['course-list-item.scss'], | ||||
| }) | ||||
| export class CoreCoursesCourseListItemComponent implements OnInit { | ||||
| 
 | ||||
|     @Input() course!: CoreCourseSearchedData; // The course to render.
 | ||||
|     @Input() course!: CoreCourseSearchedData & CoreCourseWithImageAndColor & { | ||||
|         completionusertracked?: boolean; // If the user is completion tracked.
 | ||||
|         progress?: number; // Progress percentage.
 | ||||
|     }; // The course to render.
 | ||||
| 
 | ||||
|     icons: CoreCoursesEnrolmentIcons[] = []; | ||||
|     isEnrolled = false; | ||||
| @ -44,9 +48,13 @@ export class CoreCoursesCourseListItemComponent implements OnInit { | ||||
|      * Component being initialized. | ||||
|      */ | ||||
|     async ngOnInit(): Promise<void> { | ||||
|         CoreCoursesHelper.instance.loadCourseColorAndImage(this.course); | ||||
| 
 | ||||
|         // Check if the user is enrolled in the course.
 | ||||
|         try { | ||||
|             await CoreCourses.instance.getUserCourse(this.course.id); | ||||
|             const course = await CoreCourses.instance.getUserCourse(this.course.id); | ||||
|             this.course.progress = course.progress; | ||||
|             this.course.completionusertracked = course.completionusertracked; | ||||
| 
 | ||||
|             this.isEnrolled = true; | ||||
|         } catch { | ||||
| @ -87,11 +95,13 @@ export class CoreCoursesCourseListItemComponent implements OnInit { | ||||
|      * @param course The course to open. | ||||
|      */ | ||||
|     openCourse(): void { | ||||
|         if (this.isEnrolled) { | ||||
|         /* if (this.isEnrolled) { | ||||
|             CoreCourseHelper.instance.openCourse(this.course); | ||||
|         } else { | ||||
|             this.navCtrl.navigateForward('/courses/preview', { queryParams: { course: this.course } }); | ||||
|         } | ||||
|         } */ | ||||
|         // @todo while opencourse function is not completed, open preview page.
 | ||||
|         this.navCtrl.navigateForward('/courses/preview', { queryParams: { course: this.course } }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -6,34 +6,34 @@ | ||||
|         height: calc(100% - 20px); | ||||
| 
 | ||||
|         &[course-color="0"] .core-course-thumb { | ||||
|             background: var(--core-course-image-background-0); | ||||
|             background: var(--core-course-color-0); | ||||
|         } | ||||
|         &[course-color="1"] .core-course-thumb { | ||||
|             background: var(--core-course-image-background-1); | ||||
|             background: var(--core-course-color-1); | ||||
|         } | ||||
|         &[course-color="2"] .core-course-thumb { | ||||
|             background: var(--core-course-image-background-2); | ||||
|             background: var(--core-course-color-2); | ||||
|         } | ||||
|         &[course-color="3"] .core-course-thumb { | ||||
|             background: var(--core-course-image-background-3); | ||||
|             background: var(--core-course-color-3); | ||||
|         } | ||||
|         &[course-color="4"] .core-course-thumb { | ||||
|             background: var(--core-course-image-background-4); | ||||
|             background: var(--core-course-color-4); | ||||
|         } | ||||
|         &[course-color="5"] .core-course-thumb { | ||||
|             background: var(--core-course-image-background-5); | ||||
|             background: var(--core-course-color-5); | ||||
|         } | ||||
|         &[course-color="6"] .core-course-thumb { | ||||
|             background: var(--core-course-image-background-6); | ||||
|             background: var(--core-course-color-6); | ||||
|         } | ||||
|         &[course-color="7"] .core-course-thumb { | ||||
|             background: var(--core-course-image-background-7); | ||||
|             background: var(--core-course-color-7); | ||||
|         } | ||||
|         &[course-color="8"] .core-course-thumb { | ||||
|             background: var(--core-course-image-background-8); | ||||
|             background: var(--core-course-color-8); | ||||
|         } | ||||
|         &[course-color="9"] .core-course-thumb { | ||||
|             background: var(--core-course-image-background-9); | ||||
|             background: var(--core-course-color-9); | ||||
|         } | ||||
| 
 | ||||
|         .core-course-thumb { | ||||
|  | ||||
| @ -11,11 +11,12 @@ | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
|     <core-loading [hideUntil]="dataLoaded"> | ||||
| 
 | ||||
|         <ion-list *ngIf="course"> | ||||
|         <div class="core-course-thumb-parallax"> | ||||
|             <div *ngIf="courseImageUrl" (click)="openCourse()" class="core-course-thumb"> | ||||
|                 <img [src]="courseImageUrl" core-external-content alt=""/> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div class="core-course-thumb-parallax-content"> | ||||
|             <ion-item class="ion-text-wrap" (click)="openCourse()" [title]="course.fullname" [attr.details]="!avoidOpenCourse && canAccessCourse"> | ||||
|                 <ion-icon name="fas-graduation-cap" fixed-width slot="start"></ion-icon> | ||||
|                 <ion-label> | ||||
| @ -37,7 +38,11 @@ | ||||
|             </ion-item> | ||||
| 
 | ||||
|             <ng-container class="ion-text-wrap" *ngIf="course.contacts && course.contacts.length"> | ||||
|                 <ion-item-divider>{{ 'core.teachers' | translate }}</ion-item-divider> | ||||
|                 <ion-item-divider> | ||||
|                     <ion-label> | ||||
|                         <h2>{{ 'core.teachers' | translate }}</h2> | ||||
|                     </ion-label> | ||||
|                 </ion-item-divider> | ||||
|                 <ion-item class="ion-text-wrap" *ngFor="let contact of course.contacts" core-user-link | ||||
|                     [userId]="contact.id" | ||||
|                     [courseId]="isEnrolled ? course.id : null" | ||||
| @ -115,6 +120,6 @@ | ||||
|                 <ion-icon name="fas-external-link-alt" slot="start"></ion-icon> | ||||
|                 <ion-label><h2>{{ 'core.openinbrowser' | translate }}</h2></ion-label> | ||||
|             </ion-item> | ||||
|         </ion-list> | ||||
|         </div> | ||||
|     </core-loading> | ||||
| </ion-content> | ||||
|  | ||||
| @ -1,20 +1,39 @@ | ||||
| :host { | ||||
|     .core-course-thumb { | ||||
|         height: 150px; | ||||
|         width: 100%; | ||||
|     --scroll-factor: 0.5; | ||||
|     --translate-z: calc(-2 * var(--scroll-factor))px; | ||||
|     --scale: calc(1 + var(--scroll-factor) * 2); | ||||
| 
 | ||||
|     perspective: 1px; | ||||
|     perspective-origin: center top; | ||||
|     transform-style: preserve-3d; | ||||
| 
 | ||||
|     .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; | ||||
|         cursor: pointer; | ||||
|         pointer-events: auto; | ||||
|         position: relative; | ||||
|         transform-origin: center top; | ||||
| 
 | ||||
|         img { | ||||
|             position: absolute; | ||||
|             top: 0; | ||||
|             bottom: 0; | ||||
|             margin: auto; | ||||
|             width: 100%; | ||||
|         } | ||||
|         /** | ||||
|          * Calculated with scroll-factor: 0.5; | ||||
|          * translate-z: -2 * $scroll-factor px; | ||||
|          * scale: 1 + $scroll-factor * 2; | ||||
|          */ | ||||
|         transform: translateZ(-1px) scale(2); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     .core-customfieldvalue core-format-text { | ||||
|         display: inline; | ||||
|     } | ||||
|  | ||||
| @ -18,6 +18,7 @@ import { CoreUtils } from '@services/utils/utils'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreCourses, CoreCourseSearchedData, CoreCourseUserAdminOrNavOptionIndexed, CoreEnrolledCourseData } from './courses'; | ||||
| import { makeSingleton } from '@singletons/core.singletons'; | ||||
| import { CoreWSExternalFile } from '@services/ws'; | ||||
| // import { AddonCourseCompletionProvider } from '@addon/coursecompletion/providers/coursecompletion';
 | ||||
| // import { CoreCoursePickerMenuPopoverComponent } from '@components/course-picker-menu/course-picker-menu-popover';
 | ||||
| 
 | ||||
| @ -51,20 +52,17 @@ export class CoreCoursesHelperProvider { | ||||
|         course: CoreEnrolledCourseDataWithExtraInfo, | ||||
|         courseByField: CoreCourseSearchedData, | ||||
|         addCategoryName: boolean = false, | ||||
|         colors?: (string | undefined)[], | ||||
|     ): void { | ||||
|         if (courseByField) { | ||||
|             course.displayname = courseByField.displayname; | ||||
|             course.categoryname = addCategoryName ? courseByField.categoryname : undefined; | ||||
| 
 | ||||
|             if (courseByField.overviewfiles && courseByField.overviewfiles[0]) { | ||||
|                 course.courseImage = courseByField.overviewfiles[0].fileurl; | ||||
|             } else { | ||||
|                 delete course.courseImage; | ||||
|             } | ||||
|             course.overviewfiles = course.overviewfiles || courseByField.overviewfiles; | ||||
|         } else { | ||||
|             delete course.displayname; | ||||
|             delete course.courseImage; | ||||
|         } | ||||
| 
 | ||||
|         this.loadCourseColorAndImage(course, colors); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -84,21 +82,14 @@ export class CoreCoursesHelperProvider { | ||||
|         let coursesInfo = {}; | ||||
|         let courseInfoAvailable = false; | ||||
| 
 | ||||
|         const site = CoreSites.instance.getCurrentSite(); | ||||
|         const promises: Promise<void>[] = []; | ||||
|         const colors: (string | undefined)[] = []; | ||||
|         let colors: (string | undefined)[] = []; | ||||
| 
 | ||||
|         if (site?.isVersionGreaterEqualThan('3.8')) { | ||||
|             promises.push(site.getConfig().then((configs) => { | ||||
|                 for (let x = 0; x < 10; x++) { | ||||
|                     colors[x] = configs['core_admin_coursecolor' + (x + 1)] || undefined; | ||||
|                 } | ||||
|         promises.push(this.loadCourseSiteColors().then((loadedColors) => { | ||||
|             colors = loadedColors; | ||||
| 
 | ||||
|             return; | ||||
|             }).catch(() => { | ||||
|                 // Ignore errors.
 | ||||
|         })); | ||||
|         } | ||||
| 
 | ||||
|         if (CoreCourses.instance.isGetCoursesByFieldAvailable() && (loadCategoryNames || | ||||
|                 (typeof courses[0].overviewfiles == 'undefined' && typeof courses[0].displayname == 'undefined'))) { | ||||
| @ -117,13 +108,50 @@ export class CoreCoursesHelperProvider { | ||||
|         await Promise.all(promises); | ||||
| 
 | ||||
|         courses.forEach((course) => { | ||||
|             this.loadCourseExtraInfo(course, courseInfoAvailable ? coursesInfo[course.id] : course, loadCategoryNames); | ||||
|             this.loadCourseExtraInfo(course, courseInfoAvailable ? coursesInfo[course.id] : course, loadCategoryNames, colors); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|             if (!course.courseImage) { | ||||
|     /** | ||||
|      * Load course colors from site config. | ||||
|      * | ||||
|      * @return course colors RGB. | ||||
|      */ | ||||
|     protected async loadCourseSiteColors(): Promise<(string | undefined)[]> { | ||||
|         const site = CoreSites.instance.getCurrentSite(); | ||||
|         const colors: (string | undefined)[] = []; | ||||
| 
 | ||||
|         if (site?.isVersionGreaterEqualThan('3.8')) { | ||||
|             try { | ||||
|                 const configs = await site.getConfig(); | ||||
|                 for (let x = 0; x < 10; x++) { | ||||
|                     colors[x] = configs['core_admin_coursecolor' + (x + 1)] || undefined; | ||||
|                 } | ||||
|             } catch { | ||||
|                 // Ignore errors.
 | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return colors; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Loads the color of the course or the thumb image. | ||||
|      * | ||||
|      * @param course Course data. | ||||
|      * @param colors Colors loaded. | ||||
|      */ | ||||
|     async loadCourseColorAndImage(course: CoreCourseWithImageAndColor, colors?: (string | undefined)[]): Promise<void> { | ||||
|         if (!colors) { | ||||
|             colors = await this.loadCourseSiteColors(); | ||||
|         } | ||||
| 
 | ||||
|         if (course.overviewfiles && course.overviewfiles[0]) { | ||||
|             course.courseImage = course.overviewfiles[0].fileurl; | ||||
|         } else { | ||||
|             course.colorNumber = course.id % 10; | ||||
|             course.color = colors.length ? colors[course.colorNumber] : undefined; | ||||
|         } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -157,12 +185,20 @@ export class CoreCoursesHelperProvider { | ||||
| export class CoreCoursesHelper extends makeSingleton(CoreCoursesHelperProvider) { } | ||||
| 
 | ||||
| /** | ||||
|  * Enrolled course data with extra rendering info. | ||||
|  * Course with colors info and course image. | ||||
|  */ | ||||
| export type CoreEnrolledCourseDataWithExtraInfo = CoreEnrolledCourseData & { | ||||
| export type CoreCourseWithImageAndColor = { | ||||
|     id: number; // Course id.
 | ||||
|     overviewfiles?: CoreWSExternalFile[]; | ||||
|     colorNumber?: number; // Color index number.
 | ||||
|     color?: string; // Color RGB.
 | ||||
|     courseImage?: string; // Course thumbnail.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Enrolled course data with extra rendering info. | ||||
|  */ | ||||
| export type CoreEnrolledCourseDataWithExtraInfo = CoreCourseWithImageAndColor & CoreEnrolledCourseData & { | ||||
|     categoryname?: string; // Category name,
 | ||||
| }; | ||||
| 
 | ||||
|  | ||||
| @ -154,16 +154,16 @@ | ||||
|     --core-login-background: var(--custom-login-background, var(--white)); | ||||
|     --core-login-text-color: var(--custom-login-text-color, var(--black)); | ||||
| 
 | ||||
|     --core-course-image-background-0: var(--custom-course-image-background-0, #81ecec); | ||||
|     --core-course-image-background-1: var(--custom-course-image-background-1, #74b9ff); | ||||
|     --core-course-image-background-2: var(--custom-course-image-background-2, #a29bfe); | ||||
|     --core-course-image-background-3: var(--custom-course-image-background-3, #dfe6e9); | ||||
|     --core-course-image-background-4: var(--custom-course-image-background-4, #00b894); | ||||
|     --core-course-image-background-5: var(--custom-course-image-background-5, #0984e3); | ||||
|     --core-course-image-background-6: var(--custom-course-image-background-6, #b2bec3); | ||||
|     --core-course-image-background-7: var(--custom-course-image-background-7, #fdcb6e); | ||||
|     --core-course-image-background-8: var(--custom-course-image-background-9, #fd79a8); | ||||
|     --core-course-image-background-9: var(--custom-course-image-background-90, #6c5ce7); | ||||
|     --core-course-color-0: var(--custom-course-color-0, #81ecec); | ||||
|     --core-course-color-1: var(--custom-course-color-1, #74b9ff); | ||||
|     --core-course-color-2: var(--custom-course-color-2, #a29bfe); | ||||
|     --core-course-color-3: var(--custom-course-color-3, #dfe6e9); | ||||
|     --core-course-color-4: var(--custom-course-color-4, #00b894); | ||||
|     --core-course-color-5: var(--custom-course-color-5, #0984e3); | ||||
|     --core-course-color-6: var(--custom-course-color-6, #b2bec3); | ||||
|     --core-course-color-7: var(--custom-course-color-7, #fdcb6e); | ||||
|     --core-course-color-8: var(--custom-course-color-9, #fd79a8); | ||||
|     --core-course-color-9: var(--custom-course-color-90, #6c5ce7); | ||||
|     --core-star-color: var(--custom-star-color, var(--core-color)); | ||||
| } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user