Adding account editing popup and displaying profile picture in the top right corner when logged in

This commit is contained in:
Zoe Roux 2020-03-16 01:29:56 +01:00
parent c8e145d2e5
commit 8154b93f9b
18 changed files with 2683 additions and 1719 deletions

View File

@ -3,7 +3,7 @@
"version": 1,
"newProjectRoot": "projects",
"projects": {
"Kyoo": {
"kyoo": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
@ -71,18 +71,18 @@
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "Kyoo:build"
"browserTarget": "kyoo:build"
},
"configurations": {
"production": {
"browserTarget": "Kyoo:build:production"
"browserTarget": "kyoo:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "Kyoo:build"
"browserTarget": "kyoo:build"
}
},
"lint": {
@ -100,5 +100,5 @@
}
}
}},
"defaultProject": "Kyoo"
"defaultProject": "kyoo"
}

4159
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,41 +9,41 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^8.2.14",
"@angular/cdk": "^8.2.3",
"@angular/common": "^8.2.14",
"@angular/compiler": "^8.2.14",
"@angular/core": "^8.2.14",
"@angular/forms": "^8.2.14",
"@angular/material": "^8.2.3",
"@angular/platform-browser": "^8.2.14",
"@angular/platform-browser-dynamic": "^8.2.14",
"@angular/router": "^8.2.14",
"@angular/animations": "^9.0.6",
"@angular/cdk": "^9.1.2",
"@angular/common": "^9.0.6",
"@angular/compiler": "^9.0.6",
"@angular/core": "^9.0.6",
"@angular/forms": "^9.0.6",
"@angular/material": "^9.1.2",
"@angular/platform-browser": "^9.0.6",
"@angular/platform-browser-dynamic": "^9.0.6",
"@angular/router": "^9.0.6",
"angular-auth-oidc-client": "^10.0.15",
"bootstrap": "^4.4.1",
"detect-browser": "^4.8.0",
"detect-browser": "^5.0.0",
"hammerjs": "^2.0.8",
"hls.js": "^0.12.4",
"hls.js": "^0.13.2",
"jquery": "^3.4.1",
"popper.js": "^1.16.1",
"zone.js": "~0.9.1"
"zone.js": "~0.10.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "^0.803.25",
"@angular/cli": "^8.3.25",
"@angular/compiler-cli": "^8.2.14",
"@angular/language-service": "^8.2.14",
"@angular-devkit/build-angular": "^0.900.6",
"@angular/cli": "^9.0.6",
"@angular/compiler-cli": "^9.0.6",
"@angular/language-service": "^9.0.6",
"@types/bootstrap": "^4.3.1",
"@types/hls.js": "^0.12.5",
"@types/jasmine": "~3.3.8",
"@types/jasmine": "~3.5.9",
"@types/jasminewd2": "^2.0.8",
"@types/jquery": "^3.3.32",
"@types/node": "~8.9.4",
"@types/jquery": "^3.3.33",
"@types/node": "~13.9.1",
"@types/video.js": "^7.3.3",
"codelyzer": "^5.2.1",
"protractor": "^5.4.3",
"ts-node": "~7.0.0",
"tslint": "~5.15.0",
"typescript": "~3.5.3"
"ts-node": "~8.6.2",
"tslint": "^5.0.0",
"typescript": "~3.7.5"
}
}

View File

@ -1,16 +1,27 @@
<h1 mat-dialog-title>Account</h1>
<div mat-dialog-content>
<mat-form-field>
<mat-label>Email</mat-label>
<input matInput name="accountEmail" #accountEmail="ngModel" [(ngModel)]="account.email" required email>
</mat-form-field>
<br/>
<mat-form-field>
<mat-label>Username</mat-label>
<input matInput name="accountUsername" #accountUsername="ngModel" [(ngModel)]="account.username">
</mat-form-field>
<div class="row">
<div class="col-8">
<mat-form-field class="w-75">
<mat-label>Email</mat-label>
<input matInput name="accountEmail" [(ngModel)]="account.email" required email>
</mat-form-field>
<br/>
<mat-form-field class="w-75">
<mat-label>Username</mat-label>
<input matInput name="accountUsername" [(ngModel)]="account.username">
</mat-form-field>
</div>
<div class="col-4">
<input type="file" class="d-none" (change)="onPictureSelected($event)" #fileInput/>
<div class="w-100 profilePicture">
<img [src]="account.picture" alt="Profile picture" fallback="account.svg" #accountImg/>
</div>
<button mat-icon-button class="upload_picture" matTooltipPosition="above" matTooltip="Upload picture" (click)="fileInput.click()">
<mat-icon>photo_camera</mat-icon>
</button>
</div>
</div>
<div mat-dialog-actions fxFlexAlign="end" align="end" style="text-align: end">
<button mat-button (click)="cancel()">Cancel</button>
<button mat-button [mat-dialog-close]="this.account" cdkFocusInitial>Ok</button>
<button mat-button (click)="finish()" cdkFocusInitial>Ok</button>
</div>

View File

@ -0,0 +1,38 @@
*
{
box-sizing: border-box;
outline: none !important;
}
*:before, *:after
{
box-sizing: border-box;
}
.upload_picture
{
position: absolute;
bottom: 2%;
left: 0;
right: 0;
margin: auto;
}
.profilePicture
{
padding-top: 100%;
height: 0;
position: relative;
> img
{
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 100%;
height: 100%;
border-radius: 50%;
}
}

View File

@ -1,6 +1,7 @@
import {Component, Inject} from '@angular/core';
import {Component, ElementRef, Inject, ViewChild} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {Account} from "../../models/account";
import {HttpClient} from "@angular/common/http";
@Component({
@ -10,10 +11,37 @@ import {Account} from "../../models/account";
})
export class AccountComponent
{
constructor(public dialogRef: MatDialogRef<AccountComponent>, @Inject(MAT_DIALOG_DATA) public account: Account) {}
selectedPicture: File;
@ViewChild("accountImg") accountImg: ElementRef;
constructor(public dialogRef: MatDialogRef<AccountComponent>, @Inject(MAT_DIALOG_DATA) public account: Account, private http: HttpClient) {}
finish()
{
let data = new FormData();
data.append("email", this.account.email);
data.append("username", this.account.username);
data.append("picture", this.selectedPicture);
this.http.post("api/account/update", data).subscribe(() =>
{
this.dialogRef.close(this.account);
});
}
cancel()
{
this.dialogRef.close();
}
onPictureSelected(event: any)
{
this.selectedPicture = event.target.files[0];
const reader = new FileReader();
reader.onloadend = () =>
{
this.accountImg.nativeElement.src = reader.result;
};
reader.readAsDataURL(this.selectedPicture);
}
}

View File

@ -13,8 +13,6 @@ import { ShowResolverService } from './services/show-resolver.service';
import { StreamResolverService } from "./services/stream-resolver.service";
import { ShowDetailsComponent } from './show-details/show-details.component';
import {LoginComponent} from "./login/login.component";
import {AccountComponent} from "./account/account.component";
import {AuthenticatedGuard} from "./guards/authenticated-guard.service";
import {UnauthorizedComponent} from "./unauthorized/unauthorized.component";
import {LogoutComponent} from "./logout/logout.component";

View File

@ -25,14 +25,12 @@
</a>
</li>
<ng-template #accountDrop>
<li class="nav-item">
<button mat-icon-button [matMenuTriggerFor]="accountMenu" class="icon" matTooltipPosition="below" [matTooltip]="authManager.user.name">
<mat-icon>more_vert</mat-icon>
</button>
<li class="nav-item icon" style="opacity: 1 !important;">
<img matRipple [src]="authManager.user.picture" [matMenuTriggerFor]="accountMenu" class="profilePicture" matTooltipPosition="below" [matTooltip]="authManager.user.name" />
</li>
<mat-menu #accountMenu="matMenu">
<button mat-menu-item (click)="this.openAccountDialog()">Settings</button>
<button mat-menu-item (click)="this.authManager.logout()">Logout</button>
<button class="dropButton" mat-menu-item (click)="this.openAccountDialog()">Settings</button>
<button class="dropButton" mat-menu-item (click)="this.authManager.logout()">Logout</button>
</mat-menu>
</ng-template>
</ul>

View File

@ -94,3 +94,17 @@ input::-webkit-search-cancel-button
opacity: 1;
}
}
.profilePicture
{
width: 24px;
height: 24px;
display: inline-block;
vertical-align: middle;
border-radius: 50%;
}
.dropButton
{
outline: none;
}

View File

@ -19,7 +19,7 @@ export class AppComponent
isLoading: boolean = false;
account: Account;
constructor(http: HttpClient, private router: Router, private location: Location, private authManager: AuthService, public dialog: MatDialog)
constructor(private http: HttpClient, private router: Router, private location: Location, private authManager: AuthService, public dialog: MatDialog)
{
http.get<Library[]>("api/libraries").subscribe(result =>
{
@ -81,7 +81,11 @@ export class AppComponent
openAccountDialog()
{
this.dialog.open(AccountComponent, {width: "500px", data: this.account});
const dialog = this.dialog.open(AccountComponent, {width: "500px", data: this.account});
dialog.afterClosed().subscribe((result: Account) =>
{
this.account = result;
});
}
}

View File

@ -38,10 +38,11 @@ import {
OpenIdConfiguration
} from "angular-auth-oidc-client";
import { AccountComponent } from './account/account.component';
import {AuthenticatedGuard} from "./guards/authenticated-guard.service";
import { UnauthorizedComponent } from './unauthorized/unauthorized.component';
import { LogoutComponent } from './logout/logout.component';
import {MatDialogModule} from '@angular/material/dialog';
import {FallbackDirective} from "./misc/fallback.directive";
import {AuthenticatedGuard} from "./misc/guards/authenticated-guard.service";
export function loadConfig(oidcConfigService: OidcConfigService)
{
@ -64,7 +65,8 @@ export function loadConfig(oidcConfigService: OidcConfigService)
PasswordValidator,
AccountComponent,
UnauthorizedComponent,
LogoutComponent
LogoutComponent,
FallbackDirective
],
imports: [
BrowserModule,

View File

@ -0,0 +1,18 @@
import {Directive, ElementRef, HostListener, Input} from '@angular/core';
@Directive({
selector: 'img[fallback]'
})
export class FallbackDirective
{
@Input() fallback: string;
constructor(private img: ElementRef) { }
@HostListener("error")
onError()
{
const html: HTMLImageElement = this.img.nativeElement;
html.src = this.fallback;
}
}

View File

@ -10,7 +10,7 @@ import {
Router
} from '@angular/router';
import { Observable } from 'rxjs';
import {AuthService} from "../services/auth.service";
import {AuthService} from "../../services/auth.service";
@Injectable({
providedIn: 'root'

View File

@ -57,6 +57,6 @@ export class AuthService
if (!this.isAuthenticated)
return null;
console.log(this.user);
return {email: this.user.email, username: this.user.username};
return {email: this.user.email, username: this.user.username, picture: this.user.picture};
}
}

View File

@ -2,4 +2,5 @@ export interface Account
{
username: string;
email: string;
picture: string;
}

View File

View File

@ -1,20 +0,0 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

1
static/account.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"/><path d="M0 0h24v24H0z" fill="none"/></svg>

After

Width:  |  Height:  |  Size: 365 B