Merge branch 'master' of Software_Engineering_I/greenvironment-frontend into max_dev

master
Max_ES 5 years ago committed by Gitea
commit ccaf90b82a

@ -29,7 +29,8 @@
], ],
"styles": [ "styles": [
"src/styles/greenvironment-material-theme.scss", "src/styles/greenvironment-material-theme.scss",
"src/styles.sass" "src/styles.sass",
"./node_modules/ngx-lightbox/lightbox.css"
], ],
"scripts": [] "scripts": []
}, },

@ -21,6 +21,7 @@
"resources": { "resources": {
"files": [ "files": [
"/assets/**", "/assets/**",
"/data/**",
"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)" "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
] ]
} }

29
package-lock.json generated

@ -3136,9 +3136,9 @@
} }
}, },
"abbrev": { "abbrev": {
"version": "1.0.9", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=" "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
}, },
"accepts": { "accepts": {
"version": "1.3.5", "version": "1.3.5",
@ -6931,9 +6931,9 @@
} }
}, },
"globule": { "globule": {
"version": "1.2.1", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.0.tgz",
"integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", "integrity": "sha512-YlD4kdMqRCQHrhVdonet4TdRtv1/sZKepvoxNT4Nrhrp5HI8XFfc8kFlGlBn2myBo80aGp8Eft259mbcUJhgSg==",
"requires": { "requires": {
"glob": "~7.1.1", "glob": "~7.1.1",
"lodash": "~4.17.10", "lodash": "~4.17.10",
@ -9104,6 +9104,11 @@
"opencollective-postinstall": "^2.0.2" "opencollective-postinstall": "^2.0.2"
} }
}, },
"ngx-lightbox": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ngx-lightbox/-/ngx-lightbox-2.1.1.tgz",
"integrity": "sha512-mI1hUo/DrhcTeWi/7AVusKfzLr5ySz+EFrOksNlCEiaQKVMqCZdUgj+7ruKDROF7dZcNkJL9Wf99yyiG2nhezQ=="
},
"ngx-socket-io": { "ngx-socket-io": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/ngx-socket-io/-/ngx-socket-io-2.1.1.tgz", "resolved": "https://registry.npmjs.org/ngx-socket-io/-/ngx-socket-io-2.1.1.tgz",
@ -9223,9 +9228,9 @@
} }
}, },
"node-sass": { "node-sass": {
"version": "4.13.0", "version": "4.13.1",
"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.13.0.tgz", "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.13.1.tgz",
"integrity": "sha512-W1XBrvoJ1dy7VsvTAS5q1V45lREbTlZQqFbiHb3R3OTTCma0XBtuG6xZ6Z4506nR4lmHPTqVRwxT6KgtWC97CA==", "integrity": "sha512-TTWFx+ZhyDx1Biiez2nB0L3YrCZ/8oHagaDalbuBSlqXgUPsdkUSzJsVxeDO9LtPB49+Fh3WQl3slABo6AotNw==",
"requires": { "requires": {
"async-foreach": "^0.1.3", "async-foreach": "^0.1.3",
"chalk": "^1.1.1", "chalk": "^1.1.1",
@ -10944,9 +10949,9 @@
} }
}, },
"rxjs": { "rxjs": {
"version": "6.3.3", "version": "6.5.4",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz",
"integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==",
"requires": { "requires": {
"tslib": "^1.9.0" "tslib": "^1.9.0"
} }

@ -35,9 +35,10 @@
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
"js-sha512": "^0.8.0", "js-sha512": "^0.8.0",
"ngx-infinite-scroll": "^8.0.1", "ngx-infinite-scroll": "^8.0.1",
"ngx-lightbox": "^2.1.1",
"ngx-socket-io": "^2.1.1", "ngx-socket-io": "^2.1.1",
"node-sass": "^4.13.0", "node-sass": "^4.13.1",
"rxjs": "~6.3.3", "rxjs": "~6.5.4",
"ts-md5": "^1.2.7", "ts-md5": "^1.2.7",
"zone.js": "^0.8.29" "zone.js": "^0.8.29"
}, },

@ -11,10 +11,10 @@ $scrollbar-color-dark: transparent
$scrollbar-thumb-color-dark: #aaa $scrollbar-thumb-color-dark: #aaa
::ng-deep body ::ng-deep body
scrollbar-color: $scrollbar-color $scrollbar-thumb-color scrollbar-color: $scrollbar-thumb-color $scrollbar-color
scrollbar-width: thin scrollbar-width: thin
.dark-theme .dark-theme
scrollbar-color: $scrollbar-color-dark $scrollbar-thumb-color-dark scrollbar-color: $scrollbar-thumb-color-dark $scrollbar-color-dark
::ng-deep ::-webkit-scrollbar ::ng-deep ::-webkit-scrollbar
width: 4px width: 4px

@ -21,11 +21,4 @@ describe('AppComponent', () => {
const app = fixture.debugElement.componentInstance; const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('greenvironment'); expect(app.title).toEqual('greenvironment');
}); });
it('should render title in a h1 tag', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to greenvironment!');
});
}); });

@ -63,6 +63,7 @@ import {MatNativeDateModule, MatProgressBarModule} from '@angular/material/';
import {MatSnackBarModule} from '@angular/material/snack-bar'; import {MatSnackBarModule} from '@angular/material/snack-bar';
import { ServiceWorkerModule } from '@angular/service-worker'; import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from '../environments/environment'; import { environment } from '../environments/environment';
import {LightboxModule} from 'ngx-lightbox';
const config: SocketIoConfig = {url: 'http://localhost:4444', options: {}}; const config: SocketIoConfig = {url: 'http://localhost:4444', options: {}};
@ -147,6 +148,7 @@ const appRoutes: Routes = [
MatDatepickerModule, MatDatepickerModule,
MatSnackBarModule, MatSnackBarModule,
MatProgressBarModule, MatProgressBarModule,
LightboxModule,
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }), ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
], ],
entryComponents: [ entryComponents: [

@ -1,6 +1,6 @@
<mat-card class="post" *ngFor="let post of childPostList" [class.selected]="post === selectedPost" tabindex="0"> <mat-card class="post" *ngFor="let post of childPostList" [class.selected]="post === selectedPost" tabindex="0">
<mat-card-header> <mat-card-header>
<div mat-card-avatar> <div mat-card-avatar (click)="showUserProfile(this.post)">
<img class="profile-picture" [src]="post.author.profilePicture"/> <img class="profile-picture" [src]="post.author.profilePicture"/>
</div> </div>
<div id="button-box"> <div id="button-box">
@ -14,7 +14,7 @@
</mat-menu> </mat-menu>
</div> </div>
<mat-card-title> <mat-card-title>
{{post.author.name}} <span class="postlistUsername" (click)="showUserProfile(this.post)">{{post.author.name}}</span>
<a class="mat-card-subtitle" (click)="showUserProfile(this.post)">@{{post.author.handle}}</a> <a class="mat-card-subtitle" (click)="showUserProfile(this.post)">@{{post.author.handle}}</a>
<p class="mat-card-subtitle">&nbsp; {{post.date}}</p> <p class="mat-card-subtitle">&nbsp; {{post.date}}</p>
</mat-card-title> </mat-card-title>
@ -25,17 +25,24 @@
<p [innerHTML]="post.htmlContent"></p> <p [innerHTML]="post.htmlContent"></p>
</mat-card-content> </mat-card-content>
<mat-card-actions> <mat-card-actions>
<button mat-button (click)="voteUp(post)" matTooltip="vote up" matTooltipShowDelay="500"> <div *ngIf="post.activity" class="activity-info" fxHide.gt-sm="true">
<mat-icon class="voteButton voted" aria-hidden="false" color="primary" *ngIf="post.userVote == 'UPVOTE'">thumb_up</mat-icon> <span class="span" [matTooltip]="post.activity.description" matTooltipShowDelay="200">
<mat-icon class="voteButton" aria-hidden="false" *ngIf="!post.userVote || post.userVote == 'DOWNVOTE'">thumb_up</mat-icon> {{post.activity.points}} points earned through <b>{{post.activity.name}}</b>
</button> </span>
{{post.upvotes}} </div>
<button mat-button (click)="voteDown(post)" matTooltip="vote down" matTooltipShowDelay="500"> <div class="postVoteButtons">
<mat-icon class="voteButton voted" aria-hidden="false" color="primary" *ngIf="post.userVote == 'DOWNVOTE'">thumb_down</mat-icon> <button mat-button (click)="voteUp(post)" matTooltip="vote up" matTooltipShowDelay="500">
<mat-icon class="voteButton" aria-hidden="false" *ngIf="!post.userVote || post.userVote == 'UPVOTE'">thumb_down</mat-icon> <mat-icon class="voteButton voted" aria-hidden="false" color="primary" *ngIf="post.userVote == 'UPVOTE'">thumb_up</mat-icon>
</button> <mat-icon class="voteButton" aria-hidden="false" *ngIf="!post.userVote || post.userVote == 'DOWNVOTE'">thumb_up</mat-icon>
{{post.downvotes}} </button>
<div *ngIf="post.activity" class="activity-info"> <div class="voteCount">{{post.upvotes}}</div>
<button mat-button (click)="voteDown(post)" matTooltip="vote down" matTooltipShowDelay="500">
<mat-icon class="voteButton voted" aria-hidden="false" color="primary" *ngIf="post.userVote == 'DOWNVOTE'">thumb_down</mat-icon>
<mat-icon class="voteButton" aria-hidden="false" *ngIf="!post.userVote || post.userVote == 'UPVOTE'">thumb_down</mat-icon>
</button>
<div class="voteCount">{{post.downvotes}}</div>
</div>
<div *ngIf="post.activity" class="activity-info" fxHide.lt-md="true">
<span class="span" [matTooltip]="post.activity.description" matTooltipShowDelay="200"> <span class="span" [matTooltip]="post.activity.description" matTooltipShowDelay="200">
{{post.activity.points}} points earned through <b>{{post.activity.name}}</b> {{post.activity.points}} points earned through <b>{{post.activity.name}}</b>
</span> </span>

@ -17,7 +17,7 @@ $mat-card-header-size: 40px !default
.mat-card-content .mat-card-content
overflow: auto overflow: auto
margin: 0 1em margin: 0.5em 1em 0
::ng-deep a ::ng-deep a
color: $primary-color color: $primary-color
.mat-card-actions .mat-card-actions
@ -26,8 +26,14 @@ $mat-card-header-size: 40px !default
::ng-deep p ::ng-deep p
margin: 0 margin: 0
a:hover .postlistUsername, a
cursor: pointer cursor: pointer
margin-right: 0.4em
&:hover
text-decoration: underline
.postVoteButtons
display: inline-block
#button-box #button-box
text-align: right text-align: right
@ -61,6 +67,7 @@ $mat-card-header-size: 40px !default
border-radius: 50% border-radius: 50%
width: $mat-card-header-size width: $mat-card-header-size
height: $mat-card-header-size height: $mat-card-header-size
cursor: pointer
.activity-info .activity-info
display: contents display: contents
@ -74,4 +81,28 @@ $mat-card-header-size: 40px !default
&.voted &.voted
scale: 0.9 scale: 0.9
.voteCount
display: inline
@media (max-width: 959px)
.activity-info > .span
margin-left: 1em
width: calc(100% - 1em)
display: block
text-align: left
.postVoteButtons
text-align: end
margin-right: 0.5em
display: flex
align-items: center
.voteButton
scale: 1
&.voted
scale: 1.1
.mat-card-actions
display: flex
justify-content: end
align-items: center

@ -3,11 +3,11 @@
<!--on small screen--> <!--on small screen-->
<mat-toolbar id="toolbar" fxShow="true" fxHide.gt-sm="true"> <mat-toolbar id="toolbar" fxShow="true" fxHide.gt-sm="true">
<mat-toolbar-row> <mat-toolbar-row>
<div class="hover-box" matTooltip="upload new picture" *ngIf="ownProfile" (click)="openFileUploadDialog()"> <div class="hover-box profile-picture-container" matTooltip="upload new picture" *ngIf="ownProfile" (click)="openFileUploadDialog()">
<img class="profile-picture" [src]="userProfile.profilePicture"/> <img class="profile-picture" [src]="userProfile.profilePicture"/>
<mat-icon id="icon">camera_alt</mat-icon> <mat-icon id="icon">camera_alt</mat-icon>
</div> </div>
<div *ngIf="!ownProfile"> <div class="profile-picture-container" *ngIf="!ownProfile" (click)="openPfpLightbox()">
<img class="profile-picture" [src]="userProfile.profilePicture"/> <img class="profile-picture" [src]="userProfile.profilePicture"/>
</div> </div>
@ -44,12 +44,12 @@
<!--on big screen--> <!--on big screen-->
<mat-toolbar id="toolbar" fxShow="true" fxHide.lt-md="true"> <mat-toolbar id="toolbar" fxShow="true" fxHide.lt-md="true">
<mat-toolbar-row> <mat-toolbar-row>
<div class="hover-box" matTooltip="upload new picture" *ngIf="ownProfile" (click)="openFileUploadDialog()"> <div class="hover-box profile-picture-container" matTooltip="upload new picture" *ngIf="ownProfile" (click)="openFileUploadDialog()">
<img class="profile-picture" [src]="userProfile.profilePicture"/> <img class="profile-picture" [src]="userProfile.profilePicture"/>
<mat-icon id="icon">camera_alt</mat-icon> <mat-icon id="icon">camera_alt</mat-icon>
</div> </div>
<div *ngIf="!ownProfile"> <div class="profile-picture-container" *ngIf="!ownProfile" (click)="openPfpLightbox()">
<img class="profile-picture" [src]="userProfile.profilePicture"/> <img class="profile-picture" [src]="userProfile.profilePicture" />
</div> </div>
<span id="username">{{userProfile.username}}</span> <span id="username">{{userProfile.username}}</span>
<span id="handle">@{{userProfile.handle}}</span> <span id="handle">@{{userProfile.handle}}</span>

@ -54,8 +54,11 @@
color: white color: white
$mat-card-header-size: 100px !default $mat-card-header-size: 100px !default
.hover-box
.profile-picture-container
cursor: pointer cursor: pointer
z-index: 11
.hover-box
text-align: center text-align: center
display: flex display: flex
justify-content: center justify-content: center

@ -9,6 +9,7 @@ import {HttpClient} from '@angular/common/http';
import {SelfService} from '../../services/selfservice/self.service'; import {SelfService} from '../../services/selfservice/self.service';
import {MatDialog} from '@angular/material'; import {MatDialog} from '@angular/material';
import {DialogFileUploadComponent} from './fileUpload/fileUpload.component'; import {DialogFileUploadComponent} from './fileUpload/fileUpload.component';
import {Lightbox} from 'ngx-lightbox';
@Component({ @Component({
selector: 'app-profile', selector: 'app-profile',
@ -32,7 +33,7 @@ export class ProfileComponent implements OnInit {
private requestService: RequestService, private requestService: RequestService,
private data: DatasharingService, private data: DatasharingService,
private profileService: ProfileService, private profileService: ProfileService,
private selfService: SelfService, private lightbox: Lightbox,
public dialog: MatDialog) { public dialog: MatDialog) {
router.events.forEach((event) => { router.events.forEach((event) => {
// check if the user is on the profile page (of userY) and routes to the page of userY (e.g. his own page) // check if the user is on the profile page (of userY) and routes to the page of userY (e.g. his own page)
@ -85,4 +86,15 @@ export class ProfileComponent implements OnInit {
} }
}); });
} }
/**
* Opens a lightbox with the users profile picture
*/
openPfpLightbox() {
this.lightbox.open([{
src: this.userProfile.profilePicture,
caption: `${this.userProfile.username}'s profile picture`,
thumb: this.userProfile.profilePicture,
}], 0, {disableScrolling: true});
}
} }

@ -7,6 +7,7 @@ import {Activity} from 'src/app/models/activity';
import {BehaviorSubject} from 'rxjs'; import {BehaviorSubject} from 'rxjs';
import {tap} from 'rxjs/operators'; import {tap} from 'rxjs/operators';
import {BaseService} from '../base.service'; import {BaseService} from '../base.service';
import {formatDate} from '@angular/common';
const createPostGqlQuery = `mutation($content: String!) { const createPostGqlQuery = `mutation($content: String!) {
createPost(content: $content) { createPost(content: $content) {
@ -125,6 +126,15 @@ export class FeedService extends BaseService {
}; };
} }
/**
* Returns if the input date is today
* @param date
*/
private static dateIsToday(date: Date) {
date = new Date(date);
return new Date().setHours(0, 0, 0, 0) === date.setHours(0, 0, 0, 0);
}
/** /**
* Creates a new post * Creates a new post
* @param pContent * @param pContent
@ -240,8 +250,13 @@ export class FeedService extends BaseService {
.pipe(this.retryRated()) .pipe(this.retryRated())
.subscribe(response => { .subscribe(response => {
const posts = this.constructAllPosts(response); const posts = this.constructAllPosts(response);
const updatedPostList = this.posts.getValue().concat(posts); const previousPosts = this.posts.getValue();
this.posts.next(updatedPostList); for (const post of previousPosts.reverse()) {
if (!posts.find(p => p.id === post.id)) {
posts.unshift(post);
}
}
this.posts.next(posts);
if (posts.length < this.offsetStep) { if (posts.length < this.offsetStep) {
this.postsAvailable.next(false); this.postsAvailable.next(false);
} }
@ -257,8 +272,13 @@ export class FeedService extends BaseService {
profilePicture = 'assets/images/default-profilepic.svg'; profilePicture = 'assets/images/default-profilepic.svg';
} }
const author = new Author(post.author.id, post.author.name, post.author.handle, profilePicture); const author = new Author(post.author.id, post.author.name, post.author.handle, profilePicture);
const temp = new Date(Number(post.createdAt)); const postDate = new Date(Number(post.createdAt));
const date = temp.toLocaleString('en-GB'); let date: string;
if (FeedService.dateIsToday(postDate)) {
date = formatDate(postDate, 'HH:mm', 'en-GB');
} else {
date = formatDate(postDate, 'dd.MM.yy HH:mm', 'en-GB');
}
let activity: Activity; let activity: Activity;
if (post.activity) { if (post.activity) {
activity = new Activity( activity = new Activity(
@ -293,8 +313,13 @@ export class FeedService extends BaseService {
profilePicture = 'assets/images/default-profilepic.svg'; profilePicture = 'assets/images/default-profilepic.svg';
} }
const author = new Author(post.author.id, post.author.name, post.author.handle, profilePicture); const author = new Author(post.author.id, post.author.name, post.author.handle, profilePicture);
const temp = new Date(Number(post.createdAt)); const postDate = new Date(Number(post.createdAt));
const date = temp.toLocaleString('en-GB'); let date: string;
if (FeedService.dateIsToday(postDate)) {
date = formatDate(postDate, 'HH:mm', 'en-GB');
} else {
date = formatDate(postDate, 'dd.MM.yy HH:mm', 'en-GB');
}
let activity: Activity; let activity: Activity;
if (post.activity) { if (post.activity) {
activity = new Activity( activity = new Activity(

Loading…
Cancel
Save