Merge branch 'master' into julius-dev

master
trivernis 4 years ago
commit 3d15e5a8d0

@ -6,30 +6,45 @@
<div [hidden]="!loggedIn">
<mat-card>
<mat-card-content>
<div id="inputPreviewWrapper">
<h2 *ngIf="localFileUrl">Preview:</h2>
<img *ngIf="fileType == 'image'" id="inputPreview" [src]="localFileUrl"/>
<video *ngIf="fileType == 'video'" [src]="localFileUrl" controls="" class="html5-video-player">
Your browser does not support playing HTML5 video.
</video>
<div id="inputPreviewWrapper" *ngIf="localFileUrl">
<h2>Preview:</h2>
<div id="media-box">
<button class="discard-button" mat-button mat-icon-button (click)="discardFile()"
matTooltip="discard file"
matTooltipShowDelay="200"
[disabled]="posting">
<mat-icon>close</mat-icon>
</button>
<img *ngIf="fileType == 'image'" id="inputPreview" [src]="localFileUrl"/>
<video *ngIf="fileType == 'video'" [src]="localFileUrl" id="inputPreview" controls="" class="html5-video-player">
Your browser does not support playing HTML5 video.
</video>
</div>
</div>
<mat-form-field id="input">
<textarea matInput #content type="text" (input)="onTextInputChange()" [(ngModel)]="textInputValue"
mat-autosize="true" matAutosizeMaxRows="3" placeholder="post something"></textarea>
<button mat-button matSuffix mat-icon-button (click)="name.click()"
<mat-form-field class="input">
<textarea matInput #content type="text" (input)="onTextInputChange()" [(ngModel)]="textInputValue" [disabled]="posting"
mat-autosize="true" matAutosizeMaxRows="3" placeholder="post something"></textarea>
</mat-form-field>
<input style="display: none" id="fileInput" type="file" accept="video/*,image/*" (change)="onFileInputChange($event)" #fileInput>
<div class="input">
<div class="left">
<p id="check">
<mat-checkbox color="primary" [(ngModel)]="checked" checked="checked" [disabled]="posting">I protected the environment.
</mat-checkbox>
</p>
</div>
<div class="right">
<button mat-button mat-icon-button (click)="fileInput.click()"
matTooltip="upload a picture or video (up to 10MB)"
matTooltipShowDelay="200">
matTooltipShowDelay="200"
[disabled]="posting">
<mat-icon>attach_file</mat-icon>
</button>
<input style="display: none" id="input-file" type="file" accept="video/*,image/*" (change)="onFileInputChange($event)" #name>
</mat-form-field>
<p id="check">
<mat-checkbox color="primary" [(ngModel)]="checked" checked="checked">I protected the environment.
</mat-checkbox>
</p>
</div>
</div>
<mat-form-field id="action-chooser" *ngIf="checked">
<mat-label>What did you do?</mat-label>
<mat-select [(ngModel)]="activityId" name="action">
<mat-select [(ngModel)]="activityId" name="action" [disabled]="posting">
<mat-option>nothing ;)</mat-option>
<mat-option *ngFor="let action of actionlist.Actions" [value]="action.id" [matTooltip]="action.description"
matTooltipShowDelay="200">
@ -38,7 +53,8 @@
</mat-select>
</mat-form-field>
<mat-error *ngIf="errorOccurred && textInputValue">{{getErrorMessage()}}</mat-error>
<button mat-raised-button *ngIf="textInputValue" color="primary" id="post-button"
<mat-progress-bar id="progress-bar" *ngIf="posting" mode="indeterminate"></mat-progress-bar>
<button mat-raised-button *ngIf="textInputValue" color="primary" id="post-button" [disabled]="posting"
(click)=createPost(content,activityId)>
POST
</button>

@ -27,23 +27,26 @@
#feedlist
width: 100%
#input
.input
width: 100%
padding-left: 0.5em
padding-right: 0.5em
.mat-icon
transform: scale(1.5)
.left, .right
display: inline-block
width: 50%
.left
text-align: left
.right
text-align: right
#inputPreview
max-width: 75%
max-height: 100%
max-width: 100%
max-height: 45vh
width: auto
border-radius: 4px
mask-mode: luminance
outline: none
user-select: none
::ng-deep video
width: 100%
max-height: 40vh
outline: none
user-select: none
#inputPreviewWrapper
@ -51,6 +54,19 @@
text-align: center
max-height: 512px
margin-bottom: 1em
#media-box
position: relative
width: 100%
::ng-deep .light-theme .discard-button
margin: 0.5em
position: absolute
background: hsla(255,100%,100%,0.3)
::ng-deep .dark-theme .discard-button
margin: 0.5em
position: absolute
background: hsla(0,0%,0%,0.3)
#progress-bar
margin-top: 1em
#action-chooser
width: 100%
@ -60,6 +76,10 @@
#check
margin: 0
padding-left: 0.5em
display: contents
#button-box
text-align: right
margin-left: auto
#post-button
width: 100%

@ -1,4 +1,4 @@
import {Component, OnInit} from '@angular/core';
import {Component, OnInit, ViewChild, ElementRef} from '@angular/core';
import {Post} from 'src/app/models/post';
import {FeedService, Sort} from 'src/app/services/feed/feed.service';
import {Activitylist} from 'src/app/models/activity';
@ -17,11 +17,13 @@ export class FeedComponent implements OnInit {
loadingNew = true;
loadingMostLiked = true;
// file upload variables
@ViewChild('fileInput', {static: false}) fileInput: ElementRef;
public uploading = false;
public profilePictureUrl: BehaviorSubject<string | null>;
private file;
fileType;
public localFileUrl;
posting = false;
checked = false; // if the "I protected the environment."-box is checked
view = 'new';
@ -61,6 +63,13 @@ export class FeedComponent implements OnInit {
this.feedService.postsAvailable.subscribe(available => {
this.loadingMostLiked = this.loadingNew = available;
});
this.feedService.posting.subscribe(posting => {
const temp = this.posting;
this.posting = posting;
if (temp !== this.posting && !this.posting) {
this.resetPostInput();
}
});
}
/**
@ -70,38 +79,41 @@ export class FeedComponent implements OnInit {
*/
createPost(postElement, activityId: string) {
if (postElement && activityId && this.checked) {
this.posting = true;
this.feedService.createPostActivity(postElement.value, activityId, this.file).subscribe(() => {
postElement.value = '';
this.textInputValue = '';
this.checked = false;
this.file = null;
this.localFileUrl = null;
this.fileType = null;
if (this.view !== 'new') {
this.showNew();
}
}, (error: IErrorResponse) => {
this.errorOccurred = true;
this.posting = false;
this.errorMessage = error.error.errors[0].message;
});
} else if (postElement) {
this.feedService.createPost(postElement.value, this.file).subscribe(() => {
postElement.value = '';
this.textInputValue = '';
this.checked = false;
this.file = null;
this.localFileUrl = null;
this.fileType = null;
if (this.view !== 'new') {
this.showNew();
}
this.posting = true;
this.feedService.createPost(postElement.value, this.file).subscribe((result) => {
}, (error: IErrorResponse) => {
console.log(error);
this.posting = false;
this.errorOccurred = true;
this.errorMessage = error.error.errors[0].message;
});
}
}
discardFile() {
this.file = null;
this.localFileUrl = null;
this.fileType = null;
this.fileInput.nativeElement.value = '';
}
resetPostInput() {
this.textInputValue = '';
this.checked = false;
this.discardFile();
if (this.view !== 'new') {
this.showNew();
}
}
onFileInputChange(event) {
this.errorOccurred = false;
this.errorMessage = '';
@ -119,7 +131,7 @@ export class FeedComponent implements OnInit {
}
/**
* Fetches the next posts when scrolled
* Fetches the next posts when scrolled down
*/
onScroll() {
this.feedService.getNextPosts();

@ -22,8 +22,11 @@
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<mat-spinner *ngIf="post.mediaLoading && post.mediaType === 'IMAGE'" style="margin:0 auto; margin-top: 2em;" diameter="50"></mat-spinner>
<div class="postMedia">
<img *ngIf="post.mediaType === 'IMAGE'" [src]="post.mediaUrl" alt="post image"/>
<div [hidden]="post.mediaLoading">
<img *ngIf="post.mediaType === 'IMAGE'" [src]="post.mediaUrl" (load)="onLoad(this.post)" alt="post image"/>
</div>
<video *ngIf="post.mediaType === 'VIDEO'" controls>
<source [src]="post.mediaUrl" type="video/webm">
</video>

@ -60,16 +60,19 @@ $mat-card-header-size: 40px !default
display: block
margin-left: auto
margin-right: auto
margin-bottom: 0.5em
::ng-deep video
width: 100%
max-height: 40vh
outline: none
user-select: none
margin-bottom: 0.5em
::ng-deep audio
width: 100%
max-height: 40vh
outline: none
user-select: none
margin-bottom: 0.5em
.mat-button
min-width: 32px !important

@ -49,6 +49,10 @@ export class PostlistComponent implements OnInit {
});
}
onLoad(post: Post) {
post.mediaLoading = false;
}
public showUserProfile(post: any) {
this.router.navigate(['profile/' + post.author.id]);
}

@ -54,7 +54,7 @@ $mat-card-header-size: 100px !default
#icon
display: none
position: absolute
z-index: 11
z-index: 100
color: white
$mat-card-header-size: 100px !default

@ -17,7 +17,7 @@
</button>
</mat-menu>
</div>
<div mat-card-avatar (click)="showGroupProfile(group)">
<div mat-card-avatar (click)="showFriendProfile(friend)">
<img class="profile-picture" [src]="friend.profilePicture"/>
</div>
<mat-card-title (click)="showFriendProfile(friend)">{{friend.name}}</mat-card-title>

@ -1,5 +1,6 @@
import {Author} from './author';
import {Activity} from './activity';
import { environment } from 'src/environments/environment';
export class Post {
id: number;
@ -12,6 +13,7 @@ export class Post {
deletable: boolean;
author: Author;
activity: Activity;
mediaLoading: boolean;
mediaUrl: string;
mediaType: 'VIDEO' | 'IMAGE';
@ -39,8 +41,9 @@ export class Post {
this.author = author;
this.activity = activity;
if (media) {
this.mediaUrl = media.url;
this.mediaUrl = environment.greenvironmentUrl + media.url;
this.mediaType = media.type;
this.mediaLoading = true;
}
}
}

@ -10,8 +10,8 @@ import {BaseService} from '../base.service';
import {formatDate} from '@angular/common';
import {IFileUploadResult} from '../../models/interfaces/IFileUploadResult';
const createPostGqlQuery = `mutation($content: String!) {
createPost(content: $content) {
const createPostGqlQuery = `mutation($content: String!, $type: PostType) {
createPost(content: $content, type: $type) {
id,
content,
htmlContent,
@ -34,8 +34,8 @@ const createPostGqlQuery = `mutation($content: String!) {
createdAt}
}`;
const createPostActivityGqlQuery = `mutation($content: String!, $id: ID) {
createPost(content: $content activityId: $id) {
const createPostActivityGqlQuery = `mutation($content: String!, $id: ID, $type: PostType) {
createPost(content: $content activityId: $id, type: $type) {
id,
content,
htmlContent,
@ -70,7 +70,7 @@ const downvotePostGqlQuery = `mutation($postId: ID!) {
}
}`;
const getPostGqlQuery = `query($first: Int, $offset: Int, $sort: SortType){
const getPostsGqlQuery = `query($first: Int, $offset: Int, $sort: SortType){
getPosts (first: $first, offset: $offset, sort: $sort) {
id,
content,
@ -109,6 +109,7 @@ export class FeedService extends BaseService {
}
public postsAvailable = new BehaviorSubject<boolean>(true);
public posting = new BehaviorSubject<boolean>(false);
public posts: BehaviorSubject<Post[]> = new BehaviorSubject([]);
private activePostList: Sort = Sort.NEW;
private offset = 0;
@ -122,7 +123,7 @@ export class FeedService extends BaseService {
*/
private static buildGetPostBody(sort: string, offset: number, first: number = 10) {
return {
query: getPostGqlQuery, variables: {
query: getPostsGqlQuery, variables: {
first,
offset,
sort
@ -145,10 +146,15 @@ export class FeedService extends BaseService {
* @param file
*/
public createPost(pContent: String, file?: File) {
let type: string;
if (file) { type = 'MEDIA'; } else {
type = 'TEXT';
}
const body = {
query: createPostGqlQuery,
variables: {
content: pContent
content: pContent,
type
}
};
return this.createPostRequest(body, file);
@ -161,10 +167,15 @@ export class FeedService extends BaseService {
* @param file
*/
public createPostActivity(pContent: String, activityId: String, file?: File) {
let type: string;
if (file) { type = 'MEDIA'; } else {
type = 'TEXT';
}
const body = {
query: createPostActivityGqlQuery, variables: {
content: pContent,
id: activityId
id: activityId,
type
}
};
return this.createPostRequest(body, file);
@ -176,26 +187,39 @@ export class FeedService extends BaseService {
* @param file - a file that is being uploaded with the post
*/
private createPostRequest(body: { variables: any; query: string }, file?: File) {
return this.postGraphql(body, null, 0)
this.posting.next(true);
if (file) {
return this.postGraphql(body, null, 0)
.pipe(tap(response => {
const updatedPosts = this.posts.getValue();
const post = this.constructPost(response);
this.uploadPostImage(post.id, file).subscribe((result) => {
if (this.activePostList === Sort.NEW) {
post.mediaUrl = result.fileName;
post.mediaType = result.fileName.endsWith('.png') ? 'IMAGE' : 'VIDEO';
updatedPosts.unshift(post);
this.posts.next(updatedPosts);
this.posting.next(false);
}
}, error => {
console.error(error);
this.posting.next(false);
this.deletePost(post.id);
});
}
));
} else if (!file) {
return this.postGraphql(body, null, 0)
.pipe(tap(response => {
this.posting.next(false);
const updatedPosts = this.posts.getValue();
if (this.activePostList === Sort.NEW) {
const updatedPosts = this.posts.getValue();
const post = this.constructPost(response);
updatedPosts.unshift(post);
if (file) {
this.uploadPostImage(post.id, file).subscribe((result) => {
post.mediaUrl = result.fileName;
post.mediaType = result.fileName.endsWith('.png') ? 'IMAGE' : 'VIDEO';
this.posts.next(updatedPosts);
}, error => {
console.error(error);
this.deletePost(post.id);
});
} else {
this.posts.next(updatedPosts);
}
this.posts.next(updatedPosts);
}
}));
}
}
/**
@ -266,7 +290,7 @@ export class FeedService extends BaseService {
{headers: this.headers})
.pipe(this.retryRated())
.subscribe(response => {
this.posts.next(this.constructAllPosts(response));
this.posts.next(this.constructAllPosts(response.data.getPosts));
this.activePostList = sort;
});
}
@ -280,7 +304,7 @@ export class FeedService extends BaseService {
this.http.post(environment.graphQLUrl, body, {headers: this.headers})
.pipe(this.retryRated())
.subscribe(response => {
const posts = this.constructAllPosts(response);
const posts = this.constructAllPosts(response.data.getPosts);
const previousPosts = this.posts.getValue();
for (const post of previousPosts.reverse()) {
if (!posts.find(p => p.id === post.id)) {
@ -337,7 +361,7 @@ export class FeedService extends BaseService {
public constructAllPosts(response: any): Post[] {
const posts = new Array<Post>();
for (const post of response.data.getPosts) {
for (const post of response) {
let profilePicture: string;
if (post.author.profilePicture) {
profilePicture = environment.greenvironmentUrl + post.author.profilePicture;

@ -7,6 +7,7 @@ import {User} from 'src/app/models/user';
import {Subject} from 'rxjs';
import {Activity} from 'src/app/models/activity';
import {BaseService} from '../base.service';
import {FeedService} from 'src/app/services/feed/feed.service';
const graphqlGetProfileQuery = `query($userId: ID) {
getUser(userId:$userId){
@ -30,6 +31,7 @@ const graphqlGetProfileQuery = `query($userId: ID) {
downvotes,
userVote,
deletable,
media {url, type},
activity{
id
name
@ -39,7 +41,7 @@ const graphqlGetProfileQuery = `query($userId: ID) {
author{
name,
handle,
profilePicture
profilePicture,
id},
createdAt
}
@ -51,7 +53,7 @@ const graphqlGetProfileQuery = `query($userId: ID) {
})
export class ProfileService extends BaseService {
constructor(http: HttpClient) {
constructor(http: HttpClient, private feedService: FeedService) {
super(http);
}
@ -96,38 +98,7 @@ export class ProfileService extends BaseService {
const temp = new Date(Number(response.data.getUser.joinedAt));
const date = temp.toLocaleString('en-GB');
profile.joinedAt = date;
for (const post of response.data.getUser.posts) {
const id: number = post.id;
const content: string = post.content;
const htmlContent: string = post.htmlContent;
const upvotes: number = post.upvotes;
const downvotes: number = post.downvotes;
const userVote: string = post.userVote;
const deletable: boolean = post.deletable;
let profilePicture: string;
if (post.author.profilePicture) {
profilePicture = environment.greenvironmentUrl + post.author.profilePicture;
} else {
profilePicture = 'assets/images/default-profilepic.svg';
}
const author = new Author(post.author.id, post.author.name, post.author.handle, profilePicture);
const ptemp = new Date(Number(post.createdAt));
const pdate = ptemp.toLocaleString('en-GB');
let activity: Activity;
if (post.activity) {
activity = new Activity(
post.activity.id,
post.activity.name,
post.activity.description,
post.activity.points);
} else {
activity = null;
}
// tslint:disable-next-line: max-line-length
posts.push(new Post(id, content, htmlContent, upvotes, downvotes, userVote, deletable, pdate, author, activity));
}
profile.posts = posts;
profile.posts = this.feedService.constructAllPosts(response.data.getUser.posts);
return profile;
}
return null;

Loading…
Cancel
Save