MOBILE-3663 forum: Add ratings to forum

main
Pau Ferrer Ocaña 2021-03-05 16:38:42 +01:00
parent 44a03b61e7
commit 44e3c42d76
7 changed files with 100 additions and 60 deletions

View File

@ -18,6 +18,7 @@ import { CoreCourseComponentsModule } from '@features/course/components/componen
import { CoreEditorComponentsModule } from '@features/editor/components/components.module';
import { CoreSharedModule } from '@/core/shared.module';
import { CoreTagComponentsModule } from '@features/tag/components/components.module';
import { CoreRatingComponentsModule } from '@features/rating/components/components.module';
import { AddonModForumDiscussionOptionsMenuComponent } from './discussion-options-menu/discussion-options-menu';
import { AddonModForumEditPostComponent } from './edit-post/edit-post';
@ -40,6 +41,7 @@ import { AddonModForumSortOrderSelectorComponent } from './sort-order-selector/s
CoreCourseComponentsModule,
CoreTagComponentsModule,
CoreEditorComponentsModule,
CoreRatingComponentsModule,
],
exports: [
AddonModForumIndexComponent,

View File

@ -50,6 +50,10 @@ import { CoreScreen } from '@services/screen';
import { CoreArray } from '@singletons/array';
import { AddonModForumPrefetchHandler } from '../../services/handlers/prefetch';
import { AddonModForumModuleHandlerService } from '../../services/handlers/module';
import { CoreRatingProvider } from '@features/rating/services/rating';
import { CoreRatingSyncProvider } from '@features/rating/services/rating-sync';
import { CoreRatingOffline } from '@features/rating/services/rating-offline';
import { ContextLevel } from '@/core/constants';
/**
* Component that displays a forum entry page.
@ -88,9 +92,9 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
protected viewDiscObserver?: CoreEventObserver;
protected changeDiscObserver?: CoreEventObserver;
hasOfflineRatings?: boolean;
protected ratingOfflineObserver: any;
protected ratingSyncObserver: any;
hasOfflineRatings = false;
protected ratingOfflineObserver?: CoreEventObserver;
protected ratingSyncObserver?: CoreEventObserver;
constructor(
route: ActivatedRoute,
@ -166,7 +170,21 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
}
});
// @todo Listen for offline ratings saved and synced.
// Listen for offline ratings saved and synced.
this.ratingOfflineObserver = CoreEvents.on(CoreRatingProvider.RATING_SAVED_EVENT, (data) => {
if (this.forum && data.component == 'mod_forum' && data.ratingArea == 'post' &&
data.contextLevel == ContextLevel.MODULE && data.instanceId == this.forum.cmid) {
this.hasOfflineRatings = true;
}
});
this.ratingSyncObserver = CoreEvents.on(CoreRatingSyncProvider.SYNCED_EVENT, async (data) => {
if (this.forum && data.component == 'mod_forum' && data.ratingArea == 'post' &&
data.contextLevel == ContextLevel.MODULE && data.instanceId == this.forum.cmid) {
this.hasOfflineRatings =
await CoreRatingOffline.hasRatings('mod_forum', 'post', ContextLevel.MODULE, this.forum.cmid);
}
});
}
async ngAfterViewInit(): Promise<void> {
@ -224,7 +242,11 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
await Promise.all([
this.fetchOfflineDiscussions(),
this.fetchDiscussions(refresh),
// @todo fetch hasOfflineRatings.
CoreRatingOffline.hasRatings('mod_forum', 'post', ContextLevel.MODULE, this.forum!.cmid).then((hasRatings) => {
this.hasOfflineRatings = hasRatings;
return;
}),
]);
} catch (error) {
if (refresh) {

View File

@ -75,8 +75,7 @@
<core-tag-list [tags]="post.tags"></core-tag-list>
</ion-label>
</ion-item>
<!-- @todo -->
<!-- <core-rating-rate *ngIf="forum && ratingInfo"
<core-rating-rate *ngIf="forum && ratingInfo"
[ratingInfo]="ratingInfo" contextLevel="module" [instanceId]="componentId" [itemId]="post.id"
[itemSetId]="discussionId" [courseId]="courseId" [aggregateMethod]="forum.assessed" [scaleId]="forum.scale"
[userId]="post.author.id" (onUpdate)="ratingUpdated()">
@ -84,7 +83,7 @@
<core-rating-aggregate *ngIf="forum && ratingInfo"
[ratingInfo]="ratingInfo" contextLevel="module" [instanceId]="componentId" [itemId]="post.id"
[courseId]="courseId" [aggregateMethod]="forum.assessed" [scaleId]="forum.scale">
</core-rating-aggregate> -->
</core-rating-aggregate>
<ion-item *ngIf="post.id > 0 && post.capabilities.reply && !post.isprivatereply"
class="ion-no-padding ion-text-end addon-forum-reply-button">

View File

@ -52,6 +52,7 @@ import { AddonModForumOffline, AddonModForumReplyOptions } from '../../services/
import { CoreUtils } from '@services/utils/utils';
import { AddonModForumPostOptionsMenuComponent } from '../post-options-menu/post-options-menu';
import { AddonModForumEditPostComponent } from '../edit-post/edit-post';
import { CoreRatingInfo } from '@features/rating/services/rating';
/**
* Components that shows a discussion post, its attachments and the action buttons allowed (reply, etc.).
@ -75,7 +76,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
@Input() forum!: AddonModForumData; // The forum the post belongs to. Required for attachments and offline posts.
@Input() accessInfo!: AddonModForumAccessInformation; // Forum access information.
@Input() parentSubject?: string; // Subject of parent post.
@Input() ratingInfo?: any; // TODO CoreRatingInfo; // Rating info item.
@Input() ratingInfo?: CoreRatingInfo; // Rating info item.
@Input() leavingPage?: boolean; // Whether the page that contains this post is being left and will be destroyed.
@Input() highlight = false;
@Output() onPostChange: EventEmitter<void> = new EventEmitter<void>(); // Event emitted when a reply is posted or modified.

View File

@ -12,9 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { ContextLevel } from '@/core/constants';
import { Component, OnDestroy, ViewChild, OnInit, AfterViewInit, ElementRef, Optional } from '@angular/core';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { CoreFileUploader } from '@features/fileuploader/services/fileuploader';
import { CoreRatingInfo, CoreRatingProvider } from '@features/rating/services/rating';
import { CoreRatingOffline } from '@features/rating/services/rating-offline';
import { CoreRatingSyncProvider } from '@features/rating/services/rating-sync';
import { CoreUser } from '@features/user/services/user';
import { CanLeave } from '@guards/can-leave';
import { IonContent } from '@ionic/angular';
@ -34,7 +38,6 @@ import {
AddonModForumDiscussion,
AddonModForumPost,
AddonModForumProvider,
AddonModForumRatingInfo,
} from '../../services/forum';
import { AddonModForumHelper } from '../../services/helper';
import { AddonModForumOffline } from '../../services/offline';
@ -101,8 +104,8 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
protected syncObserver?: CoreEventObserver;
protected syncManualObserver?: CoreEventObserver;
ratingInfo?: AddonModForumRatingInfo;
hasOfflineRatings!: boolean;
ratingInfo?: CoreRatingInfo;
hasOfflineRatings = false;
protected ratingOfflineObserver?: CoreEventObserver;
protected ratingSyncObserver?: CoreEventObserver;
protected changeDiscObserver?: CoreEventObserver;
@ -203,7 +206,20 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
AddonModForum.invalidateDiscussionsList(this.forumId);
}
// @todo Listen for offline ratings saved and synced.
// Listen for offline ratings saved and synced.
this.ratingOfflineObserver = CoreEvents.on(CoreRatingProvider.RATING_SAVED_EVENT, (data) => {
if (data.component == 'mod_forum' && data.ratingArea == 'post' && data.contextLevel == ContextLevel.MODULE &&
data.instanceId == this.cmId && data.itemSetId == this.discussionId) {
this.hasOfflineRatings = true;
}
});
this.ratingSyncObserver = CoreEvents.on(CoreRatingSyncProvider.SYNCED_EVENT, async (data) => {
if (data.component == 'mod_forum' && data.ratingArea == 'post' && data.contextLevel == ContextLevel.MODULE &&
data.instanceId == this.cmId && data.itemSetId == this.discussionId) {
this.hasOfflineRatings = false;
}
});
this.changeDiscObserver = CoreEvents.on(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, data => {
if ((this.forumId && this.forumId === data.forumId) || data.cmId === this.cmId) {
@ -345,7 +361,8 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
const response = await AddonModForum.getDiscussionPosts(this.discussionId, { cmId: this.cmId });
const replies = await AddonModForumOffline.getDiscussionReplies(this.discussionId);
const ratingInfo = response.ratinginfo;
this.ratingInfo = response.ratinginfo;
onlinePosts = response.posts;
this.courseId = response.courseid || this.courseId;
this.forumId = response.forumid || this.forumId;
@ -462,7 +479,6 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
}
this.posts = posts;
this.ratingInfo = ratingInfo;
this.postSubjects = this.getAllPosts().reduce(
(postSubjects, post) => {
postSubjects[post.id] = post.subject;
@ -487,7 +503,8 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
this.canPin = false;
}
// @todo fetch hasOfflineRatings.
this.hasOfflineRatings =
await CoreRatingOffline.hasRatings('mod_forum', 'post', ContextLevel.MODULE, this.cmId, this.discussionId);
} catch (error) {
CoreDomUtils.showErrorModal(error);
} finally {

View File

@ -17,6 +17,7 @@ import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
import { CoreCourseCommonModWSOptions } from '@features/course/services/course';
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
import { CoreFileEntry } from '@features/fileuploader/services/fileuploader';
import { CoreRatingInfo } from '@features/rating/services/rating';
import { CoreUser } from '@features/user/services/user';
import { CoreApp } from '@services/app';
import { CoreFilepool } from '@services/filepool';
@ -553,7 +554,7 @@ export class AddonModForumProvider {
posts: AddonModForumPost[];
courseid?: number;
forumid?: number;
ratinginfo?: AddonModForumRatingInfo;
ratinginfo?: CoreRatingInfo;
}> {
// Convenience function to translate legacy data to new format.
const translateLegacyPostsFormat = (posts: AddonModForumLegacyPost[]): AddonModForumPost[] => posts.map((post) => {
@ -1526,40 +1527,6 @@ export type AddonModForumLegacyPost = {
}[];
};
/**
* Forum rating info.
*/
export type AddonModForumRatingInfo = {
contextid: number; // Context id.
component: string; // Context name.
ratingarea: string; // Rating area name.
canviewall?: boolean; // Whether the user can view all the individual ratings.
canviewany?: boolean; // Whether the user can view aggregate of ratings of others.
scales?: { // Different scales used information.
id: number; // Scale id.
courseid?: number; // Course id.
name?: string; // Scale name (when a real scale is used).
max: number; // Max value for the scale.
isnumeric: boolean; // Whether is a numeric scale.
items?: { // Scale items. Only returned for not numerical scales.
value: number; // Scale value/option id.
name: string; // Scale name.
}[];
}[];
ratings?: { // The ratings.
itemid: number; // Item id.
scaleid?: number; // Scale id.
userid?: number; // User who rated id.
aggregate?: number; // Aggregated ratings grade.
aggregatestr?: string; // Aggregated ratings as string.
aggregatelabel?: string; // The aggregation label.
count?: number; // Ratings count (used when aggregating).
rating?: number; // The rating the user gave.
canrate?: boolean; // Whether the user can rate the item.
canviewaggregate?: boolean; // Whether the user can view the aggregated grade.
}[];
};
/**
* Options to pass to get discussions.
*/
@ -1994,7 +1961,7 @@ export type AddonModForumGetDiscussionPostsWSResponse = {
posts: AddonModForumWSPost[];
forumid: number; // The forum id.
courseid: number; // The forum course id.
ratinginfo?: AddonModForumRatingInfo; // Rating information.
ratinginfo?: CoreRatingInfo; // Rating information.
warnings?: CoreWSExternalWarning[];
};
@ -2012,7 +1979,7 @@ export type AddonModForumGetForumDiscussionPostsWSParams = {
*/
export type AddonModForumGetForumDiscussionPostsWSResponse = {
posts: AddonModForumLegacyPost[];
ratinginfo?: AddonModForumRatingInfo; // Rating information.
ratinginfo?: CoreRatingInfo; // Rating information.
warnings?: CoreWSExternalWarning[];
};

View File

@ -12,11 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { ContextLevel } from '@/core/constants';
import { Injectable } from '@angular/core';
import { CoreSyncBaseProvider } from '@classes/base-sync';
import { CoreCourse } from '@features/course/services/course';
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
import { CoreFileUploader } from '@features/fileuploader/services/fileuploader';
import { CoreRatingSync } from '@features/rating/services/rating-sync';
import { CoreApp } from '@services/app';
import { CoreGroups } from '@services/groups';
import { CoreSites } from '@services/sites';
@ -327,14 +329,44 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider<AddonModForu
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved if sync is successful, rejected otherwise.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async syncRatings(cmId?: number, discussionId?: number, force?: boolean, siteId?: string): Promise<{
updated: boolean;
warnings: string[];
}> {
// @todo
async syncRatings(cmId?: number, discussionId?: number, force?: boolean, siteId?: string): Promise<AddonModForumSyncResult> {
siteId = siteId || CoreSites.getCurrentSiteId();
return { updated: true, warnings: [] };
const results =
await CoreRatingSync.syncRatings('mod_forum', 'post', ContextLevel.MODULE, cmId, discussionId, force, siteId);
let updated = false;
const warnings: string[] = [];
const promises: Promise<void>[] = [];
results.forEach((result) => {
if (result.updated.length) {
updated = true;
// Invalidate discussions of updated ratings.
promises.push(AddonModForum.invalidateDiscussionPosts(result.itemSet!.itemSetId, undefined, siteId));
}
if (result.warnings.length) {
// Fetch forum to construct the warning message.
promises.push(AddonModForum.getForum(result.itemSet!.courseId!, result.itemSet!.instanceId, { siteId })
.then((forum) => {
result.warnings.forEach((warning) => {
warnings.push(Translate.instant('core.warningofflinedatadeleted', {
component: this.componentTranslate,
name: forum.name,
error: warning,
}));
});
return;
}));
}
});
await CoreUtils.allPromises(promises);
return { updated, warnings };
}
/**