MOBILE-2302 courses: Implement course progress component
parent
296932b76a
commit
e778448824
|
@ -21,6 +21,7 @@ import { CoreMarkRequiredComponent } from './mark-required/mark-required';
|
||||||
import { CoreInputErrorsComponent } from './input-errors/input-errors';
|
import { CoreInputErrorsComponent } from './input-errors/input-errors';
|
||||||
import { CoreShowPasswordComponent } from './show-password/show-password';
|
import { CoreShowPasswordComponent } from './show-password/show-password';
|
||||||
import { CoreIframeComponent } from './iframe/iframe';
|
import { CoreIframeComponent } from './iframe/iframe';
|
||||||
|
import { CoreProgressBarComponent } from './progress-bar/progress-bar';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -28,7 +29,8 @@ import { CoreIframeComponent } from './iframe/iframe';
|
||||||
CoreMarkRequiredComponent,
|
CoreMarkRequiredComponent,
|
||||||
CoreInputErrorsComponent,
|
CoreInputErrorsComponent,
|
||||||
CoreShowPasswordComponent,
|
CoreShowPasswordComponent,
|
||||||
CoreIframeComponent
|
CoreIframeComponent,
|
||||||
|
CoreProgressBarComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
IonicModule,
|
IonicModule,
|
||||||
|
@ -40,7 +42,8 @@ import { CoreIframeComponent } from './iframe/iframe';
|
||||||
CoreMarkRequiredComponent,
|
CoreMarkRequiredComponent,
|
||||||
CoreInputErrorsComponent,
|
CoreInputErrorsComponent,
|
||||||
CoreShowPasswordComponent,
|
CoreShowPasswordComponent,
|
||||||
CoreIframeComponent
|
CoreIframeComponent,
|
||||||
|
CoreProgressBarComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class CoreComponentsModule {}
|
export class CoreComponentsModule {}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<div *ngIf="progress >= 0">
|
||||||
|
<progress max="100" [value]="progress">
|
||||||
|
<div class="progress-bar-fallback" role="progressbar" aria-valuemin="0" aria-valuemax="100" [attr.aria-valuenow]="progress">
|
||||||
|
<span [style.width]="width"></span>
|
||||||
|
</div>
|
||||||
|
</progress>
|
||||||
|
<span class="mm-progress-text">{{ 'core.percentagenumber' | translate: {$a: text} }}</span>
|
||||||
|
</div>
|
|
@ -0,0 +1,53 @@
|
||||||
|
$mm-progress-bar-height: 5px !default;
|
||||||
|
|
||||||
|
core-progress-bar {
|
||||||
|
padding-right: 55px;
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
// @extend .clearfix;
|
||||||
|
|
||||||
|
.mm-progress-text {
|
||||||
|
margin-left: 10px;
|
||||||
|
line-height: 35px;
|
||||||
|
color: $gray-darker;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
color: #626262;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
height: $mm-progress-bar-height;
|
||||||
|
margin: 15px 0;
|
||||||
|
padding: 0;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.progress-bar-fallback,
|
||||||
|
&[value]::-webkit-progress-bar {
|
||||||
|
background-color: $gray-light;
|
||||||
|
border-radius: 2px;
|
||||||
|
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar-fallback span,
|
||||||
|
&[value]::-webkit-progress-value {
|
||||||
|
background-color: $mm-color-light;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar-fallback {
|
||||||
|
width: 100%;
|
||||||
|
height: $mm-progress-bar-height;
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
span {
|
||||||
|
height: $mm-progress-bar-height;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// 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, Input, OnChanges, SimpleChange, ChangeDetectionStrategy } from '@angular/core';
|
||||||
|
import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to show a progress bar and its value.
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
* <core-progress-bar [progress]="percentage"></core-progress-bar>
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'core-progress-bar',
|
||||||
|
templateUrl: 'progress-bar.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class CoreProgressBarComponent implements OnChanges {
|
||||||
|
@Input() progress: number|string; // Percentage from 0 to 100.
|
||||||
|
@Input() text?: string; // Percentage in text to be shown at the right. If not defined, progress will be used.
|
||||||
|
width: SafeStyle;
|
||||||
|
protected textSupplied = false;
|
||||||
|
|
||||||
|
constructor(private sanitizer: DomSanitizer) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect changes on input properties.
|
||||||
|
*/
|
||||||
|
ngOnChanges(changes: {[name: string]: SimpleChange}) {
|
||||||
|
if (changes.text && typeof changes.text.currentValue != 'undefined') {
|
||||||
|
// User provided a custom text, don't use default.
|
||||||
|
this.textSupplied = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changes.progress) {
|
||||||
|
// Progress has changed.
|
||||||
|
this.width = this.sanitizer.bypassSecurityTrustStyle(this.progress + '%');
|
||||||
|
if (typeof this.progress == 'string') {
|
||||||
|
this.progress = parseInt(this.progress, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.progress < 0 || isNaN(this.progress)) {
|
||||||
|
this.progress = -1;
|
||||||
|
} else if (!this.textSupplied) {
|
||||||
|
this.text = String(this.progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
<ion-card>
|
||||||
|
<a ion-item text-wrap class="item-course" [class.item-progress]="roundProgress" (click)="openCourse(course)" [title]="course.fullname">
|
||||||
|
<div class="progress-chart-container" *ngIf="roundProgress">
|
||||||
|
<div *ngIf="course.progress !== false" class="progress-doughnut">
|
||||||
|
<div class="progress-text has-percent">{{course.progress}}%</div>
|
||||||
|
<div class="progress-indicator">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g>
|
||||||
|
<title>{{course.progress}}%</title>
|
||||||
|
<circle class="circle percent-{{course.progress}}" r="27.5" cx="35" cy="35"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="course.progress === false" class="no-progress">
|
||||||
|
<ion-icon name="ionic"></ion-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h2><core-format-text [text]="course.fullname"></core-format-text></h2>
|
||||||
|
<core-progress-bar *ngIf="!roundProgress && course.progress !== false" [progress]="course.progress"></core-progress-bar>
|
||||||
|
<!-- Course options. -->
|
||||||
|
<!-- <i *ngIf="actionsLoaded" class="icon ion-android-more-vertical mm-animate-show-hide" ng-click="showCourseActions($event)"></i>
|
||||||
|
<ion-spinner *ngIf="!actionsLoaded" class="mm-animate-show-hide"></ion-spinner> -->
|
||||||
|
<!-- Download course spinner. -->
|
||||||
|
<!-- <ion-spinner *ngIf="prefetchCourseIcon == 'spinner'" class="mm-course-download-spinner"></ion-spinner> -->
|
||||||
|
</a>
|
||||||
|
<ion-item text-wrap *ngIf="showSummary && course.summary">
|
||||||
|
<p>
|
||||||
|
<summary>
|
||||||
|
<core-format-text [text]="course.summary" maxHeight="60"></core-format-text>
|
||||||
|
</summary>
|
||||||
|
</p>
|
||||||
|
</ion-item>
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</ion-card>
|
|
@ -0,0 +1,2 @@
|
||||||
|
core-courses-course-progress {
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
// (C) Copyright 2015 Martin Dougiamas
|
||||||
|
//
|
||||||
|
// 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, Input, OnInit } from '@angular/core';
|
||||||
|
import { NavController } from 'ionic-angular';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { CoreUtilsProvider } from '../../../../providers/utils/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component is meant to display a course for a list of courses with progress.
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
*
|
||||||
|
* <core-courses-course-progress *ngFor="let course of filteredCourses" [course]="course" showSummary="true">
|
||||||
|
* </core-courses-course-progress>
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'core-courses-course-progress',
|
||||||
|
templateUrl: 'course-progress.html'
|
||||||
|
})
|
||||||
|
export class CoreCoursesCourseProgressComponent implements OnInit {
|
||||||
|
@Input() course: any; // The course to render.
|
||||||
|
@Input() roundProgress?: boolean|string; // Whether to show the progress.
|
||||||
|
@Input() showSummary?: boolean|string; // Whether to show the summary.
|
||||||
|
|
||||||
|
actionsLoaded = true;
|
||||||
|
prefetchCourseIcon: string;
|
||||||
|
|
||||||
|
protected obsStatus;
|
||||||
|
protected downloadText;
|
||||||
|
protected downloadingText;
|
||||||
|
protected downloadButton = {
|
||||||
|
isDownload: true,
|
||||||
|
className: 'mm-download-course',
|
||||||
|
priority: 1000
|
||||||
|
};
|
||||||
|
protected buttons;
|
||||||
|
|
||||||
|
constructor(private navCtrl: NavController, private translate: TranslateService, private utils: CoreUtilsProvider) {
|
||||||
|
this.downloadText = this.translate.instant('core.course.downloadcourse');
|
||||||
|
this.downloadingText = this.translate.instant('core.downloading');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
// @todo: Handle course prefetch.
|
||||||
|
// @todo: Handle course handlers (participants, etc.).
|
||||||
|
this.roundProgress = this.utils.isTrueOrOne(this.roundProgress);
|
||||||
|
this.showSummary = this.utils.isTrueOrOne(this.showSummary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a course.
|
||||||
|
*/
|
||||||
|
openCourse(course) {
|
||||||
|
this.navCtrl.push('CoreCourseSectionPage', {course: course});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -14,14 +14,19 @@
|
||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { CoreCoursesProvider } from './providers/courses';
|
import { CoreCoursesProvider } from './providers/courses';
|
||||||
|
import { CoreCoursesCourseProgressComponent } from './components/course-progress/course-progress';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
CoreCoursesCourseProgressComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
CoreCoursesProvider
|
CoreCoursesProvider
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
CoreCoursesCourseProgressComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class CoreCoursesModule {}
|
export class CoreCoursesModule {}
|
||||||
|
|
|
@ -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 { Directive, ElementRef, Input, OnInit, Output, EventEmitter } from '@angular/core';
|
import { Directive, ElementRef, Input, Output, EventEmitter, OnChanges, SimpleChange } from '@angular/core';
|
||||||
import { Platform } from 'ionic-angular';
|
import { Platform } from 'ionic-angular';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { CoreAppProvider } from '../providers/app';
|
import { CoreAppProvider } from '../providers/app';
|
||||||
|
@ -38,7 +38,7 @@ import { CoreExternalContentDirective } from '../directives/external-content';
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: 'core-format-text'
|
selector: 'core-format-text'
|
||||||
})
|
})
|
||||||
export class CoreFormatTextDirective implements OnInit {
|
export class CoreFormatTextDirective implements OnChanges {
|
||||||
@Input() text: string; // The text to format.
|
@Input() text: string; // The text to format.
|
||||||
@Input() siteId?: string; // Site ID to use.
|
@Input() siteId?: string; // Site ID to use.
|
||||||
@Input() component?: string; // Component for CoreExternalContentDirective.
|
@Input() component?: string; // Component for CoreExternalContentDirective.
|
||||||
|
@ -68,11 +68,13 @@ export class CoreFormatTextDirective implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function executed when the directive is initialized.
|
* Detect changes on input properties.
|
||||||
*/
|
*/
|
||||||
ngOnInit() : void {
|
ngOnChanges(changes: {[name: string]: SimpleChange}) {
|
||||||
|
if (changes.text) {
|
||||||
this.formatAndRenderContents();
|
this.formatAndRenderContents();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply CoreExternalContentDirective to a certain element.
|
* Apply CoreExternalContentDirective to a certain element.
|
||||||
|
|
Loading…
Reference in New Issue