aboutsummaryrefslogtreecommitdiff
path: root/src/app/components/post
diff options
context:
space:
mode:
authorKamen Mladenov <kamen.d.mladenov@protonmail.com>2021-04-09 19:55:59 +0300
committerGitHub <noreply@github.com>2021-04-09 19:55:59 +0300
commitf849e37ccdd6fd48f83119a3b3b65cdd8b765dc3 (patch)
tree83b88a773bb7dc053bb3aced35bce302264ec925 /src/app/components/post
parentbcd88af53b1a920d728ec98b45daa9ac2e2c0917 (diff)
parentc13889759d70687de6833c505221c203f65fedb8 (diff)
downloadDevHive-Angular-0.2.tar
DevHive-Angular-0.2.tar.gz
DevHive-Angular-0.2.zip
Merge pull request #7 from Team-Kaleidoscope/devHEADv0.2main
Second Stage: Complete
Diffstat (limited to 'src/app/components/post')
-rw-r--r--src/app/components/post/post.component.css127
-rw-r--r--src/app/components/post/post.component.html121
-rw-r--r--src/app/components/post/post.component.ts210
3 files changed, 302 insertions, 156 deletions
diff --git a/src/app/components/post/post.component.css b/src/app/components/post/post.component.css
index 1b88c7d..1015564 100644
--- a/src/app/components/post/post.component.css
+++ b/src/app/components/post/post.component.css
@@ -1,134 +1,55 @@
-.post {
- display: flex;
- width: 98%;
-
- margin: .5em auto;
+.left-pane {
box-sizing: border-box;
- padding: .5em;
- background-color: var(--card-bg);
- position: relative;
-}
-
-.post:first-child {
- margin-top: 0;
-}
-
-hr {
- border: 1px solid black;
- width: 90%;
}
/* Author */
-.author {
- display: flex;
- margin-bottom: .2em;
-}
-
-.author:hover {
- cursor: pointer;
-}
-
-.author > img {
+.author-picture {
width: 2.2em;
height: 2.2em;
- margin-right: .2em;
-}
-
-.author-info > .handle {
- font-size: .9em;
- color: gray;
}
/* Content */
.content {
- flex: 1;
+ padding: 0 var(--card-padding);
}
.message {
- margin: .3em 0;
- word-break: break-all;
-}
-
-.bottom-post {
- font-size: .5em;
- color: gray;
- display: flex;
- align-items: center;
-}
-
-.separator {
- margin: 0 .5em;
-}
-
-.comment-count {
- font-size: 1em;
+ word-break: break-word;
}
-.comment-count > img {
- height: .8em;
+.post-details {
+ margin-bottom: calc(var(--card-padding) / 1.5);
+ padding: 0.2em 0;
}
-.message:hover, .timestamp:hover {
- cursor: pointer;
+.post-details > * {
+ margin-left: 1.1em;
}
-/* Rating */
-
-/* Temporary, until ratings are implemented fully */
-.rating {
- display: none !important;
+.rating img {
+ height: 1.2em;
+ width: 1.2em;
}
-.rating {
- display: flex;
- flex-direction: column;
- align-items: center;
- min-height: 4.4em;
- margin: auto -.1em auto 0;
-}
+/* Edit */
-.score {
- flex: 1;
- display: flex;
- align-items: center;
+#attachments-btns img, .file-button > input {
+ height: 1.4em;
+ width: 1.4em;
}
-
-.vote {
- display: flex;
- align-items: center;
- flex: 1;
-
- background: var(--card-bg);
- font-size: 1em;
-
- border: 1px solid var(--card-bg);
- box-sizing: border-box;
- border-radius: .2em;
-
- }
-
-.vote:hover {
- border: 1px solid var(--focus-color);
- color: var(--focus-color);
- cursor: pointer;
-}
-
-/* Attachments */
-
-.attachments {
- display: flex;
- width: 98%;
- margin: -.3em auto .5em auto;
- flex-wrap: wrap;
+.file-button {
+ position: relative;
}
-.attachments:empty {
- display: none;
+.file-button > img {
+ position: absolute;
+ pointer-events: none;
}
-.attachments > * {
- flex: 1;
+.file-button > input {
+ font-size: inherit;
+ color: transparent;
}
diff --git a/src/app/components/post/post.component.html b/src/app/components/post/post.component.html
index bc0d84a..0c1cce2 100644
--- a/src/app/components/post/post.component.html
+++ b/src/app/components/post/post.component.html
@@ -1,49 +1,88 @@
<app-loading *ngIf="!loaded"></app-loading>
-<div class="post rounded-border" *ngIf="loaded">
- <div class="content">
- <div class="author" (click)="goToAuthorProfile()">
- <img class="round-image" [src]="user.profilePictureURL">
- <div class="author-info">
- <div class="name">
- {{ user.firstName }} {{ user.lastName }}
+<section class="card flex-row" [hidden]="loaded" (mouseleave)="resetShareBtn()">
+ <aside class="left-pane">
+ <img class="author-picture round-image hover-half-opacity" [src]="user.profilePictureURL" (click)="goToAuthorProfile()">
+ </aside>
+ <main class="content flexible">
+ <summary class="font-size-dot8 text-vertical-middle hover-half-opacity" (click)="goToAuthorProfile()">
+ <span>
+ {{ user.firstName }} {{ user.lastName }}
+ </span>
+ <span class="fg-faded">
+ @{{ user.userName }}
+ </span>
+ </summary>
+ <article class="message margin-top-bot-dot2" *ngIf="!editingPost">
+ {{ post.message }}
+ </article>
+ <section class="flex-row flexible-children" *ngIf="!editingPost">
+ <figure *ngFor="let fileURL of post.fileURLs">
+ <app-post-attachment [paramURL]="fileURL"></app-post-attachment>
+ </figure>
+ </section>
+ <form [formGroup]="editPostFormGroup" *ngIf="editingPost">
+ <textarea class="textarea-new-msg width-full border-faded-slim border-bottom-only padding-dot2" rows="1" formControlName="newPostMessage" placeholder="What's on your mind?"></textarea>
+ <section class="flex-row flex-justify-start align-children-center top-bot-padding-dot6ger">
+ <div class="file-button hover-half-opacity click-effect">
+ <img src="/assets/icons/tabler-icon-paperclip.svg">
+ <input type="file" formControlName="fileUpload" (change)="onFileUpload($event)" multiple>
</div>
- <div class="handle">
- @{{ user.userName }}
+ </section>
+ </form>
+ <section class="flex-row bot-padding-dot6ger" *ngIf="editingPost">
+ <div *ngFor="let file of files" class="form-attachment border-faded-slim flexible flex-row flex-no-wrap flex-center-align-items padding-dot2 margin-top-bot-dot2">
+ <div class="flexible">
+ {{ file.name ? file.name : 'Attachment' }}
+ </div>
+ <div class="flex-col hover-half-opacity border-radius-dot2 click-effect" (click)="removeAttachment(file.name)">
+ <img src="/assets/icons/tabler-icon-x.svg">
</div>
</div>
- </div>
- <div class="message" (click)="goToPostPage()">
- {{ post.message }}
- </div>
- <div class="bottom-post" (click)="goToPostPage()">
- <div class="timestamp">
- {{ timeCreated }}
- </div>
- <div class="separator">
- ║
- </div>
- <div class="comment-count">
- {{ post.comments.length }}
- <img src="assets/images/comment.png">
- </div>
- </div>
-
- </div>
- <div class="rating">
- <button class="vote">
- ᐃ
+ </section>
+ <button class="border-faded-slim width-full padding-dot2 lighter-hover click-effect border-radius-dot3 margin-bot-dot5" *ngIf="editingPost" (click)="editPost()">
+ Update Post
+ </button>
+ <section class="post-details flex-row flex-justify-end font-size-dot7 border-faded-slim border-bottom-only">
+ <time class="flex-row flex-center-align-items">
+ <img class="height-font" src="/assets/icons/tabler-icon-calendar-time.svg">&nbsp;
+ <span>
+ {{ timeCreated }}
+ </span>
+ </time>
+ <summary class="flex-row flex-center-align-items">
+ <img class="height-font" src="/assets/icons/tabler-icon-message-2.svg">&nbsp;
+ <span>
+ {{ post.comments.length }}
+ </span>
+ </summary>
+ </section>
+ <section class="flex-row justify-children-center align-children-center">
+ <button class="padding-dot2 lighter-hover click-effect border-radius-dot3" *ngIf="loggedInAuthor" (click)="toggleEditing()">
+ <img src="/assets/icons/tabler-icon-edit.svg">
+ </button>
+ <button class="flexible padding-dot2 lighter-hover click-effect border-radius-dot3" (click)="goToPostPage()">
+ <img src="/assets/icons/tabler-icon-message-2.svg">
+ &nbsp;Comment
+ </button>
+ <button #share ngxClipboard [cbContent]="getPostLink()" class="flexible padding-dot2 lighter-hover click-effect border-radius-dot3" (click)="showCopiedMessage()">
+ <img src="/assets/icons/tabler-icon-link.svg">
+ &nbsp;Share
+ </button>
+ <button class="padding-dot2 lighter-hover click-effect border-radius-dot3" *ngIf="loggedInAuthor" (click)="deletePost()">
+ <img src="/assets/icons/tabler-icon-trash.svg">
+ </button>
+ </section>
+ </main>
+ <aside class="rating flex-col flex-center-align-items">
+ <button #upvote class="flex-col lighter-hover border-radius-dot2 click-effect" (click)="votePost(true)">
+ <img src="/assets/icons/tabler-icon-chevron-up.svg">
</button>
- <div class="score">
+ <summary class="top-bot-padding-dot2">
{{ votesNumber }}
- </div>
- <button class="vote">
- ᐁ
+ </summary>
+ <button #downvote class="flex-col lighter-hover border-radius-dot2 click-effect" (click)="votePost(false)">
+ <img src="/assets/icons/tabler-icon-chevron-down.svg">
</button>
- </div>
-</div>
-<div class="attachments">
- <div *ngFor="let fileURL of post.fileURLs">
- <app-post-attachment class="no-events" [paramURL]="fileURL"></app-post-attachment>
- </div>
-</div>
+ </aside>
+</section>
diff --git a/src/app/components/post/post.component.ts b/src/app/components/post/post.component.ts
index 387f56f..e8ba430 100644
--- a/src/app/components/post/post.component.ts
+++ b/src/app/components/post/post.component.ts
@@ -1,50 +1,123 @@
-import { Component, Input, OnInit } from '@angular/core';
+import { AfterViewInit, Component, ElementRef, Input, OnInit, Renderer2, ViewChild } from '@angular/core';
+import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
import { Router } from '@angular/router';
import { Guid } from 'guid-typescript';
+import { CloudinaryService } from 'src/app/services/cloudinary.service';
import { PostService } from 'src/app/services/post.service';
+import { RatingService } from 'src/app/services/rating.service';
import { UserService } from 'src/app/services/user.service';
-import { User } from 'src/models/identity/user';
-import { Post } from 'src/models/post';
+import { User } from 'src/models/identity/user.model';
+import { Post } from 'src/models/post.model';
+import { TokenService } from '../../services/token.service';
@Component({
selector: 'app-post',
templateUrl: './post.component.html',
styleUrls: ['./post.component.css'],
})
-export class PostComponent implements OnInit {
+export class PostComponent implements OnInit, AfterViewInit {
public loaded = false;
public user: User;
public post: Post;
public votesNumber: number;
public timeCreated: string;
@Input() paramId: string;
+ @ViewChild('upvote') upvoteBtn: ElementRef;
+ @ViewChild('downvote') downvoteBtn: ElementRef;
+ @ViewChild('share') shareBtn: ElementRef;
+ private _defaultShareBtnInnerHTML: string;
+ private _linkShared = false; // For optimisation purposes
+ public loggedIn = false;
+ public loggedInAuthor = false;
+ public editingPost = false;
+ public files: File[];
+ public editPostFormGroup: FormGroup;
- constructor(private _postService: PostService, private _userService: UserService, private _router: Router)
+ constructor(private _postService: PostService, private _ratingServe: RatingService, private _userService: UserService, private _router: Router, private _tokenService: TokenService, private _cloudinaryService: CloudinaryService, private _fb: FormBuilder, private _elem: ElementRef, private _renderer: Renderer2)
{ }
ngOnInit(): void {
+ this.loggedIn = this._tokenService.getTokenFromSessionStorage() !== '';
+
this.post = this._postService.getDefaultPost();
this.user = this._userService.getDefaultUser();
+ this.files = [];
- this._postService.getPostRequest(Guid.parse(this.paramId)).subscribe(
- (result: object) => {
+ this._postService.getPostRequest(Guid.parse(this.paramId)).subscribe({
+ next: (result: object) => {
Object.assign(this.post, result);
+
this.post.fileURLs = Object.values(result)[7];
- this.votesNumber = 23;
+ this.votesNumber = this.post.currentRating;
this.timeCreated = new Date(this.post.timeCreated).toLocaleString('en-GB');
+
this.loadUser();
}
- );
+ });
+
+ this.editPostFormGroup = this._fb.group({
+ newPostMessage: new FormControl(''),
+ fileUpload: new FormControl('')
+ });
}
private loadUser(): void {
- this._userService.getUserByUsernameRequest(this.post.creatorUsername).subscribe(
- (result: object) => {
+ this._userService.getUserByUsernameRequest(this.post.creatorUsername).subscribe({
+ next: (result: object) => {
Object.assign(this.user, result);
+
+ if (this.loggedIn) {
+ this.loggedInAuthor = this._tokenService.getUsernameFromSessionStorageToken() === this.post.creatorUsername;
+ this.editPostFormGroup.get('newPostMessage')?.setValue(this.post.message);
+
+ if (this.post.fileURLs.length > 0) {
+ this.loadFiles();
+ return;
+ }
+ }
+
this.loaded = true;
}
- );
+ });
+ }
+
+ private loadFiles(): void {
+ for (const fileURL of this.post.fileURLs) {
+ this._cloudinaryService.getFileRequest(fileURL).subscribe({
+ next: (result: object) => {
+ const file = result as File;
+ const tmp = {
+ name: fileURL.match('(?<=\/)(?:.(?!\/))+$')?.pop() ?? 'Attachment'
+ };
+
+ Object.assign(file, tmp);
+ this.files.push(file);
+
+ if (this.files.length === this.post.fileURLs.length) {
+ this.loaded = true;
+ }
+ }
+ });
+ }
+ }
+
+ ngAfterViewInit(): void {
+ if (this.loggedIn) {
+ this._ratingServe.getRatingByUserAndPostWithSessionStorageRequest(Guid.parse(this.paramId)).subscribe({
+ next: (x: object) => {
+ if (!x) {
+ return;
+ }
+
+ const isLike: boolean = Object.values(x)[3];
+
+ this.changeColorOfVoteButton(isLike, !isLike);
+ }
+ });
+ }
+
+ this._defaultShareBtnInnerHTML = this.shareBtn.nativeElement.innerHTML;
}
goToAuthorProfile(): void {
@@ -54,4 +127,117 @@ export class PostComponent implements OnInit {
goToPostPage(): void {
this._router.navigate(['/post/' + this.post.postId]);
}
+
+ toggleEditing(): void {
+ this.editingPost = !this.editingPost;
+ }
+
+ onFileUpload(event: any): void {
+ this.files.push(...event.target.files);
+ this.editPostFormGroup.get('fileUpload')?.reset();
+ }
+
+ removeAttachment(fileName: string): void {
+ this.files = this.files.filter(x => x.name !== fileName);
+ }
+
+ editPost(): void {
+ const newMessage = this.editPostFormGroup.get('newPostMessage')?.value;
+
+ if (newMessage !== '') {
+ this._postService.putPostWithSessionStorageRequest(Guid.parse(this.paramId), newMessage, this.files).subscribe({
+ next: () => {
+ this.reloadPage();
+ }
+ });
+ this.loaded = false;
+ }
+ }
+
+ deletePost(): void {
+ this._postService.deletePostWithSessionStorage(Guid.parse(this.paramId)).subscribe({
+ next: () => {
+ this._router.navigate(['/profile/' + this._tokenService.getUsernameFromSessionStorageToken()]);
+ }
+ });
+ }
+
+ private reloadPage(): void {
+ this._router.routeReuseStrategy.shouldReuseRoute = () => false;
+ this._router.onSameUrlNavigation = 'reload';
+ this._router.navigate([this._router.url]);
+ }
+
+ votePost(isLike: boolean): void {
+ if (!this.loggedIn) {
+ this._router.navigate(['/login']);
+ return;
+ }
+
+ this._ratingServe.getRatingByUserAndPostWithSessionStorageRequest(Guid.parse(this.paramId)).subscribe({
+ next: (x: object) => {
+ if (x == null) { // checks if result is null or undefined
+ this.createRating(isLike);
+
+ this.changeColorOfVoteButton(isLike, !isLike);
+ }
+ else if (Object.values(x)[3] === isLike) {
+ this.deleteRating(Object.values(x)[0], isLike);
+
+ this.changeColorOfVoteButton(false, false);
+ }
+ else {
+ this.putRating(isLike);
+
+ this.changeColorOfVoteButton(isLike, !isLike);
+ }
+ }
+ });
+ }
+
+ private createRating(isLike: boolean): void {
+ this._ratingServe.createRatingWithSessionStorageRequest(Guid.parse(this.paramId), isLike).subscribe({
+ next: () => {
+ this.votesNumber += -1 + Number(isLike) * 2;
+ }
+ });
+ }
+
+ private putRating(isLike: boolean): void {
+ this._ratingServe.putRatingWithSessionStorageRequest(Guid.parse(this.paramId), isLike).subscribe({
+ next: () => {
+ // when false -2 + 0 wjen true -2 + 4
+ this.votesNumber += -2 + Number(isLike) * 4;
+ }
+ });
+ }
+
+ private deleteRating(ratingId: string, isLike: boolean): void {
+ this._ratingServe.deleteRatingFromSessionStorageRequest(Guid.parse(ratingId)).subscribe({
+ next: () => {
+ this.votesNumber += 1 - Number(isLike) * 2;
+ }
+ });
+ }
+
+ private changeColorOfVoteButton(isUpvoted: boolean, isDownvoted: boolean): void {
+ this._renderer.setStyle(this.upvoteBtn.nativeElement, 'backgroundColor', (isUpvoted) ? 'var(--upvote-highlight)' : 'inherit');
+ this._renderer.setStyle(this.downvoteBtn.nativeElement, 'backgroundColor', (isDownvoted) ? 'var(--downvote-highlight)' : 'inherit');
+ }
+
+ resetShareBtn(): void {
+ if (this._linkShared) {
+ this._renderer.setProperty(this.shareBtn.nativeElement, 'innerHTML', this._defaultShareBtnInnerHTML);
+ this._linkShared = false;
+ }
+ }
+
+ showCopiedMessage(): void {
+ this._renderer.setProperty(this.shareBtn.nativeElement, 'innerHTML', 'Link copied to clipboard!');
+ this._linkShared = true;
+ }
+
+ getPostLink(): string {
+ return location.origin + '/post/' + this.paramId;
+ }
}