MOBILE-1633 core: Rating components and providers
parent
4c602c62bf
commit
beaab9d2f6
|
@ -1590,6 +1590,15 @@
|
||||||
"core.question.questionno": "question",
|
"core.question.questionno": "question",
|
||||||
"core.question.requiresgrading": "question",
|
"core.question.requiresgrading": "question",
|
||||||
"core.quotausage": "moodle",
|
"core.quotausage": "moodle",
|
||||||
|
"core.rating.aggregateavg": "moodle",
|
||||||
|
"core.rating.aggregatecount": "moodle",
|
||||||
|
"core.rating.aggregatemax": "moodle",
|
||||||
|
"core.rating.aggregatemin": "moodle",
|
||||||
|
"core.rating.aggregatesum": "moodle",
|
||||||
|
"core.rating.norating": "local_moodlemobileapp",
|
||||||
|
"core.rating.noratings": "moodle",
|
||||||
|
"core.rating.rating": "moodle",
|
||||||
|
"core.rating.ratings": "moodle",
|
||||||
"core.redirectingtosite": "local_moodlemobileapp",
|
"core.redirectingtosite": "local_moodlemobileapp",
|
||||||
"core.refresh": "moodle",
|
"core.refresh": "moodle",
|
||||||
"core.remove": "moodle",
|
"core.remove": "moodle",
|
||||||
|
|
|
@ -78,6 +78,7 @@ import { CoreCompileModule } from '@core/compile/compile.module';
|
||||||
import { CoreQuestionModule } from '@core/question/question.module';
|
import { CoreQuestionModule } from '@core/question/question.module';
|
||||||
import { CoreCommentsModule } from '@core/comments/comments.module';
|
import { CoreCommentsModule } from '@core/comments/comments.module';
|
||||||
import { CoreBlockModule } from '@core/block/block.module';
|
import { CoreBlockModule } from '@core/block/block.module';
|
||||||
|
import { CoreRatingModule } from '@core/rating/rating.module';
|
||||||
|
|
||||||
// Addon modules.
|
// Addon modules.
|
||||||
import { AddonBadgesModule } from '@addon/badges/badges.module';
|
import { AddonBadgesModule } from '@addon/badges/badges.module';
|
||||||
|
@ -198,6 +199,7 @@ export const CORE_PROVIDERS: any[] = [
|
||||||
CoreQuestionModule,
|
CoreQuestionModule,
|
||||||
CoreCommentsModule,
|
CoreCommentsModule,
|
||||||
CoreBlockModule,
|
CoreBlockModule,
|
||||||
|
CoreRatingModule,
|
||||||
AddonBadgesModule,
|
AddonBadgesModule,
|
||||||
AddonBlogModule,
|
AddonBlogModule,
|
||||||
AddonCalendarModule,
|
AddonCalendarModule,
|
||||||
|
|
|
@ -1590,6 +1590,15 @@
|
||||||
"core.question.questionno": "Question {{$a}}",
|
"core.question.questionno": "Question {{$a}}",
|
||||||
"core.question.requiresgrading": "Requires grading",
|
"core.question.requiresgrading": "Requires grading",
|
||||||
"core.quotausage": "You have currently used {{$a.used}} of your {{$a.total}} limit.",
|
"core.quotausage": "You have currently used {{$a.used}} of your {{$a.total}} limit.",
|
||||||
|
"core.rating.aggregateavg": "Average of ratings",
|
||||||
|
"core.rating.aggregatecount": "Count of ratings",
|
||||||
|
"core.rating.aggregatemax": "Maximum rating",
|
||||||
|
"core.rating.aggregatemin": "Minimum rating",
|
||||||
|
"core.rating.aggregatesum": "Sum of ratings",
|
||||||
|
"core.rating.norating": "No rating",
|
||||||
|
"core.rating.noratings": "No ratings submitted",
|
||||||
|
"core.rating.rating": "Rating",
|
||||||
|
"core.rating.ratings": "Ratings",
|
||||||
"core.redirectingtosite": "You will be redirected to the site.",
|
"core.redirectingtosite": "You will be redirected to the site.",
|
||||||
"core.refresh": "Refresh",
|
"core.refresh": "Refresh",
|
||||||
"core.remove": "Remove",
|
"core.remove": "Remove",
|
||||||
|
|
|
@ -0,0 +1,112 @@
|
||||||
|
// (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 } from '@angular/core';
|
||||||
|
import { ModalController } from 'ionic-angular';
|
||||||
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
|
import { CoreRatingProvider, CoreRatingInfo, CoreRatingInfoItem } from '@core/rating/providers/rating';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that displays the aggregation of a rating item.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'core-rating-aggregate',
|
||||||
|
templateUrl: 'core-rating-aggregate.html'
|
||||||
|
})
|
||||||
|
export class CoreRatingAggregateComponent implements OnChanges {
|
||||||
|
@Input() ratingInfo: CoreRatingInfo;
|
||||||
|
@Input() contextLevel: string;
|
||||||
|
@Input() instanceId: number;
|
||||||
|
@Input() itemId: number;
|
||||||
|
@Input() aggregateMethod: number;
|
||||||
|
@Input() scaleId: number;
|
||||||
|
@Input() courseId?: number;
|
||||||
|
|
||||||
|
protected labelKey: string;
|
||||||
|
protected showCount: boolean;
|
||||||
|
protected item: CoreRatingInfoItem;
|
||||||
|
protected aggregateObserver;
|
||||||
|
|
||||||
|
constructor(private eventsProvider: CoreEventsProvider, private modalCtrl: ModalController) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect changes on input properties.
|
||||||
|
*/
|
||||||
|
ngOnChanges(changes: {[name: string]: SimpleChange}): void {
|
||||||
|
this.aggregateObserver && this.aggregateObserver.off();
|
||||||
|
|
||||||
|
this.item = (this.ratingInfo.ratings || []).find((rating) => rating.itemid == this.itemId);
|
||||||
|
if (!this.item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.aggregateMethod == CoreRatingProvider.AGGREGATE_AVERAGE) {
|
||||||
|
this.labelKey = 'core.rating.aggregateavg';
|
||||||
|
} else if (this.aggregateMethod == CoreRatingProvider.AGGREGATE_COUNT) {
|
||||||
|
this.labelKey = 'core.rating.aggregatecount';
|
||||||
|
} else if (this.aggregateMethod == CoreRatingProvider.AGGREGATE_MAXIMUM) {
|
||||||
|
this.labelKey = 'core.rating.aggregatemax';
|
||||||
|
} else if (this.aggregateMethod == CoreRatingProvider.AGGREGATE_MINIMUM) {
|
||||||
|
this.labelKey = 'core.rating.aggregatemin';
|
||||||
|
} else if (this.aggregateMethod == CoreRatingProvider.AGGREGATE_SUM) {
|
||||||
|
this.labelKey = 'core.rating.aggregatesum';
|
||||||
|
} else {
|
||||||
|
this.labelKey = '';
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showCount = (this.aggregateMethod != CoreRatingProvider.AGGREGATE_COUNT);
|
||||||
|
|
||||||
|
// Update aggrgate when the user adds or edits a rating.
|
||||||
|
this.aggregateObserver = this.eventsProvider.on(CoreRatingProvider.AGGREGATE_CHANGED_EVENT, (data) => {
|
||||||
|
if (data.contextLevel == this.contextLevel &&
|
||||||
|
data.instanceId == this.instanceId &&
|
||||||
|
data.component == this.ratingInfo.component &&
|
||||||
|
data.ratingArea == this.ratingInfo.ratingarea &&
|
||||||
|
data.itemId == this.itemId) {
|
||||||
|
this.item.aggregatestr = data.aggregate;
|
||||||
|
this.item.count = data.count;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the individual ratings page.
|
||||||
|
*/
|
||||||
|
openRatings(): void {
|
||||||
|
if (!this.ratingInfo.canviewall || !this.item.count) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
contextLevel: this.contextLevel,
|
||||||
|
instanceId: this.instanceId,
|
||||||
|
ratingComponent: this.ratingInfo.component,
|
||||||
|
ratingArea: this.ratingInfo.ratingarea,
|
||||||
|
itemId: this.itemId,
|
||||||
|
scaleId: this.scaleId,
|
||||||
|
courseId: this.courseId
|
||||||
|
};
|
||||||
|
const modal = this.modalCtrl.create('CoreRatingRatingsPage', params);
|
||||||
|
modal.present();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being destroyed.
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.aggregateObserver && this.aggregateObserver.off();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
<a *ngIf="item && item.canviewaggregate && labelKey" ion-item text-wrap [attr.detail-none]="ratingInfo.canviewall && item.count ? null : true" (click)="openRatings()">
|
||||||
|
{{ labelKey | translate }}{{ 'core.labelsep' | translate }} {{ item.aggregatestr || '-' }}
|
||||||
|
<span *ngIf="showCount && item.count > 0">({{ item.count }})</span>
|
||||||
|
</a>
|
|
@ -0,0 +1,39 @@
|
||||||
|
// (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 { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { IonicModule } from 'ionic-angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CoreRatingAggregateComponent } from './aggregate/aggregate';
|
||||||
|
import { CoreRatingRateComponent } from './rate/rate';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
CoreRatingAggregateComponent,
|
||||||
|
CoreRatingRateComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
IonicModule,
|
||||||
|
TranslateModule.forChild()
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
CoreRatingAggregateComponent,
|
||||||
|
CoreRatingRateComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class CoreRatingComponentsModule {}
|
|
@ -0,0 +1,6 @@
|
||||||
|
<ion-item text-wrap *ngIf="item && (item.canrate || item.rating != null)">
|
||||||
|
<ion-label>{{ 'core.rating.rating' | translate }}</ion-label>
|
||||||
|
<ion-select text-start [(ngModel)]="rating" (ngModelChange)="userRatingChanged()" interface="action-sheet" [disabled]="!item.canrate">
|
||||||
|
<ion-option *ngFor="let scaleItem of scale.items" [value]="scaleItem.value">{{ scaleItem.name }}</ion-option>
|
||||||
|
</ion-select>
|
||||||
|
</ion-item>
|
|
@ -0,0 +1,106 @@
|
||||||
|
// (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 } from '@angular/core';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { CoreRatingProvider, CoreRatingInfo, CoreRatingInfoItem, CoreRatingScale } from '@core/rating/providers/rating';
|
||||||
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
|
import { CoreRatingOfflineProvider } from '@core/rating/providers/offline';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that displays the user rating select.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'core-rating-rate',
|
||||||
|
templateUrl: 'core-rating-rate.html'
|
||||||
|
})
|
||||||
|
export class CoreRatingRateComponent implements OnChanges {
|
||||||
|
@Input() ratingInfo: CoreRatingInfo;
|
||||||
|
@Input() contextLevel: string; // Context level: course, module, user, etc.
|
||||||
|
@Input() instanceId: number; // Context instance id.
|
||||||
|
@Input() itemId: number; // Item id. Example: forum post id.
|
||||||
|
@Input() itemSetId: number; // Item set id. Example: forum discussion id.
|
||||||
|
@Input() courseId: number;
|
||||||
|
@Input() aggregateMethod: number;
|
||||||
|
@Input() scaleId: number;
|
||||||
|
@Input() userId: number;
|
||||||
|
|
||||||
|
item: CoreRatingInfoItem;
|
||||||
|
scale: CoreRatingScale;
|
||||||
|
rating: number;
|
||||||
|
|
||||||
|
constructor(private domUtils: CoreDomUtilsProvider, private translate: TranslateService,
|
||||||
|
private ratingProvider: CoreRatingProvider, private ratingOffline: CoreRatingOfflineProvider) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect changes on input properties.
|
||||||
|
*/
|
||||||
|
ngOnChanges(changes: {[name: string]: SimpleChange}): void {
|
||||||
|
this.item = (this.ratingInfo.ratings || []).find((rating) => rating.itemid == this.itemId);
|
||||||
|
this.scale = (this.ratingInfo.scales || []).find((scale) => scale.id == this.scaleId);
|
||||||
|
|
||||||
|
if (!this.item || !this.scale || !this.ratingProvider.isAddRatingWSAvailable()) {
|
||||||
|
this.item = null;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set numeric scale items.
|
||||||
|
if (!this.scale.items) {
|
||||||
|
this.scale.items = [];
|
||||||
|
if (this.scale.isnumeric) {
|
||||||
|
for (let n = 0; n <= this.scale.max; n++) {
|
||||||
|
this.scale.items.push({name: String(n), value: n});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add "No rating" item to the scale.
|
||||||
|
if (!this.scale.items[0] || this.scale.items[0].value != CoreRatingProvider.UNSET_RATING) {
|
||||||
|
this.scale.items.unshift({
|
||||||
|
name: this.translate.instant('core.rating.norating'),
|
||||||
|
value: CoreRatingProvider.UNSET_RATING
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ratingOffline.getRating(this.contextLevel, this.instanceId, this.ratingInfo.component, this.ratingInfo.ratingarea,
|
||||||
|
this.itemId).then((rating) => {
|
||||||
|
this.rating = rating.rating;
|
||||||
|
}).catch(() => {
|
||||||
|
if (this.item && this.item.rating != null) {
|
||||||
|
this.rating = this.item.rating;
|
||||||
|
} else {
|
||||||
|
this.rating = CoreRatingProvider.UNSET_RATING;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send or save the user rating when changed.
|
||||||
|
*/
|
||||||
|
protected userRatingChanged(): void {
|
||||||
|
const modal = this.domUtils.showModalLoading('core.sending', true);
|
||||||
|
this.ratingProvider.addRating(this.ratingInfo.component, this.ratingInfo.ratingarea, this.contextLevel, this.instanceId,
|
||||||
|
this.itemId, this.itemSetId, this.courseId, this.scaleId, this.rating, this.userId, this.aggregateMethod)
|
||||||
|
.then((response) => {
|
||||||
|
if (response == null) {
|
||||||
|
this.domUtils.showToast('core.datastoredoffline', true, 3000);
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
this.domUtils.showErrorModal(error);
|
||||||
|
}).finally(() => {
|
||||||
|
modal.dismiss();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"aggregateavg": "Average of ratings",
|
||||||
|
"aggregatecount": "Count of ratings",
|
||||||
|
"aggregatemax": "Maximum rating",
|
||||||
|
"aggregatemin": "Minimum rating",
|
||||||
|
"aggregatesum": "Sum of ratings",
|
||||||
|
"norating": "No rating",
|
||||||
|
"noratings": "No ratings submitted",
|
||||||
|
"rating": "Rating",
|
||||||
|
"ratings": "Ratings"
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-navbar core-back-button>
|
||||||
|
<ion-title>{{ 'core.rating.ratings' | translate }}</ion-title>
|
||||||
|
<ion-buttons end>
|
||||||
|
<button ion-button icon-only (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
|
||||||
|
<ion-icon name="close"></ion-icon>
|
||||||
|
</button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-navbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<ion-refresher [enabled]="loaded" (ionRefresh)="refreshRatings($event)">
|
||||||
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
|
</ion-refresher>
|
||||||
|
<core-loading [hideUntil]="loaded">
|
||||||
|
<ion-list *ngIf="ratings.length > 0">
|
||||||
|
<ion-item text-wrap *ngFor="let rating of ratings">
|
||||||
|
<ion-avatar core-user-avatar [user]="rating" [courseId]="courseId" item-start></ion-avatar>
|
||||||
|
<ion-note item-end padding-left *ngIf="rating.timemodified">
|
||||||
|
{{ rating.timemodified | coreDateDayOrTime }}
|
||||||
|
</ion-note>
|
||||||
|
<h2><core-format-text [text]="rating.userfullname"></core-format-text></h2>
|
||||||
|
<p>{{ rating.rating }}</p>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
<core-empty-box *ngIf="ratings.length == 0" icon="stats" [message]="'core.rating.noratings' | translate"></core-empty-box>
|
||||||
|
</core-loading>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,35 @@
|
||||||
|
// (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 { NgModule } from '@angular/core';
|
||||||
|
import { IonicPageModule } from 'ionic-angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CoreRatingRatingsPage } from './ratings';
|
||||||
|
import { CoreComponentsModule } from '@components/components.module';
|
||||||
|
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
|
import { CorePipesModule } from '@pipes/pipes.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
CoreRatingRatingsPage
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CoreComponentsModule,
|
||||||
|
CoreDirectivesModule,
|
||||||
|
CorePipesModule,
|
||||||
|
IonicPageModule.forChild(CoreRatingRatingsPage),
|
||||||
|
TranslateModule.forChild()
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class CoreRatingRatingsPageModule {}
|
|
@ -0,0 +1,95 @@
|
||||||
|
// (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 } from '@angular/core';
|
||||||
|
import { IonicPage, NavParams, ViewController } from 'ionic-angular';
|
||||||
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
|
import { CoreRatingProvider, CoreRatingItemRating } from '@core/rating/providers/rating';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page that displays individual ratings
|
||||||
|
*/
|
||||||
|
@IonicPage({ segment: 'core-rating-ratings' })
|
||||||
|
@Component({
|
||||||
|
selector: 'page-core-rating-ratings',
|
||||||
|
templateUrl: 'ratings.html',
|
||||||
|
})
|
||||||
|
export class CoreRatingRatingsPage {
|
||||||
|
contextLevel: string;
|
||||||
|
instanceId: number;
|
||||||
|
component: string;
|
||||||
|
ratingArea: string;
|
||||||
|
aggregateMethod: number;
|
||||||
|
itemId: number;
|
||||||
|
scaleId: number;
|
||||||
|
courseId: number;
|
||||||
|
loaded = false;
|
||||||
|
ratings: CoreRatingItemRating[] = [];
|
||||||
|
|
||||||
|
constructor(navParams: NavParams, private viewCtrl: ViewController, private domUtils: CoreDomUtilsProvider,
|
||||||
|
private ratingProvider: CoreRatingProvider) {
|
||||||
|
this.contextLevel = navParams.get('contextLevel');
|
||||||
|
this.instanceId = navParams.get('instanceId');
|
||||||
|
this.component = navParams.get('ratingComponent');
|
||||||
|
this.ratingArea = navParams.get('ratingArea');
|
||||||
|
this.aggregateMethod = navParams.get('aggregateMethod');
|
||||||
|
this.itemId = navParams.get('itemId');
|
||||||
|
this.scaleId = navParams.get('scaleId');
|
||||||
|
this.courseId = navParams.get('courseId');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View loaded.
|
||||||
|
*/
|
||||||
|
ionViewDidLoad(): void {
|
||||||
|
this.fetchData().finally(() => {
|
||||||
|
this.loaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch all the data required for the view.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Resolved when done.
|
||||||
|
*/
|
||||||
|
fetchData(): Promise<any> {
|
||||||
|
return this.ratingProvider.getItemRatings(this.contextLevel, this.instanceId, this.component, this.ratingArea, this.itemId,
|
||||||
|
this.scaleId, undefined, this.courseId).then((ratings) => {
|
||||||
|
this.ratings = ratings;
|
||||||
|
}).catch((error) => {
|
||||||
|
this.domUtils.showErrorModal(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh data.
|
||||||
|
*
|
||||||
|
* @param {any} refresher Refresher.
|
||||||
|
*/
|
||||||
|
refreshRatings(refresher: any): void {
|
||||||
|
this.ratingProvider.invalidateRatingItems(this.contextLevel, this.instanceId, this.component, this.ratingArea, this.itemId,
|
||||||
|
this.scaleId).finally(() => {
|
||||||
|
return this.fetchData().finally(() => {
|
||||||
|
refresher.complete();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close modal.
|
||||||
|
*/
|
||||||
|
closeModal(): void {
|
||||||
|
this.viewCtrl.dismiss();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,305 @@
|
||||||
|
// (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 { Injectable } from '@angular/core';
|
||||||
|
import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites';
|
||||||
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure of offline ratings.
|
||||||
|
*/
|
||||||
|
export interface CoreRatingOfflineRating {
|
||||||
|
component: string;
|
||||||
|
ratingarea: string;
|
||||||
|
contextlevel: string;
|
||||||
|
instanceid: number;
|
||||||
|
itemid: number;
|
||||||
|
itemsetid: number;
|
||||||
|
courseid: number;
|
||||||
|
scaleid: number;
|
||||||
|
rating: number;
|
||||||
|
rateduserid: number;
|
||||||
|
aggregation: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure of item sets.
|
||||||
|
*/
|
||||||
|
export interface CoreRatingItemSet {
|
||||||
|
component: string;
|
||||||
|
ratingArea: string;
|
||||||
|
contextLevel: string;
|
||||||
|
instanceId: number;
|
||||||
|
itemSetId: number;
|
||||||
|
courseId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service to handle offline data for rating.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CoreRatingOfflineProvider {
|
||||||
|
|
||||||
|
// Variables for database.
|
||||||
|
static RATINGS_TABLE = 'rating_ratings';
|
||||||
|
protected siteSchema: CoreSiteSchema = {
|
||||||
|
name: 'CoreCourseOfflineProvider',
|
||||||
|
version: 1,
|
||||||
|
tables: [
|
||||||
|
{
|
||||||
|
name: CoreRatingOfflineProvider.RATINGS_TABLE,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'component',
|
||||||
|
type: 'TEXT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ratingarea',
|
||||||
|
type: 'TEXT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'contextlevel',
|
||||||
|
type: 'INTEGER',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'instanceid',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'itemid',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'itemsetid',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'courseid',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'scaleid',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'rating',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'rateduserid',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'aggregation',
|
||||||
|
type: 'INTEGER'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
primaryKeys: ['component', 'ratingarea', 'contextlevel', 'instanceid', 'itemid']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider) {
|
||||||
|
this.sitesProvider.registerSiteSchema(this.siteSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an offline rating.
|
||||||
|
*
|
||||||
|
* @param {string} contextLevel Context level: course, module, user, etc.
|
||||||
|
* @param {numnber} instanceId Context instance id.
|
||||||
|
* @param {string} component Component. Example: "mod_forum".
|
||||||
|
* @param {string} ratingArea Rating area. Example: "post".
|
||||||
|
* @param {number} itemId Item id. Example: forum post id.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<CoreRatingOfflineRating>} Promise resolved with the saved rating, rejected if not found.
|
||||||
|
*/
|
||||||
|
getRating(contextLevel: string, instanceId: number, component: string, ratingArea: string, itemId: number, siteId?: string):
|
||||||
|
Promise<CoreRatingOfflineRating> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const conditions = {
|
||||||
|
contextlevel: contextLevel,
|
||||||
|
instanceid: instanceId,
|
||||||
|
component: component,
|
||||||
|
ratingarea: ratingArea,
|
||||||
|
itemid: itemId
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.getDb().getRecord(CoreRatingOfflineProvider.RATINGS_TABLE, conditions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an offline rating.
|
||||||
|
*
|
||||||
|
* @param {string} component Component. Example: "mod_forum".
|
||||||
|
* @param {string} ratingArea Rating area. Example: "post".
|
||||||
|
* @param {string} contextLevel Context level: course, module, user, etc.
|
||||||
|
* @param {numnber} instanceId Context instance id.
|
||||||
|
* @param {number} itemId Item id. Example: forum post id.
|
||||||
|
* @param {number} itemSetId Item set id. Example: forum discussion id.
|
||||||
|
* @param {number} courseId Course id.
|
||||||
|
* @param {number} scaleId Scale id.
|
||||||
|
* @param {number} rating Rating value. Use CoreRatingProvider.UNSET_RATING to delete rating.
|
||||||
|
* @param {number} ratedUserId Rated user id.
|
||||||
|
* @param {number} aggregateMethod Aggregate method.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the rating is saved.
|
||||||
|
*/
|
||||||
|
addRating(component: string, ratingArea: string, contextLevel: string, instanceId: number, itemId: number, itemSetId: number,
|
||||||
|
courseId: number, scaleId: number, rating: number, ratedUserId: number, aggregateMethod: number, siteId?: string):
|
||||||
|
Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const data: CoreRatingOfflineRating = {
|
||||||
|
component: component,
|
||||||
|
ratingarea: ratingArea,
|
||||||
|
contextlevel: contextLevel,
|
||||||
|
instanceid: instanceId,
|
||||||
|
itemid: itemId,
|
||||||
|
itemsetid: itemSetId,
|
||||||
|
courseid: courseId,
|
||||||
|
scaleid: scaleId,
|
||||||
|
rating: rating,
|
||||||
|
rateduserid: ratedUserId,
|
||||||
|
aggregation: aggregateMethod
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.getDb().insertRecord(CoreRatingOfflineProvider.RATINGS_TABLE, data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete offline rating.
|
||||||
|
*
|
||||||
|
* @param {string} component Component. Example: "mod_forum".
|
||||||
|
* @param {string} ratingArea Rating area. Example: "post".
|
||||||
|
* @param {string} contextLevel Context level: course, module, user, etc.
|
||||||
|
* @param {number} itemId Item id. Example: forum post id.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the rating is saved.
|
||||||
|
*/
|
||||||
|
deleteRating(component: string, ratingArea: string, contextLevel: string, instanceId: number, itemId: number, siteId?: string):
|
||||||
|
Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const conditions = {
|
||||||
|
component: component,
|
||||||
|
ratingarea: ratingArea,
|
||||||
|
contextlevel: contextLevel,
|
||||||
|
instanceid: instanceId,
|
||||||
|
itemid: itemId
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.getDb().deleteRecords(CoreRatingOfflineProvider.RATINGS_TABLE, conditions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of item sets in a component or instance.
|
||||||
|
*
|
||||||
|
* @param {string} component Component. Example: "mod_forum".
|
||||||
|
* @param {string} ratingArea Rating Area. Example: "post".
|
||||||
|
* @param {string} [contextLevel] Context level: course, module, user, etc.
|
||||||
|
* @param {numnber} [instanceId] Context instance id.
|
||||||
|
* @param {number} [itemSetId] Item set id.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<CoreRatingItemSet[]>} Promise resolved with the list of item set ids.
|
||||||
|
*/
|
||||||
|
getItemSets(component: string, ratingArea: string, contextLevel?: string, instanceId?: number, itemSetId?: number,
|
||||||
|
siteId?: string): Promise<CoreRatingItemSet[]> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const fields = 'DISTINCT contextlevel, instanceid, itemsetid, courseid';
|
||||||
|
const conditions: any = {
|
||||||
|
component,
|
||||||
|
ratingarea: ratingArea
|
||||||
|
};
|
||||||
|
if (contextLevel != null && instanceId != null) {
|
||||||
|
conditions.contextlevel = contextLevel;
|
||||||
|
conditions.instanceId = instanceId;
|
||||||
|
}
|
||||||
|
if (itemSetId != null) {
|
||||||
|
conditions.itemSetId = itemSetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return site.getDb().getRecords(CoreRatingOfflineProvider.RATINGS_TABLE, conditions, undefined, fields)
|
||||||
|
.then((records: any[]) => {
|
||||||
|
return records.map((record) => {
|
||||||
|
return {
|
||||||
|
component,
|
||||||
|
ratingArea,
|
||||||
|
contextLevel: record.contextlevel,
|
||||||
|
instanceId: record.instanceid,
|
||||||
|
itemSetId: record.itemsetid,
|
||||||
|
courseId: record.courseid
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get offline ratings of an item set.
|
||||||
|
*
|
||||||
|
* @param {string} component Component. Example: "mod_forum".
|
||||||
|
* @param {string} ratingArea Rating Area. Example: "post".
|
||||||
|
* @param {string} contextLevel Context level: course, module, user, etc.
|
||||||
|
* @param {number} itemId Item id. Example: forum post id.
|
||||||
|
* @param {number} itemSetId Item set id. Example: forum discussion id.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<CoreRatingOfflineRating[]>} Promise resolved with the list of ratings.
|
||||||
|
*/
|
||||||
|
getRatings(component: string, ratingArea: string, contextLevel: string, instanceId: number, itemSetId: number, siteId?: string):
|
||||||
|
Promise<CoreRatingOfflineRating[]> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const conditions = {
|
||||||
|
component,
|
||||||
|
ratingarea: ratingArea,
|
||||||
|
contextlevel: contextLevel,
|
||||||
|
instanceid: instanceId,
|
||||||
|
itemsetid: itemSetId
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.getDb().getRecords(CoreRatingOfflineProvider.RATINGS_TABLE, conditions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether a component, instance or item set has offline ratings.
|
||||||
|
*
|
||||||
|
* @param {string} component Component. Example: "mod_forum".
|
||||||
|
* @param {string} ratingArea Rating Area. Example: "post".
|
||||||
|
* @param {string} [contextLevel] Context level: course, module, user, etc.
|
||||||
|
* @param {number} [instanceId] Context instance id.
|
||||||
|
* @param {number} [itemSetId] Item set id. Example: forum discussion id.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<boolean>} Promise resolved with a boolean.
|
||||||
|
*/
|
||||||
|
hasRatings(component: string, ratingArea: string, contextLevel?: string, instanceId?: number, itemSetId?: number,
|
||||||
|
siteId?: string): Promise<boolean> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const conditions: any = {
|
||||||
|
component,
|
||||||
|
ratingarea: ratingArea
|
||||||
|
};
|
||||||
|
if (contextLevel != null && instanceId != null) {
|
||||||
|
conditions.contextlevel = contextLevel;
|
||||||
|
conditions.instanceId = instanceId;
|
||||||
|
}
|
||||||
|
if (itemSetId != null) {
|
||||||
|
conditions.itemsetid = itemSetId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.utils.promiseWorks(site.getDb().recordExists(CoreRatingOfflineProvider.RATINGS_TABLE, conditions));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,352 @@
|
||||||
|
// (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 { Injectable } from '@angular/core';
|
||||||
|
import { CoreSiteWSPreSets } from '@classes/site';
|
||||||
|
import { CoreAppProvider } from '@providers/app';
|
||||||
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
import { CoreUserProvider } from '@core/user/providers/user';
|
||||||
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
import { CoreRatingOfflineProvider } from './offline';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure of the rating info returned by web services.
|
||||||
|
*/
|
||||||
|
export interface CoreRatingInfo {
|
||||||
|
contextid: number;
|
||||||
|
component: string;
|
||||||
|
ratingarea: string;
|
||||||
|
canviewall: boolean;
|
||||||
|
canviewany: boolean;
|
||||||
|
scales?: CoreRatingScale[];
|
||||||
|
ratings?: CoreRatingInfoItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure of scales in the rating info.
|
||||||
|
*/
|
||||||
|
export interface CoreRatingScale {
|
||||||
|
id: number;
|
||||||
|
courseid?: number;
|
||||||
|
name?: string;
|
||||||
|
max: number;
|
||||||
|
isnumeric: boolean;
|
||||||
|
items?: {value: number, name: string}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure of items in the rating info.
|
||||||
|
*/
|
||||||
|
export interface CoreRatingInfoItem {
|
||||||
|
itemid: number;
|
||||||
|
scaleid?: number;
|
||||||
|
scale?: CoreRatingScale;
|
||||||
|
userid?: number;
|
||||||
|
aggregate?: number;
|
||||||
|
aggregatestr?: string;
|
||||||
|
count?: number;
|
||||||
|
rating?: number;
|
||||||
|
canrate?: boolean;
|
||||||
|
canviewaggregate?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure of a rating returned by the item ratings web service.
|
||||||
|
*/
|
||||||
|
export interface CoreRatingItemRating {
|
||||||
|
id: number;
|
||||||
|
userid: number;
|
||||||
|
userpictureurl: string;
|
||||||
|
userfullname: string;
|
||||||
|
rating: string;
|
||||||
|
timemodified: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service to handle ratings.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CoreRatingProvider {
|
||||||
|
|
||||||
|
static AGGREGATE_NONE = 0; // No ratings.
|
||||||
|
static AGGREGATE_AVERAGE = 1;
|
||||||
|
static AGGREGATE_COUNT = 2;
|
||||||
|
static AGGREGATE_MAXIMUM = 3;
|
||||||
|
static AGGREGATE_MINIMUM = 4;
|
||||||
|
static AGGREGATE_SUM = 5;
|
||||||
|
|
||||||
|
static UNSET_RATING = -999;
|
||||||
|
|
||||||
|
static AGGREGATE_CHANGED_EVENT = 'core_rating_aggregate_changed';
|
||||||
|
static RATING_SAVED_EVENT = 'core_rating_rating_saved';
|
||||||
|
|
||||||
|
protected ROOT_CACHE_KEY = 'CoreRating:';
|
||||||
|
|
||||||
|
constructor(private appProvider: CoreAppProvider,
|
||||||
|
private eventsProvider: CoreEventsProvider,
|
||||||
|
private sitesProvider: CoreSitesProvider,
|
||||||
|
private userProvider: CoreUserProvider,
|
||||||
|
private utils: CoreUtilsProvider,
|
||||||
|
private ratingOffline: CoreRatingOfflineProvider) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the web serivce to add ratings is available.
|
||||||
|
*
|
||||||
|
* @return {boolean} If WS is abalaible.
|
||||||
|
* @since 3.2
|
||||||
|
*/
|
||||||
|
isAddRatingWSAvailable(): boolean {
|
||||||
|
return this.sitesProvider.wsAvailableInCurrentSite('core_rating_add_rating');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a rating to an item.
|
||||||
|
*
|
||||||
|
* @param {string} component Component. Example: "mod_forum".
|
||||||
|
* @param {string} ratingArea Rating area. Example: "post".
|
||||||
|
* @param {string} contextLevel Context level: course, module, user, etc.
|
||||||
|
* @param {number} instanceId Context instance id.
|
||||||
|
* @param {number} itemId Item id. Example: forum post id.
|
||||||
|
* @param {number} itemSetId Item set id. Example: forum discussion id.
|
||||||
|
* @param {number} courseId Course id.
|
||||||
|
* @param {number} scaleId Scale id.
|
||||||
|
* @param {number} rating Rating value. Use CoreRatingProvider.UNSET_RATING to delete rating.
|
||||||
|
* @param {number} ratedUserId Rated user id.
|
||||||
|
* @param {number} aggregateMethod Aggregate method.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<CoreRatingItemRating|null>} Promise resolved with the aggregated rating or null if stored offline.
|
||||||
|
* @since 3.2
|
||||||
|
*/
|
||||||
|
addRating(component: string, ratingArea: string, contextLevel: string, instanceId: number, itemId: number, itemSetId: number,
|
||||||
|
courseId: number, scaleId: number, rating: number, ratedUserId: number, aggregateMethod: number, siteId?: string):
|
||||||
|
Promise<CoreRatingItemRating[]> {
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
// Convenience function to store a rating to be synchronized later.
|
||||||
|
const storeOffline = (): Promise<any> => {
|
||||||
|
return this.ratingOffline.addRating(component, ratingArea, contextLevel, instanceId, itemId, itemSetId, courseId,
|
||||||
|
scaleId, rating, ratedUserId, aggregateMethod, siteId).then(() => {
|
||||||
|
this.eventsProvider.trigger(CoreRatingProvider.RATING_SAVED_EVENT, {
|
||||||
|
component,
|
||||||
|
ratingArea,
|
||||||
|
contextLevel,
|
||||||
|
instanceId,
|
||||||
|
itemSetId,
|
||||||
|
itemId
|
||||||
|
}, siteId);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!this.appProvider.isOnline()) {
|
||||||
|
// App is offline, store the action.
|
||||||
|
return storeOffline();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.ratingOffline.deleteRating(component, ratingArea, contextLevel, instanceId, itemId, siteId).then(() => {
|
||||||
|
return this.addRatingOnline(component, ratingArea, contextLevel, instanceId, itemId, scaleId, rating, ratedUserId,
|
||||||
|
aggregateMethod, siteId).catch((error) => {
|
||||||
|
|
||||||
|
if (this.utils.isWebServiceError(error)) {
|
||||||
|
// The WebService has thrown an error or offline not supported, reject.
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Couldn't connect to server, store offline.
|
||||||
|
return storeOffline();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a rating to an item. It will fail if offline or cannot connect.
|
||||||
|
*
|
||||||
|
* @param {string} component Component. Example: "mod_forum".
|
||||||
|
* @param {string} ratingArea Rating area. Example: "post".
|
||||||
|
* @param {string} contextLevel Context level: course, module, user, etc.
|
||||||
|
* @param {number} instanceId Context instance id.
|
||||||
|
* @param {number} itemId Item id. Example: forum post id.
|
||||||
|
* @param {number} scaleId Scale id.
|
||||||
|
* @param {number} rating Rating value. Use CoreRatingProvider.UNSET_RATING to delete rating.
|
||||||
|
* @param {number} ratedUserId Rated user id.
|
||||||
|
* @param {number} aggregateMethod Aggregate method.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<CoreRatingItemRating>} Promise resolved with the aggregated rating.
|
||||||
|
* @since 3.2
|
||||||
|
*/
|
||||||
|
addRatingOnline(component: string, ratingArea: string, contextLevel: string, instanceId: number, itemId: number,
|
||||||
|
scaleId: number, rating: number, ratedUserId: number, aggregateMethod: number, siteId?: string):
|
||||||
|
Promise<CoreRatingItemRating> {
|
||||||
|
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const params = {
|
||||||
|
contextlevel: contextLevel,
|
||||||
|
instanceid: instanceId,
|
||||||
|
component: component,
|
||||||
|
ratingarea: ratingArea,
|
||||||
|
itemid: itemId,
|
||||||
|
scaleid: scaleId,
|
||||||
|
rating: rating,
|
||||||
|
rateduserid: ratedUserId,
|
||||||
|
aggregation: aggregateMethod
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.write('core_rating_add_rating', params).then((response) => {
|
||||||
|
return this.invalidateRatingItems(contextLevel, instanceId, component, ratingArea, itemId, scaleId).then(() => {
|
||||||
|
this.eventsProvider.trigger(CoreRatingProvider.AGGREGATE_CHANGED_EVENT, {
|
||||||
|
contextLevel,
|
||||||
|
instanceId,
|
||||||
|
component,
|
||||||
|
ratingArea,
|
||||||
|
itemId,
|
||||||
|
aggregate: response.aggregate,
|
||||||
|
count: response.count
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get item ratings.
|
||||||
|
*
|
||||||
|
* @param {string} contextLevel Context level: course, module, user, etc.
|
||||||
|
* @param {number} instanceId Context instance id.
|
||||||
|
* @param {string} component Component. Example: "mod_forum".
|
||||||
|
* @param {string} ratingArea Rating area. Example: "post".
|
||||||
|
* @param {number} itemId Item id. Example: forum post id.
|
||||||
|
* @param {number} scaleId Scale id.
|
||||||
|
* @param {string} [sort="timemodified"] Sort field.
|
||||||
|
* @param {number} [courseId] Course id. Used for fetching user profiles.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @param {boolean} [ignoreCache=false] True if it should ignore cached data (it will always fail in offline or server down).
|
||||||
|
* @return {Promise<CoreRatingItemRating[]>} Promise resolved with the list of ratings.
|
||||||
|
*/
|
||||||
|
getItemRatings(contextLevel: string, instanceId: number, component: string, ratingArea: string, itemId: number,
|
||||||
|
scaleId: number, sort: string = 'timemodified', courseId?: number, siteId?: string, ignoreCache: boolean = false):
|
||||||
|
Promise<CoreRatingItemRating[]> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const params = {
|
||||||
|
contextlevel: contextLevel,
|
||||||
|
instanceid: instanceId,
|
||||||
|
component: component,
|
||||||
|
ratingarea: ratingArea,
|
||||||
|
itemid: itemId,
|
||||||
|
scaleid: scaleId,
|
||||||
|
sort: sort
|
||||||
|
};
|
||||||
|
const preSets: CoreSiteWSPreSets = {
|
||||||
|
cacheKey: this.getItemRatingsCacheKey(contextLevel, instanceId, component, ratingArea, itemId, scaleId, sort)
|
||||||
|
};
|
||||||
|
if (ignoreCache) {
|
||||||
|
preSets.getFromCache = false;
|
||||||
|
preSets.emergencyCache = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return site.read('core_rating_get_item_ratings', params, preSets).then((response) => {
|
||||||
|
if (!response || !response.ratings) {
|
||||||
|
return Promise.reject(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to fetch profiles because the returned profile pictures are incorrect.
|
||||||
|
const promises = response.ratings.map((rating: CoreRatingItemRating) => {
|
||||||
|
return this.userProvider.getProfile(rating.userid, courseId, true, site.id).then((user) => {
|
||||||
|
rating.userpictureurl = user.profileimageurl;
|
||||||
|
}).catch(() => {
|
||||||
|
// Ignore error.
|
||||||
|
rating.userpictureurl = null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises).then(() => {
|
||||||
|
return response.ratings;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate item ratings.
|
||||||
|
*
|
||||||
|
* @param {string} contextLevel Context level: course, module, user, etc.
|
||||||
|
* @param {number} instanceId Context instance id.
|
||||||
|
* @param {string} component Component. Example: "mod_forum".
|
||||||
|
* @param {string} ratingArea Rating area. Example: "post".
|
||||||
|
* @param {number} itemId Item id. Example: forum post id.
|
||||||
|
* @param {number} scaleId Scale id.
|
||||||
|
* @param {string} [sort="timemodified"] Sort field.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateRatingItems(contextLevel: string, instanceId: number, component: string, ratingArea: string,
|
||||||
|
itemId: number, scaleId: number, sort: string = 'timemodified', siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const key = this.getItemRatingsCacheKey(contextLevel, instanceId, component, ratingArea, itemId, scaleId, sort);
|
||||||
|
|
||||||
|
return site.invalidateWsCacheForKey(key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefetch individual ratings.
|
||||||
|
*
|
||||||
|
* This function should be called from the prefetch handler of activities with ratings.
|
||||||
|
*
|
||||||
|
* @param {string} contextLevel Context level: course, module, user, etc.
|
||||||
|
* @param {number} instanceId Instance id.
|
||||||
|
* @param {string} [siteId] Site id. If not defined, current site.
|
||||||
|
* @param {number} [courseId] Course id. Used for prefetching user profiles.
|
||||||
|
* @param {CoreRatingInfo} [ratingInfo] Rating info returned by web services.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
prefetchRatings(contextLevel: string, instanceId: number, scaleId: number, courseId?: number, ratingInfo?: CoreRatingInfo,
|
||||||
|
siteId?: string): Promise<any> {
|
||||||
|
if (!ratingInfo || !ratingInfo.ratings) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const promises = ratingInfo.ratings.map((item) => {
|
||||||
|
return this.getItemRatings(contextLevel, instanceId, ratingInfo.component, ratingInfo.ratingarea, item.itemid,
|
||||||
|
scaleId, undefined, courseId, site.id, true).then((ratings) => {
|
||||||
|
const userIds = ratings.map((rating: CoreRatingItemRating) => rating.userid);
|
||||||
|
|
||||||
|
return this.userProvider.prefetchProfiles(userIds, courseId, site.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache key for rating items WS calls.
|
||||||
|
*
|
||||||
|
* @param {string} contextLevel Context level: course, module, user, etc.
|
||||||
|
* @param {string} component Component. Example: "mod_forum".
|
||||||
|
* @param {string} ratingArea Rating area. Example: "post".
|
||||||
|
* @param {number} itemId Item id. Example: forum post id.
|
||||||
|
* @param {number} scaleId Scale id.
|
||||||
|
* @param {string} sort Sort field.
|
||||||
|
* @return {string} Cache key.
|
||||||
|
*/
|
||||||
|
protected getItemRatingsCacheKey(contextLevel: string, instanceId: number, component: string, ratingArea: string,
|
||||||
|
itemId: number, scaleId: number, sort: string): string {
|
||||||
|
return `${this.ROOT_CACHE_KEY}${contextLevel}:${instanceId}:${component}:${ratingArea}:${itemId}:${scaleId}:${sort}`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,196 @@
|
||||||
|
// (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 { Injectable } from '@angular/core';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { CoreSyncBaseProvider } from '@classes/base-sync';
|
||||||
|
import { CoreAppProvider } from '@providers/app';
|
||||||
|
import { CoreLoggerProvider } from '@providers/logger';
|
||||||
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
import { CoreSyncProvider } from '@providers/sync';
|
||||||
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
|
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||||
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
import { CoreRatingProvider } from './rating';
|
||||||
|
import { CoreRatingOfflineProvider, CoreRatingItemSet } from './offline';
|
||||||
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service to sync ratings.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CoreRatingSyncProvider extends CoreSyncBaseProvider {
|
||||||
|
|
||||||
|
static SYNCED_EVENT = 'core_rating_synced';
|
||||||
|
|
||||||
|
constructor(translate: TranslateService,
|
||||||
|
appProvider: CoreAppProvider,
|
||||||
|
private eventsProvider: CoreEventsProvider,
|
||||||
|
loggerProvider: CoreLoggerProvider,
|
||||||
|
sitesProvider: CoreSitesProvider,
|
||||||
|
syncProvider: CoreSyncProvider,
|
||||||
|
textUtils: CoreTextUtilsProvider,
|
||||||
|
timeUtils: CoreTimeUtilsProvider,
|
||||||
|
private utils: CoreUtilsProvider,
|
||||||
|
private ratingProvider: CoreRatingProvider,
|
||||||
|
private ratingOffline: CoreRatingOfflineProvider) {
|
||||||
|
|
||||||
|
super('CoreRatingSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to synchronize all the ratings of a certain component, instance or item set.
|
||||||
|
*
|
||||||
|
* This function should be called from the sync provider of activities with ratings.
|
||||||
|
*
|
||||||
|
* @param {string} component Component. Example: "mod_forum".
|
||||||
|
* @param {string} ratingArea Rating Area. Example: "post".
|
||||||
|
* @param {string} [contextLevel] Context level: course, module, user, etc.
|
||||||
|
* @param {numnber} [instanceId] Context instance id.
|
||||||
|
* @param {number} [itemSetId] Item set id.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
|
||||||
|
*/
|
||||||
|
syncRatings(component: string, ratingArea: string, contextLevel?: string, instanceId?: number, itemSetId?: number,
|
||||||
|
siteId?: string): Promise<{itemSet: CoreRatingItemSet, updated: boolean, warnings: string[]}[]> {
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
return this.ratingOffline.getItemSets(component, ratingArea, contextLevel, instanceId, itemSetId, siteId)
|
||||||
|
.then((itemSets) => {
|
||||||
|
const results = [];
|
||||||
|
const promises = itemSets.map((itemSet) => {
|
||||||
|
return this.syncItemSetIfNeeded(component, ratingArea, itemSet.contextLevel, itemSet.instanceId,
|
||||||
|
itemSet.itemSetId, siteId).then((result) => {
|
||||||
|
if (result.updated) {
|
||||||
|
// Sync successful, send event.
|
||||||
|
this.eventsProvider.trigger(CoreRatingSyncProvider.SYNCED_EVENT, {
|
||||||
|
...itemSet,
|
||||||
|
warnings: result.warnings
|
||||||
|
}, siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
results.push({itemSet, ...result});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises).then(() => {
|
||||||
|
return results;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync ratings of an item set only if a certain time has passed since the last time.
|
||||||
|
*
|
||||||
|
* @param {string} component Component. Example: "mod_forum".
|
||||||
|
* @param {string} ratingArea Rating Area. Example: "post".
|
||||||
|
* @param {string} contextLevel Context level: course, module, user, etc.
|
||||||
|
* @param {number} instanceId Context instance id.
|
||||||
|
* @param {number} itemSetId Item set id. Example: forum discussion id.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when ratings are synced or if it doesn't need to be synced.
|
||||||
|
*/
|
||||||
|
protected syncItemSetIfNeeded(component: string, ratingArea: string, contextLevel: string, instanceId: number,
|
||||||
|
itemSetId: number, siteId?: string): Promise<{updated: boolean, warnings: string[]}> {
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
const syncId = this.getItemSetSyncId(component, ratingArea, contextLevel, instanceId, itemSetId);
|
||||||
|
|
||||||
|
return this.isSyncNeeded(syncId, siteId).then((needed) => {
|
||||||
|
if (needed) {
|
||||||
|
return this.syncItemSet(component, ratingArea, contextLevel, instanceId, itemSetId, siteId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronize all offline ratings of an item set.
|
||||||
|
*
|
||||||
|
* @param {string} component Component. Example: "mod_forum".
|
||||||
|
* @param {string} ratingArea Rating Area. Example: "post".
|
||||||
|
* @param {string} contextLevel Context level: course, module, user, etc.
|
||||||
|
* @param {number} instanceId Context instance id.
|
||||||
|
* @param {number} itemSetId Item set id. Example: forum discussion id.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved if sync is successful, rejected otherwise.
|
||||||
|
*/
|
||||||
|
protected syncItemSet(component: string, ratingArea: string, contextLevel: string, instanceId: number, itemSetId: number,
|
||||||
|
siteId?: string): Promise<{updated: boolean, warnings: string[]}> {
|
||||||
|
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
const syncId = this.getItemSetSyncId(component, ratingArea, contextLevel, instanceId, itemSetId);
|
||||||
|
if (this.isSyncing(syncId, siteId)) {
|
||||||
|
// There's already a sync ongoing for this item set, return the promise.
|
||||||
|
return this.getOngoingSync(syncId, siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.debug(`Try to sync ratings of component '${component}' rating area '${ratingArea}'` +
|
||||||
|
` context level '${contextLevel}' instance ${instanceId} item set ${itemSetId}`);
|
||||||
|
|
||||||
|
let updated = false;
|
||||||
|
const warnings = [];
|
||||||
|
|
||||||
|
return this.ratingOffline.getRatings(component, ratingArea, contextLevel, instanceId, itemSetId, siteId).then((ratings) => {
|
||||||
|
if (!ratings.length) {
|
||||||
|
// Nothing to sync.
|
||||||
|
return;
|
||||||
|
} else if (!this.appProvider.isOnline()) {
|
||||||
|
// Cannot sync in offline.
|
||||||
|
return Promise.reject(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const promises = ratings.map((rating) => {
|
||||||
|
return this.ratingProvider.addRatingOnline(component, ratingArea, rating.contextlevel, rating.instanceid,
|
||||||
|
rating.itemid, rating.scaleid, rating.rating, rating.rateduserid, rating.aggregation, siteId)
|
||||||
|
.catch((error) => {
|
||||||
|
if (this.utils.isWebServiceError(error)) {
|
||||||
|
warnings.push(this.textUtils.getErrorMessageFromError(error));
|
||||||
|
} else {
|
||||||
|
// Couldn't connect to server, reject.
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
updated = true;
|
||||||
|
|
||||||
|
return this.ratingOffline.deleteRating(component, ratingArea, rating.contextlevel, rating.instanceid,
|
||||||
|
rating.itemid, siteId).finally(() => {
|
||||||
|
return this.ratingProvider.invalidateRatingItems(rating.contextlevel, rating.instanceid, component,
|
||||||
|
ratingArea, rating.itemid, rating.scaleid, undefined, siteId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises).then(() => {
|
||||||
|
// All done, return the warnings.
|
||||||
|
return { updated, warnings };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the sync id of an item set.
|
||||||
|
*
|
||||||
|
* @param {string} component Component. Example: "mod_forum".
|
||||||
|
* @param {string} ratingArea Rating Area. Example: "post".
|
||||||
|
* @param {string} contextLevel Context level: course, module, user, etc.
|
||||||
|
* @param {number} instanceId Context instance id.
|
||||||
|
* @param {number} itemSetId Item set id. Example: forum discussion id.
|
||||||
|
* @return {string} Sync id.
|
||||||
|
*/
|
||||||
|
protected getItemSetSyncId(component: string, ratingArea: string, contextLevel: string, instanceId: number, itemSetId: number):
|
||||||
|
string {
|
||||||
|
return `itemSet#${component}#${ratingArea}#${contextLevel}#${instanceId}#${itemSetId}`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
// (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 { NgModule } from '@angular/core';
|
||||||
|
import { CoreRatingProvider } from './providers/rating';
|
||||||
|
import { CoreRatingOfflineProvider } from './providers/offline';
|
||||||
|
import { CoreRatingSyncProvider } from './providers/sync';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
CoreRatingProvider,
|
||||||
|
CoreRatingOfflineProvider,
|
||||||
|
CoreRatingSyncProvider
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class CoreRatingModule {}
|
Loading…
Reference in New Issue