aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/shared/shared-main/account
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2020-06-23 14:10:17 +0200
committerChocobozzz <chocobozzz@cpy.re>2020-06-23 16:00:49 +0200
commit67ed6552b831df66713bac9e672738796128d33f (patch)
tree59c97d41e0b49d75a90aa3de987968ab9b1ff447 /client/src/app/shared/shared-main/account
parent0c4bacbff53bc732f5a2677d62a6ead7752e2405 (diff)
downloadPeerTube-67ed6552b831df66713bac9e672738796128d33f.tar.gz
PeerTube-67ed6552b831df66713bac9e672738796128d33f.tar.zst
PeerTube-67ed6552b831df66713bac9e672738796128d33f.zip
Reorganize client shared modules
Diffstat (limited to 'client/src/app/shared/shared-main/account')
-rw-r--r--client/src/app/shared/shared-main/account/account.model.ts30
-rw-r--r--client/src/app/shared/shared-main/account/account.service.ts29
-rw-r--r--client/src/app/shared/shared-main/account/actor-avatar-info.component.html24
-rw-r--r--client/src/app/shared/shared-main/account/actor-avatar-info.component.scss71
-rw-r--r--client/src/app/shared/shared-main/account/actor-avatar-info.component.ts64
-rw-r--r--client/src/app/shared/shared-main/account/actor.model.ts65
-rw-r--r--client/src/app/shared/shared-main/account/avatar.component.html8
-rw-r--r--client/src/app/shared/shared-main/account/avatar.component.scss40
-rw-r--r--client/src/app/shared/shared-main/account/avatar.component.ts31
-rw-r--r--client/src/app/shared/shared-main/account/index.ts5
10 files changed, 367 insertions, 0 deletions
diff --git a/client/src/app/shared/shared-main/account/account.model.ts b/client/src/app/shared/shared-main/account/account.model.ts
new file mode 100644
index 000000000..6df2e9d10
--- /dev/null
+++ b/client/src/app/shared/shared-main/account/account.model.ts
@@ -0,0 +1,30 @@
1import { Account as ServerAccount } from '@shared/models/actors/account.model'
2import { Actor } from './actor.model'
3
4export class Account extends Actor implements ServerAccount {
5 displayName: string
6 description: string
7 nameWithHost: string
8 nameWithHostForced: string
9 mutedByUser: boolean
10 mutedByInstance: boolean
11 mutedServerByUser: boolean
12 mutedServerByInstance: boolean
13
14 userId?: number
15
16 constructor (hash: ServerAccount) {
17 super(hash)
18
19 this.displayName = hash.displayName
20 this.description = hash.description
21 this.userId = hash.userId
22 this.nameWithHost = Actor.CREATE_BY_STRING(this.name, this.host)
23 this.nameWithHostForced = Actor.CREATE_BY_STRING(this.name, this.host, true)
24
25 this.mutedByUser = false
26 this.mutedByInstance = false
27 this.mutedServerByUser = false
28 this.mutedServerByInstance = false
29 }
30}
diff --git a/client/src/app/shared/shared-main/account/account.service.ts b/client/src/app/shared/shared-main/account/account.service.ts
new file mode 100644
index 000000000..8f4abf070
--- /dev/null
+++ b/client/src/app/shared/shared-main/account/account.service.ts
@@ -0,0 +1,29 @@
1import { Observable, ReplaySubject } from 'rxjs'
2import { catchError, map, tap } from 'rxjs/operators'
3import { HttpClient } from '@angular/common/http'
4import { Injectable } from '@angular/core'
5import { RestExtractor } from '@app/core'
6import { Account as ServerAccount } from '@shared/models'
7import { environment } from '../../../../environments/environment'
8import { Account } from './account.model'
9
10@Injectable()
11export class AccountService {
12 static BASE_ACCOUNT_URL = environment.apiUrl + '/api/v1/accounts/'
13
14 accountLoaded = new ReplaySubject<Account>(1)
15
16 constructor (
17 private authHttp: HttpClient,
18 private restExtractor: RestExtractor
19 ) {}
20
21 getAccount (id: number | string): Observable<Account> {
22 return this.authHttp.get<ServerAccount>(AccountService.BASE_ACCOUNT_URL + id)
23 .pipe(
24 map(accountHash => new Account(accountHash)),
25 tap(account => this.accountLoaded.next(account)),
26 catchError(res => this.restExtractor.handleError(res))
27 )
28 }
29}
diff --git a/client/src/app/shared/shared-main/account/actor-avatar-info.component.html b/client/src/app/shared/shared-main/account/actor-avatar-info.component.html
new file mode 100644
index 000000000..d01b9ac7f
--- /dev/null
+++ b/client/src/app/shared/shared-main/account/actor-avatar-info.component.html
@@ -0,0 +1,24 @@
1<ng-container *ngIf="actor">
2 <div class="actor">
3 <div class="d-flex">
4 <img [src]="actor.avatarUrl" alt="Avatar" />
5
6 <div class="actor-img-edit-container">
7 <div class="actor-img-edit-button" [ngbTooltip]="'(extensions: '+ avatarExtensions +', '+ maxSizeText +': '+ maxAvatarSizeInBytes +')'" placement="right" container="body">
8 <my-global-icon iconName="edit"></my-global-icon>
9 <label for="avatarfile" i18n>Change your avatar</label>
10 <input #avatarfileInput type="file" title=" " name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange()"/>
11 </div>
12 </div>
13 </div>
14
15
16 <div class="actor-info">
17 <div class="actor-info-names">
18 <div class="actor-info-display-name">{{ actor.displayName }}</div>
19 <div class="actor-info-username">{{ actor.name }}</div>
20 </div>
21 <div i18n class="actor-info-followers">{{ actor.followersCount }} subscribers</div>
22 </div>
23 </div>
24</ng-container> \ No newline at end of file
diff --git a/client/src/app/shared/shared-main/account/actor-avatar-info.component.scss b/client/src/app/shared/shared-main/account/actor-avatar-info.component.scss
new file mode 100644
index 000000000..5a66ecfd2
--- /dev/null
+++ b/client/src/app/shared/shared-main/account/actor-avatar-info.component.scss
@@ -0,0 +1,71 @@
1@import '_variables';
2@import '_mixins';
3
4.actor {
5 display: flex;
6
7 img {
8 @include avatar(100px);
9
10 margin-right: 15px;
11 }
12
13 .actor-img-edit-container {
14 position: relative;
15 width: 0;
16
17 .actor-img-edit-button {
18 @include peertube-button-file(21px);
19 @include button-with-icon(19px);
20
21 margin-top: 10px;
22 margin-bottom: 5px;
23 border-radius: 50%;
24 top: 55px;
25 right: 45px;
26 cursor: pointer;
27
28 input {
29 width: 30px;
30 height: 30px;
31 }
32
33 my-global-icon {
34 right: 7px;
35 }
36 }
37 }
38
39 .actor-info {
40 justify-content: center;
41 display: inline-flex;
42 flex-direction: column;
43
44 .actor-info-names {
45 display: flex;
46 align-items: center;
47
48 .actor-info-display-name {
49 font-size: 20px;
50 font-weight: $font-bold;
51
52 @media screen and (max-width: $small-view) {
53 font-size: 16px;
54 }
55 }
56
57 .actor-info-username {
58 margin-left: 7px;
59 position: relative;
60 top: 2px;
61 font-size: 14px;
62 color: $grey-actor-name;
63 }
64 }
65
66 .actor-info-followers {
67 font-size: 15px;
68 padding-bottom: .5rem;
69 }
70 }
71}
diff --git a/client/src/app/shared/shared-main/account/actor-avatar-info.component.ts b/client/src/app/shared/shared-main/account/actor-avatar-info.component.ts
new file mode 100644
index 000000000..0c04ae4a6
--- /dev/null
+++ b/client/src/app/shared/shared-main/account/actor-avatar-info.component.ts
@@ -0,0 +1,64 @@
1import { BytesPipe } from 'ngx-pipes'
2import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
3import { Notifier, ServerService } from '@app/core'
4import { Account, VideoChannel } from '@app/shared/shared-main'
5import { I18n } from '@ngx-translate/i18n-polyfill'
6import { ServerConfig } from '@shared/models'
7
8@Component({
9 selector: 'my-actor-avatar-info',
10 templateUrl: './actor-avatar-info.component.html',
11 styleUrls: [ './actor-avatar-info.component.scss' ]
12})
13export class ActorAvatarInfoComponent implements OnInit {
14 @ViewChild('avatarfileInput') avatarfileInput: ElementRef<HTMLInputElement>
15
16 @Input() actor: VideoChannel | Account
17
18 @Output() avatarChange = new EventEmitter<FormData>()
19
20 maxSizeText: string
21
22 private serverConfig: ServerConfig
23 private bytesPipe: BytesPipe
24
25 constructor (
26 private serverService: ServerService,
27 private notifier: Notifier,
28 private i18n: I18n
29 ) {
30 this.bytesPipe = new BytesPipe()
31 this.maxSizeText = this.i18n('max size')
32 }
33
34 ngOnInit (): void {
35 this.serverConfig = this.serverService.getTmpConfig()
36 this.serverService.getConfig()
37 .subscribe(config => this.serverConfig = config)
38 }
39
40 onAvatarChange () {
41 const avatarfile = this.avatarfileInput.nativeElement.files[ 0 ]
42 if (avatarfile.size > this.maxAvatarSize) {
43 this.notifier.error('Error', 'This image is too large.')
44 return
45 }
46
47 const formData = new FormData()
48 formData.append('avatarfile', avatarfile)
49
50 this.avatarChange.emit(formData)
51 }
52
53 get maxAvatarSize () {
54 return this.serverConfig.avatar.file.size.max
55 }
56
57 get maxAvatarSizeInBytes () {
58 return this.bytesPipe.transform(this.maxAvatarSize)
59 }
60
61 get avatarExtensions () {
62 return this.serverConfig.avatar.file.extensions.join(', ')
63 }
64}
diff --git a/client/src/app/shared/shared-main/account/actor.model.ts b/client/src/app/shared/shared-main/account/actor.model.ts
new file mode 100644
index 000000000..5fc7989dd
--- /dev/null
+++ b/client/src/app/shared/shared-main/account/actor.model.ts
@@ -0,0 +1,65 @@
1import { Actor as ActorServer, Avatar } from '@shared/models'
2import { getAbsoluteAPIUrl } from '@app/helpers'
3
4export abstract class Actor implements ActorServer {
5 id: number
6 url: string
7 name: string
8 host: string
9 followingCount: number
10 followersCount: number
11 createdAt: Date | string
12 updatedAt: Date | string
13 avatar: Avatar
14
15 avatarUrl: string
16
17 static GET_ACTOR_AVATAR_URL (actor: { avatar?: Avatar }) {
18 if (actor?.avatar?.url) return actor.avatar.url
19
20 if (actor && actor.avatar) {
21 const absoluteAPIUrl = getAbsoluteAPIUrl()
22
23 return absoluteAPIUrl + actor.avatar.path
24 }
25
26 return this.GET_DEFAULT_AVATAR_URL()
27 }
28
29 static GET_DEFAULT_AVATAR_URL () {
30 return window.location.origin + '/client/assets/images/default-avatar.png'
31 }
32
33 static CREATE_BY_STRING (accountName: string, host: string, forceHostname = false) {
34 const absoluteAPIUrl = getAbsoluteAPIUrl()
35 const thisHost = new URL(absoluteAPIUrl).host
36
37 if (host.trim() === thisHost && !forceHostname) return accountName
38
39 return accountName + '@' + host
40 }
41
42 protected constructor (hash: ActorServer) {
43 this.id = hash.id
44 this.url = hash.url
45 this.name = hash.name
46 this.host = hash.host
47 this.followingCount = hash.followingCount
48 this.followersCount = hash.followersCount
49 this.createdAt = new Date(hash.createdAt.toString())
50 this.updatedAt = new Date(hash.updatedAt.toString())
51 this.avatar = hash.avatar
52
53 this.updateComputedAttributes()
54 }
55
56 updateAvatar (newAvatar: Avatar) {
57 this.avatar = newAvatar
58
59 this.updateComputedAttributes()
60 }
61
62 private updateComputedAttributes () {
63 this.avatarUrl = Actor.GET_ACTOR_AVATAR_URL(this)
64 }
65}
diff --git a/client/src/app/shared/shared-main/account/avatar.component.html b/client/src/app/shared/shared-main/account/avatar.component.html
new file mode 100644
index 000000000..09871fca4
--- /dev/null
+++ b/client/src/app/shared/shared-main/account/avatar.component.html
@@ -0,0 +1,8 @@
1<div class="wrapper" [ngClass]="'avatar-' + size">
2 <a [routerLink]="[ '/video-channels', video.byVideoChannel ]" [title]="channelLinkTitle">
3 <img [src]="video.videoChannelAvatarUrl" i18n-alt alt="Channel avatar" />
4 </a>
5 <a [routerLink]="[ '/accounts', video.byAccount ]" [title]="accountLinkTitle">
6 <img [src]="video.accountAvatarUrl" i18n-alt alt="Account avatar" />
7 </a>
8</div>
diff --git a/client/src/app/shared/shared-main/account/avatar.component.scss b/client/src/app/shared/shared-main/account/avatar.component.scss
new file mode 100644
index 000000000..37709fce6
--- /dev/null
+++ b/client/src/app/shared/shared-main/account/avatar.component.scss
@@ -0,0 +1,40 @@
1@import '_mixins';
2
3.wrapper {
4 $avatar-size: 35px;
5
6 width: $avatar-size;
7 height: $avatar-size;
8 position: relative;
9 margin-right: 5px;
10 margin-bottom: 5px;
11
12 &.avatar-sm {
13 width: 28px;
14 height: 28px;
15 margin-bottom: 3px;
16 }
17
18 a {
19 @include disable-outline;
20 }
21
22 a img {
23 height: 100%;
24 object-fit: cover;
25 position: absolute;
26 top:50%;
27 left:50%;
28 border-radius: 50%;
29 transform: translate(-50%,-50%)
30 }
31
32 a:nth-of-type(2) img {
33 height: 60%;
34 width: 60%;
35 border: 2px solid pvar(--mainBackgroundColor);
36 transform: translateX(15%);
37 position: relative;
38 background-color: pvar(--mainBackgroundColor);
39 }
40}
diff --git a/client/src/app/shared/shared-main/account/avatar.component.ts b/client/src/app/shared/shared-main/account/avatar.component.ts
new file mode 100644
index 000000000..31f39c200
--- /dev/null
+++ b/client/src/app/shared/shared-main/account/avatar.component.ts
@@ -0,0 +1,31 @@
1import { Component, Input, OnInit } from '@angular/core'
2import { Video } from '../video/video.model'
3import { I18n } from '@ngx-translate/i18n-polyfill'
4
5@Component({
6 selector: 'avatar-channel',
7 templateUrl: './avatar.component.html',
8 styleUrls: [ './avatar.component.scss' ]
9})
10export class AvatarComponent implements OnInit {
11 @Input() video: Video
12 @Input() size: 'md' | 'sm' = 'md'
13
14 channelLinkTitle = ''
15 accountLinkTitle = ''
16
17 constructor (
18 private i18n: I18n
19 ) {}
20
21 ngOnInit () {
22 this.channelLinkTitle = this.i18n(
23 '{{name}} (channel page)',
24 { name: this.video.channel.name, handle: this.video.byVideoChannel }
25 )
26 this.accountLinkTitle = this.i18n(
27 '{{name}} (account page)',
28 { name: this.video.account.name, handle: this.video.byAccount }
29 )
30 }
31}
diff --git a/client/src/app/shared/shared-main/account/index.ts b/client/src/app/shared/shared-main/account/index.ts
new file mode 100644
index 000000000..f5b9f3634
--- /dev/null
+++ b/client/src/app/shared/shared-main/account/index.ts
@@ -0,0 +1,5 @@
1export * from './account.model'
2export * from './account.service'
3export * from './actor-avatar-info.component'
4export * from './actor.model'
5export * from './avatar.component'