Skip to content
Snippets Groups Projects
Commit b5e34fd8 authored by Tom Käsler's avatar Tom Käsler
Browse files

Merge branch 'voting-logic-and-api' into 'master'

Voting logic and api

See merge request swtp-2019/arsnova-lite!24
parents f107a380 5246aeb9
No related merge requests found
Showing
with 308 additions and 147 deletions
<div fxLayout="column" fxLayoutAlign="center" fxLayoutGap="20px">
<mat-form-field class="input-block">
<textarea matInput #commentBody placeholder="{{ 'comment-page.enter-comment' | translate}}"
matAutosizeMinRows=2 matAutosizeMaxRows=5 maxlength="255" [formControl]="bodyForm"></textarea>
<mat-hint align="end">{{commentBody.value.length}} / 255</mat-hint>
</mat-form-field>
<div fxLayout="row" fxLayoutAlign="center">
<form>
<mat-form-field class="input-block">
<textarea matInput #commentBody placeholder="{{ 'comment-page.enter-comment' | translate}}"
matAutosizeMinRows=2 matAutosizeMaxRows=5 maxlength="255" [formControl]="bodyForm"></textarea>
<mat-hint align="end">{{commentBody.value.length}} / 255</mat-hint>
</mat-form-field>
<button mat-raised-button color="warn"
(click)="onNoClick()">{{ 'comment-page.abort' | translate}}</button>
<button mat-raised-button color="accent"
(click)="closeDialog(commentBody.value)">{{ 'comment-page.send' | translate}}</button>
</form>
<button mat-raised-button color="warn"
(click)="onNoClick()">{{ 'comment-page.abort' | translate}}</button>
<button mat-raised-button color="accent"
(click)="closeDialog(commentBody.value)">{{ 'comment-page.send' | translate}}</button>
</div>
</div>
......@@ -3,9 +3,9 @@ import { Comment } from '../../../../models/comment';
import { NotificationService } from '../../../../services/util/notification.service';
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material';
import { TranslateService } from '@ngx-translate/core';
import { CommentPageComponent } from '../../comment-page/comment-page.component';
import { FormControl, Validators } from '@angular/forms';
import { User } from '../../../../models/user';
import { CommentListComponent } from '../../comment-list/comment-list.component';
@Component({
......@@ -20,17 +20,15 @@ export class SubmitCommentComponent implements OnInit {
user: User;
roomId: string;
subjectForm = new FormControl('', [Validators.required]);
bodyForm = new FormControl('', [Validators.required]);
private date = new Date(Date.now());
constructor(
private notification: NotificationService,
public dialogRef: MatDialogRef<CommentPageComponent>,
private translateService: TranslateService,
public dialog: MatDialog,
private translationService: TranslateService,
@Inject(MAT_DIALOG_DATA) public data: any) {
private notification: NotificationService,
public dialogRef: MatDialogRef<CommentListComponent>,
private translateService: TranslateService,
public dialog: MatDialog,
private translationService: TranslateService,
@Inject(MAT_DIALOG_DATA) public data: any) {
}
ngOnInit() {
......
<div fxLayout="row" id="search-container">
<mat-label>
<mat-icon>search</mat-icon>
<mat-label fxLayoutAlign="center center">
<mat-icon class="search-icon">search</mat-icon>
</mat-label>
<input #searchBox placeholder="{{ 'comment-list-page.search-box-placeholder-text' | translate }}"
(focus)="hideCommentsList=true" (input)="searchComments(searchBox.value)">
<button mat-flat-button color="accent" *ngIf="hideCommentsList"
(click)="hideCommentsList=false;searchBox.value='';">{{ 'comment-list-page.cancel' | translate }}</button>
(input)="searchComments(searchBox.value)">
<button mat-button *ngIf="searchBox.value" (click)="hideCommentsList=false; searchBox.value=''">
<mat-icon>close</mat-icon>
</button>
<button mat-button *ngIf="!searchBox.value" color="accent" (click)="openSubmitDialog()">
<mat-icon class="add-icon">add_circle</mat-icon>
</button>
</div>
<mat-card class="outer-card" *ngIf="hideCommentsList && searchBox.value!=''">
<mat-card class="outer-card" *ngIf="hideCommentsList">
<app-comment *ngFor="let current of filteredComments" [comment]="current"> </app-comment>
</mat-card>
<mat-card class="outer-card" *ngIf="!hideCommentsList">
<app-comment *ngFor="let current of comments" [comment]="current"> </app-comment>
</mat-card>
\ No newline at end of file
<app-comment *ngFor="let current of comments | orderBy: 'score'" [comment]="current"> </app-comment>
</mat-card>
mat-card {
margin-bottom: 10px;
background-color: #4db6ac;
border-radius: 8px;
width: 100%;
max-width: 800px;
}
app-comment {
......@@ -13,29 +10,37 @@ app-comment {
input {
box-sizing: border-box;
padding: 0 10px 0 20px;
width: 100%;
background-color: #4db6ac;
padding: 0 10px 0 5px;
width: 90%;
background-color: #80cbc4;
border: none;
outline: none;
min-height: 60px;
font-size: large;
border-radius: 5px;
}
button{
border-left: 3px solid #b2dfdb;
border-radius: 0px;
min-height: 100% !important;
min-width: 100px;
#search-container{
border-radius: 5px;
background-color: #80cbc4;
margin-bottom: 10px;
}
mat-icon {
padding: 10px;
.add-button {
width: 44px!important;
height: 44px!important;
text-align: center;
}
mat-label {
min-height: 100% !important;
border-radius: 0px;
.add-icon {
font-size: 45px;
height: 45px;
width: 45px;
line-height: 100%!important;
}
#search-container{
background-color: #4db6ac;
margin-bottom: 10px;
.search-icon {
padding: 10px;
}
import { Component, OnInit } from '@angular/core';
import { Component, Input, OnInit } from '@angular/core';
import { Comment } from '../../../models/comment';
import { CommentService } from '../../../services/http/comment.service';
import { TranslateService } from '@ngx-translate/core';
import { LanguageService } from '../../../services/util/language.service';
import { RxStompService } from '@stomp/ng2-stompjs';
import { Message } from '@stomp/stompjs';
import { SubmitCommentComponent } from '../_dialogs/submit-comment/submit-comment.component';
import { MatDialog } from '@angular/material';
import { WsCommentServiceService } from '../../../services/websockets/ws-comment-service.service';
import { User } from '../../../models/user';
@Component({
selector: 'app-comment-list',
......@@ -12,16 +16,19 @@ import { Message } from '@stomp/stompjs';
styleUrls: ['./comment-list.component.scss']
})
export class CommentListComponent implements OnInit {
@Input() user: User;
@Input() roomId: string;
comments: Comment[];
isLoading = true;
roomId: string;
hideCommentsList: boolean;
filteredComments: Comment[];
constructor(private commentService: CommentService,
private translateService: TranslateService,
protected langService: LanguageService,
private rxStompService: RxStompService) {
private translateService: TranslateService,
public dialog: MatDialog,
protected langService: LanguageService,
private rxStompService: RxStompService,
private wsCommentService: WsCommentServiceService) {
langService.langEmitter.subscribe(lang => translateService.use(lang));
}
......@@ -45,35 +52,68 @@ export class CommentListComponent implements OnInit {
}
searchComments(term: string): void {
this.filteredComments = this.comments.filter(c => c.body.toLowerCase().includes(term));
if (term) {
this.hideCommentsList = true;
this.filteredComments = this.comments.filter(c => c.body.toLowerCase().includes(term.toLowerCase()));
} else {
this.hideCommentsList = false;
}
}
parseIncomingMessage(message: Message) {
const msg = JSON.parse(message.body);
const payload = msg.payload;
if (msg.type === 'CommentCreated') {
const c = new Comment();
c.roomId = this.roomId;
c.body = payload.body;
c.id = payload.id;
c.creationTimestamp = payload.timestamp;
this.comments = this.comments.concat(c);
} else if (msg.type === 'CommentPatched') {
switch (msg.type) {
case 'CommentCreated':
const c = new Comment();
c.roomId = this.roomId;
c.body = payload.body;
c.id = payload.id;
c.creationTimestamp = payload.timestamp;
this.comments = this.comments.concat(c);
break;
case 'CommentPatched':
for (let i = 0; i < this.comments.length; i++) {
if (payload.id === this.comments[i].id) {
for (const [key, value] of Object.entries(payload.changes)) {
switch (key) {
case 'read': this.comments[i].read = <boolean>value;
break;
case 'correct' : this.comments[i].correct = <boolean>value;
break;
case 'favorite' : this.comments[i].favorite = <boolean>value;
break;
case 'read':
this.comments[i].read = <boolean>value;
break;
case 'correct' :
this.comments[i].correct = <boolean>value;
break;
case 'favorite' :
this.comments[i].favorite = <boolean>value;
break;
case 'score' :
this.comments[i].score = <number>value;
break;
}
}
}
}
}
}
}
openSubmitDialog(): void {
const dialogRef = this.dialog.open(SubmitCommentComponent, {
width: '400px'
});
dialogRef.componentInstance.user = this.user;
dialogRef.componentInstance.roomId = this.roomId;
dialogRef.afterClosed()
.subscribe(result => {
if (result) {
this.send(result);
} else {
return;
}
});
}
send(comment: Comment): void {
this.wsCommentService.add(comment);
}
}
<div fxLayout="column" fxLayoutAlign="center" fxLayoutGap="20px">
<div fxLayout="row" fxLayoutAlign="center">
<mat-toolbar>List of Questions
<span class="fill-remaining-space"></span>
<button mat-icon-button color="primary" (click)="openSubmitDialog()">
<mat-icon>add_circle</mat-icon>
</button></mat-toolbar>
</div>
<div fxLayout="row" fxLayoutAlign="center">
<app-comment-list></app-comment-list>
<app-comment-list [user]="user" [roomId]="roomId"></app-comment-list>
</div>
</div>
......@@ -2,23 +2,3 @@ app-comment-list {
width: 100%;
max-width: 800px;
}
.mat-icon-button {
width: 50px;
height: 50px;
margin-bottom: 20px;
margin-top: 20px;
}
mat-icon {
font-size: 50px;
height: 50px;
width: 50px;
line-height: 100%!important;
}
mat-toolbar {
border-radius: 10px;
background-color: #bbdefb;
max-width: 800px;
}
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Comment } from '../../../models/comment';
import { User } from '../../../models/user';
import { NotificationService } from '../../../services/util/notification.service';
import { AuthenticationService } from '../../../services/http/authentication.service';
import { MatDialog } from '@angular/material';
import { SubmitCommentComponent } from '../_dialogs/submit-comment/submit-comment.component';
import { WsCommentServiceService } from '../../../services/websockets/ws-comment-service.service';
@Component({
selector: 'app-comment-page',
......@@ -19,32 +15,10 @@ export class CommentPageComponent implements OnInit {
constructor(private route: ActivatedRoute,
private notification: NotificationService,
public dialog: MatDialog,
private authenticationService: AuthenticationService,
private wsCommentService: WsCommentServiceService) { }
private authenticationService: AuthenticationService) { }
ngOnInit(): void {
this.roomId = localStorage.getItem('roomId');
this.user = this.authenticationService.getUser();
}
openSubmitDialog(): void {
const dialogRef = this.dialog.open(SubmitCommentComponent, {
width: '400px'
});
dialogRef.componentInstance.user = this.user;
dialogRef.componentInstance.roomId = this.roomId;
dialogRef.afterClosed()
.subscribe(result => {
if (result) {
this.send(result);
} else {
return;
}
});
}
send(comment: Comment): void {
this.wsCommentService.add(comment);
}
}
<mat-card class="card-container">
<div fxLayout="row" fxLayoutAlign="center center">
<div *ngIf="comment.createdFromLecturer !== false" mat-card-avatar class="proffesor-icon" matTooltip="created from lecturer"><mat-icon>record_voice_over</mat-icon></div>
<mat-card-content>
<p (click)="openPresentDialog(comment.body)">{{comment.body}}</p>
</mat-card-content>
<span class="fill-remaining-space"></span>
<button mat-icon-button [disabled]="isCreator" (click)="setCorrect(comment)" [matTooltip]="comment.correct ? 'Unmark as correct' : 'Mark as correct'">
<mat-icon [ngClass]="{'correct-icon' : comment.correct === true}">check_circle</mat-icon>
</button>
<button mat-icon-button [disabled]="isCreator" (click)="setFavorite(comment)" [matTooltip]="comment.favorite ? 'Mark as not favorite' : 'Mark as favorite'">
<mat-icon [ngClass]="{'favorite-icon' : comment.favorite === true}">star</mat-icon>
</button>
<button mat-icon-button [disabled]="isCreator" (click)="setRead(comment)" [matTooltip]="comment.read ? 'Mark as unread' : 'Mark as read'">
<mat-icon [ngClass]="{'read-icon' : comment.read === true}">visibility</mat-icon>
</button>
<div fxLayout="column">
<div fxLayout="row">
<span class="fill-remaining-space"></span>
<button mat-icon-button *ngIf="comment.correct || !isStudent" [disabled]="isStudent" (click)="setCorrect(comment)" [matTooltip]="comment.correct ? 'Unmark as correct' : 'Mark as correct'">
<mat-icon [ngClass]="{true : 'correct-icon', false: 'incorrect-icon'}[comment.correct]">check_circle</mat-icon>
</button>
<button mat-icon-button *ngIf="comment.favorite || !isStudent" [disabled]="isStudent" (click)="setFavorite(comment)" [matTooltip]="comment.favorite ? 'Mark as not favorite' : 'Mark as favorite'">
<mat-icon [ngClass]="{true: 'favorite-icon', false: 'not-favorite-icon'}[comment.favorite]">star</mat-icon>
</button>
<button mat-icon-button [disabled]="isStudent" (click)="setRead(comment)" [matTooltip]="comment.read ? 'Mark as unread' : 'Mark as read'">
<mat-icon [ngClass]="{true: 'read-icon', false: 'unread-icon'}[comment.read]">visibility</mat-icon>
</button>
</div>
<div fxLayout="row">
<div class="body" (click)="openPresentDialog(comment.body)">{{comment.body}}</div>
<span class="fill-remaining-space"></span>
<div fxLayout="column">
<button mat-icon-button [disabled]="!isStudent" (click)="voteUp(comment)">
<mat-icon class="voting-icon" [ngClass]="{'upvoted' : hasVoted === 1}">keyboard_arrow_up</mat-icon>
</button>
<h2>{{comment.score}}</h2>
<button mat-icon-button [disabled]="!isStudent" (click)="voteDown(comment)">
<mat-icon class="voting-icon" [ngClass]="{'downvoted' : hasVoted === -1}">keyboard_arrow_down</mat-icon>
</button>
</div>
</div>
</div>
</mat-card>
......@@ -2,6 +2,9 @@ mat-card {
margin-bottom: 20px;
background-color: #4dd0e1;
cursor: pointer;
padding-bottom: 10px;
padding-top: 10px;
padding-right: 5px;
}
mat-card-content > :first-child {
......@@ -14,25 +17,62 @@ mat-toolbar {
background-color: #bbdefb;
}
mat-icon {
color: white;
.voting-icon {
width: 35px;
height: 35px;
font-size: 35px;
line-height: 100%!important;
}
.upvoted {
color: #66bb6a;
}
.downvoted {
color: #FF8A65;
}
.incorrect-icon {
color: #c8e6c9;
}
.correct-icon {
color: green;
color: #66bb6a;
}
.read-icon {
color: blue;
color: #1e88e5;
}
.unread-icon {
color: #e3f2fd;
}
.favorite-icon {
color: #fdd835;
}
.proffesor-icon {
.not-favorite-icon {
color: #fffde7;
}
.proffessor-icon {
background-size: cover;
margin-right: 10px;
margin-top: 10px;
}
h2 {
text-align: center;
margin: 0;
}
.body {
min-width: 200px;
min-height: 100px;
text-align: start;
font-size: large;
max-height: 120px;
overflow: hidden;
text-overflow: ellipsis;
}
......@@ -18,8 +18,9 @@ import { MatDialog } from '@angular/material';
})
export class CommentComponent implements OnInit {
@Input() comment: Comment;
isCreator = false;
isStudent = false;
isLoading = true;
hasVoted = 0;
constructor(protected authenticationService: AuthenticationService,
private route: ActivatedRoute,
......@@ -34,7 +35,7 @@ export class CommentComponent implements OnInit {
ngOnInit() {
if (this.authenticationService.getRole() === 0) {
this.isCreator = true;
this.isStudent = true;
}
this.translateService.use(localStorage.getItem('currentLang'));
}
......@@ -51,6 +52,20 @@ export class CommentComponent implements OnInit {
this.comment = this.wsCommentService.toggleFavorite(comment);
}
voteUp(comment: Comment): void {
if (this.hasVoted !== 1) {
this.wsCommentService.voteUp(comment);
this.hasVoted = 1;
}
}
voteDown(comment: Comment): void {
if (this.hasVoted !== -1) {
this.wsCommentService.voteDown(comment);
this.hasVoted = -1;
}
}
delete(comment: Comment): void {
this.commentService.deleteComment(comment.id).subscribe(room => {
this.notification.show(`Comment '${comment.body}' successfully deleted.`);
......
......@@ -23,6 +23,7 @@ import { LoginComponent } from './login/login.component';
import { StatisticHelpComponent } from './_dialogs/statistic-help/statistic-help.component';
import { CommentComponent } from './comment/comment.component';
import { SubmitCommentComponent } from './_dialogs/submit-comment/submit-comment.component';
import { OrderBy } from './sort';
import { PresentCommentComponent } from './_dialogs/present-comment/present-comment.component';
@NgModule({
......@@ -54,6 +55,7 @@ import { PresentCommentComponent } from './_dialogs/present-comment/present-comm
StatisticHelpComponent,
CommentComponent,
SubmitCommentComponent,
OrderBy,
PresentCommentComponent
],
exports: [
......
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'orderBy'
})
export class OrderBy implements PipeTransform {
transform(array: Array<string>, args: string): Array<string> {
array.sort((a: any, b: any) => {
if (a[args] > b[args]) {
return -1;
} else if (a[args] <= b[args]) {
return 1;
}
});
return array;
}
}
export abstract class AbstractVote {
type: string;
payload: {
userId: string;
commentId: string;
vote: number;
};
protected constructor(type: string, userId: string, commentId: string, vote: number) {
this.type = type;
this.payload = {
userId: userId,
commentId: commentId,
vote: vote
};
}
}
import { AbstractVote } from './abstract-vote';
export class DownVote extends AbstractVote {
type: string;
payload: {
userId: string;
commentId: string;
vote: number;
};
constructor(userId: string, commentId: string) {
super('Downvote', userId, commentId, -1);
}
}
import { AbstractVote } from './abstract-vote';
export class UpVote extends AbstractVote {
type: string;
payload: {
userId: string;
commentId: string;
vote: number;
};
constructor(userId: string, commentId: string) {
super('Upvote', userId, commentId, 1);
}
}
export class Vote {
private id: string;
private userId: string;
private commentId: string;
private vote: number;
constructor(userId: string ,
commentId: string,
vote: number) {
this.id = '';
this.userId = userId;
this.commentId = commentId;
this.vote = vote;
}
}
......@@ -4,6 +4,8 @@ import { RxStompService } from '@stomp/ng2-stompjs';
import { CreateComment } from '../../models/messages/create-comment';
import { PatchComment } from '../../models/messages/patch-comment';
import { TSMap } from 'typescript-map';
import { UpVote } from '../../models/messages/up-vote';
import { DownVote } from '../../models/messages/down-vote';
@Injectable({
......@@ -49,9 +51,30 @@ export class WsCommentServiceService {
return comment;
}
voteUp(comment: Comment): void {
const message = new UpVote(comment.userId, comment.id);
this.rxStompService.publish({
destination: `/queue/vote.command.upvote`,
body: JSON.stringify(message),
headers: {
'content-type': 'application/json'
}
});
}
voteDown(comment: Comment): void {
const message = new DownVote(comment.userId, comment.id);
this.rxStompService.publish({
destination: `/queue/vote.command.downvote`,
body: JSON.stringify(message),
headers: {
'content-type': 'application/json'
}
});
}
private patchComment(comment: Comment, changes: TSMap<string, any>): void {
const message = new PatchComment(comment.id, changes);
console.log(message);
this.rxStompService.publish({
destination: `/queue/comment.command.patch`,
body: JSON.stringify(message),
......@@ -59,5 +82,5 @@ export class WsCommentServiceService {
'content-type': 'application/json'
}
});
}
}
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment