aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/shared/shared-main/users
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/shared/shared-main/users')
-rw-r--r--client/src/app/shared/shared-main/users/index.ts4
-rw-r--r--client/src/app/shared/shared-main/users/user-history.service.ts43
-rw-r--r--client/src/app/shared/shared-main/users/user-notification.model.ts184
-rw-r--r--client/src/app/shared/shared-main/users/user-notification.service.ts81
-rw-r--r--client/src/app/shared/shared-main/users/user-notifications.component.html166
-rw-r--r--client/src/app/shared/shared-main/users/user-notifications.component.scss53
-rw-r--r--client/src/app/shared/shared-main/users/user-notifications.component.ts100
7 files changed, 631 insertions, 0 deletions
diff --git a/client/src/app/shared/shared-main/users/index.ts b/client/src/app/shared/shared-main/users/index.ts
new file mode 100644
index 000000000..83401ab52
--- /dev/null
+++ b/client/src/app/shared/shared-main/users/index.ts
@@ -0,0 +1,4 @@
1export * from './user-history.service'
2export * from './user-notification.model'
3export * from './user-notification.service'
4export * from './user-notifications.component'
diff --git a/client/src/app/shared/shared-main/users/user-history.service.ts b/client/src/app/shared/shared-main/users/user-history.service.ts
new file mode 100644
index 000000000..43970dc5b
--- /dev/null
+++ b/client/src/app/shared/shared-main/users/user-history.service.ts
@@ -0,0 +1,43 @@
1import { catchError, map, switchMap } from 'rxjs/operators'
2import { HttpClient, HttpParams } from '@angular/common/http'
3import { Injectable } from '@angular/core'
4import { ComponentPaginationLight, RestExtractor, RestService } from '@app/core'
5import { ResultList } from '@shared/models'
6import { environment } from '../../../../environments/environment'
7import { Video } from '../video/video.model'
8import { VideoService } from '../video/video.service'
9
10@Injectable()
11export class UserHistoryService {
12 static BASE_USER_VIDEOS_HISTORY_URL = environment.apiUrl + '/api/v1/users/me/history/videos'
13
14 constructor (
15 private authHttp: HttpClient,
16 private restExtractor: RestExtractor,
17 private restService: RestService,
18 private videoService: VideoService
19 ) {}
20
21 getUserVideosHistory (historyPagination: ComponentPaginationLight) {
22 const pagination = this.restService.componentPaginationToRestPagination(historyPagination)
23
24 let params = new HttpParams()
25 params = this.restService.addRestGetParams(params, pagination)
26
27 return this.authHttp
28 .get<ResultList<Video>>(UserHistoryService.BASE_USER_VIDEOS_HISTORY_URL, { params })
29 .pipe(
30 switchMap(res => this.videoService.extractVideos(res)),
31 catchError(err => this.restExtractor.handleError(err))
32 )
33 }
34
35 deleteUserVideosHistory () {
36 return this.authHttp
37 .post(UserHistoryService.BASE_USER_VIDEOS_HISTORY_URL + '/remove', {})
38 .pipe(
39 map(() => this.restExtractor.extractDataBool()),
40 catchError(err => this.restExtractor.handleError(err))
41 )
42 }
43}
diff --git a/client/src/app/shared/shared-main/users/user-notification.model.ts b/client/src/app/shared/shared-main/users/user-notification.model.ts
new file mode 100644
index 000000000..de25d3ab9
--- /dev/null
+++ b/client/src/app/shared/shared-main/users/user-notification.model.ts
@@ -0,0 +1,184 @@
1import { Actor } from '../account/actor.model'
2import { ActorInfo, Avatar, FollowState, UserNotification as UserNotificationServer, UserNotificationType, VideoInfo } from '@shared/models'
3
4export class UserNotification implements UserNotificationServer {
5 id: number
6 type: UserNotificationType
7 read: boolean
8
9 video?: VideoInfo & {
10 channel: ActorInfo & { avatarUrl?: string }
11 }
12
13 videoImport?: {
14 id: number
15 video?: VideoInfo
16 torrentName?: string
17 magnetUri?: string
18 targetUrl?: string
19 }
20
21 comment?: {
22 id: number
23 threadId: number
24 account: ActorInfo & { avatarUrl?: string }
25 video: VideoInfo
26 }
27
28 videoAbuse?: {
29 id: number
30 video: VideoInfo
31 }
32
33 videoBlacklist?: {
34 id: number
35 video: VideoInfo
36 }
37
38 account?: ActorInfo & { avatarUrl?: string }
39
40 actorFollow?: {
41 id: number
42 state: FollowState
43 follower: ActorInfo & { avatarUrl?: string }
44 following: {
45 type: 'account' | 'channel' | 'instance'
46 name: string
47 displayName: string
48 host: string
49 }
50 }
51
52 createdAt: string
53 updatedAt: string
54
55 // Additional fields
56 videoUrl?: string
57 commentUrl?: any[]
58 videoAbuseUrl?: string
59 videoAutoBlacklistUrl?: string
60 accountUrl?: string
61 videoImportIdentifier?: string
62 videoImportUrl?: string
63 instanceFollowUrl?: string
64
65 constructor (hash: UserNotificationServer) {
66 this.id = hash.id
67 this.type = hash.type
68 this.read = hash.read
69
70 // We assume that some fields exist
71 // To prevent a notification popup crash in case of bug, wrap it inside a try/catch
72 try {
73 this.video = hash.video
74 if (this.video) this.setAvatarUrl(this.video.channel)
75
76 this.videoImport = hash.videoImport
77
78 this.comment = hash.comment
79 if (this.comment) this.setAvatarUrl(this.comment.account)
80
81 this.videoAbuse = hash.videoAbuse
82
83 this.videoBlacklist = hash.videoBlacklist
84
85 this.account = hash.account
86 if (this.account) this.setAvatarUrl(this.account)
87
88 this.actorFollow = hash.actorFollow
89 if (this.actorFollow) this.setAvatarUrl(this.actorFollow.follower)
90
91 this.createdAt = hash.createdAt
92 this.updatedAt = hash.updatedAt
93
94 switch (this.type) {
95 case UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION:
96 this.videoUrl = this.buildVideoUrl(this.video)
97 break
98
99 case UserNotificationType.UNBLACKLIST_ON_MY_VIDEO:
100 this.videoUrl = this.buildVideoUrl(this.video)
101 break
102
103 case UserNotificationType.NEW_COMMENT_ON_MY_VIDEO:
104 case UserNotificationType.COMMENT_MENTION:
105 if (!this.comment) break
106 this.accountUrl = this.buildAccountUrl(this.comment.account)
107 this.commentUrl = [ this.buildVideoUrl(this.comment.video), { threadId: this.comment.threadId } ]
108 break
109
110 case UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS:
111 this.videoAbuseUrl = '/admin/moderation/video-abuses/list'
112 this.videoUrl = this.buildVideoUrl(this.videoAbuse.video)
113 break
114
115 case UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS:
116 this.videoAutoBlacklistUrl = '/admin/moderation/video-auto-blacklist/list'
117 // Backward compatibility where we did not assign videoBlacklist to this type of notification before
118 if (!this.videoBlacklist) this.videoBlacklist = { id: null, video: this.video }
119
120 this.videoUrl = this.buildVideoUrl(this.videoBlacklist.video)
121 break
122
123 case UserNotificationType.BLACKLIST_ON_MY_VIDEO:
124 this.videoUrl = this.buildVideoUrl(this.videoBlacklist.video)
125 break
126
127 case UserNotificationType.MY_VIDEO_PUBLISHED:
128 this.videoUrl = this.buildVideoUrl(this.video)
129 break
130
131 case UserNotificationType.MY_VIDEO_IMPORT_SUCCESS:
132 this.videoImportUrl = this.buildVideoImportUrl()
133 this.videoImportIdentifier = this.buildVideoImportIdentifier(this.videoImport)
134
135 if (this.videoImport.video) this.videoUrl = this.buildVideoUrl(this.videoImport.video)
136 break
137
138 case UserNotificationType.MY_VIDEO_IMPORT_ERROR:
139 this.videoImportUrl = this.buildVideoImportUrl()
140 this.videoImportIdentifier = this.buildVideoImportIdentifier(this.videoImport)
141 break
142
143 case UserNotificationType.NEW_USER_REGISTRATION:
144 this.accountUrl = this.buildAccountUrl(this.account)
145 break
146
147 case UserNotificationType.NEW_FOLLOW:
148 this.accountUrl = this.buildAccountUrl(this.actorFollow.follower)
149 break
150
151 case UserNotificationType.NEW_INSTANCE_FOLLOWER:
152 this.instanceFollowUrl = '/admin/follows/followers-list'
153 break
154
155 case UserNotificationType.AUTO_INSTANCE_FOLLOWING:
156 this.instanceFollowUrl = '/admin/follows/following-list'
157 break
158 }
159 } catch (err) {
160 this.type = null
161 console.error(err)
162 }
163 }
164
165 private buildVideoUrl (video: { uuid: string }) {
166 return '/videos/watch/' + video.uuid
167 }
168
169 private buildAccountUrl (account: { name: string, host: string }) {
170 return '/accounts/' + Actor.CREATE_BY_STRING(account.name, account.host)
171 }
172
173 private buildVideoImportUrl () {
174 return '/my-account/video-imports'
175 }
176
177 private buildVideoImportIdentifier (videoImport: { targetUrl?: string, magnetUri?: string, torrentName?: string }) {
178 return videoImport.targetUrl || videoImport.magnetUri || videoImport.torrentName
179 }
180
181 private setAvatarUrl (actor: { avatarUrl?: string, avatar?: Avatar }) {
182 actor.avatarUrl = Actor.GET_ACTOR_AVATAR_URL(actor)
183 }
184}
diff --git a/client/src/app/shared/shared-main/users/user-notification.service.ts b/client/src/app/shared/shared-main/users/user-notification.service.ts
new file mode 100644
index 000000000..8dd9472fe
--- /dev/null
+++ b/client/src/app/shared/shared-main/users/user-notification.service.ts
@@ -0,0 +1,81 @@
1import { catchError, map, tap } from 'rxjs/operators'
2import { HttpClient, HttpParams } from '@angular/common/http'
3import { Injectable } from '@angular/core'
4import { ComponentPaginationLight, RestExtractor, RestService, User, UserNotificationSocket } from '@app/core'
5import { ResultList, UserNotification as UserNotificationServer, UserNotificationSetting } from '@shared/models'
6import { environment } from '../../../../environments/environment'
7import { UserNotification } from './user-notification.model'
8
9@Injectable()
10export class UserNotificationService {
11 static BASE_NOTIFICATIONS_URL = environment.apiUrl + '/api/v1/users/me/notifications'
12 static BASE_NOTIFICATION_SETTINGS = environment.apiUrl + '/api/v1/users/me/notification-settings'
13
14 constructor (
15 private authHttp: HttpClient,
16 private restExtractor: RestExtractor,
17 private restService: RestService,
18 private userNotificationSocket: UserNotificationSocket
19 ) {}
20
21 listMyNotifications (pagination: ComponentPaginationLight, unread?: boolean, ignoreLoadingBar = false) {
22 let params = new HttpParams()
23 params = this.restService.addRestGetParams(params, this.restService.componentPaginationToRestPagination(pagination))
24
25 if (unread) params = params.append('unread', `${unread}`)
26
27 const headers = ignoreLoadingBar ? { ignoreLoadingBar: '' } : undefined
28
29 return this.authHttp.get<ResultList<UserNotification>>(UserNotificationService.BASE_NOTIFICATIONS_URL, { params, headers })
30 .pipe(
31 map(res => this.restExtractor.convertResultListDateToHuman(res)),
32 map(res => this.restExtractor.applyToResultListData(res, this.formatNotification.bind(this))),
33 catchError(err => this.restExtractor.handleError(err))
34 )
35 }
36
37 countUnreadNotifications () {
38 return this.listMyNotifications({ currentPage: 1, itemsPerPage: 0 }, true)
39 .pipe(map(n => n.total))
40 }
41
42 markAsRead (notification: UserNotification) {
43 const url = UserNotificationService.BASE_NOTIFICATIONS_URL + '/read'
44
45 const body = { ids: [ notification.id ] }
46 const headers = { ignoreLoadingBar: '' }
47
48 return this.authHttp.post(url, body, { headers })
49 .pipe(
50 map(this.restExtractor.extractDataBool),
51 tap(() => this.userNotificationSocket.dispatch('read')),
52 catchError(res => this.restExtractor.handleError(res))
53 )
54 }
55
56 markAllAsRead () {
57 const url = UserNotificationService.BASE_NOTIFICATIONS_URL + '/read-all'
58 const headers = { ignoreLoadingBar: '' }
59
60 return this.authHttp.post(url, {}, { headers })
61 .pipe(
62 map(this.restExtractor.extractDataBool),
63 tap(() => this.userNotificationSocket.dispatch('read-all')),
64 catchError(res => this.restExtractor.handleError(res))
65 )
66 }
67
68 updateNotificationSettings (user: User, settings: UserNotificationSetting) {
69 const url = UserNotificationService.BASE_NOTIFICATION_SETTINGS
70
71 return this.authHttp.put(url, settings)
72 .pipe(
73 map(this.restExtractor.extractDataBool),
74 catchError(res => this.restExtractor.handleError(res))
75 )
76 }
77
78 private formatNotification (notification: UserNotificationServer) {
79 return new UserNotification(notification)
80 }
81}
diff --git a/client/src/app/shared/shared-main/users/user-notifications.component.html b/client/src/app/shared/shared-main/users/user-notifications.component.html
new file mode 100644
index 000000000..08771110d
--- /dev/null
+++ b/client/src/app/shared/shared-main/users/user-notifications.component.html
@@ -0,0 +1,166 @@
1<div *ngIf="componentPagination.totalItems === 0" class="no-notification" i18n>You don't have notifications.</div>
2
3<div class="notifications" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()">
4 <div *ngFor="let notification of notifications" class="notification" [ngClass]="{ unread: !notification.read }" (click)="markAsRead(notification)">
5
6 <ng-container [ngSwitch]="notification.type">
7 <ng-container *ngSwitchCase="UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION">
8 <ng-container *ngIf="notification.video; then hasVideo; else noVideo"></ng-container>
9
10 <ng-template #hasVideo>
11 <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">
12 <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.video.channel.avatarUrl" />
13 </a>
14
15 <div class="message" i18n>
16 {{ notification.video.channel.displayName }} published a new video: <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a>
17 </div>
18 </ng-template>
19
20 <ng-template #noVideo>
21 <my-global-icon iconName="alert" aria-hidden="true"></my-global-icon>
22
23 <div class="message" i18n>
24 The notification concerns a video now unavailable
25 </div>
26 </ng-template>
27 </ng-container>
28
29 <ng-container *ngSwitchCase="UserNotificationType.UNBLACKLIST_ON_MY_VIDEO">
30 <my-global-icon iconName="undo" aria-hidden="true"></my-global-icon>
31
32 <div class="message" i18n>
33 Your video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a> has been unblocked
34 </div>
35 </ng-container>
36
37 <ng-container *ngSwitchCase="UserNotificationType.BLACKLIST_ON_MY_VIDEO">
38 <my-global-icon iconName="no" aria-hidden="true"></my-global-icon>
39
40 <div class="message" i18n>
41 Your video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.videoBlacklist.video.name }}</a> has been blocked
42 </div>
43 </ng-container>
44
45 <ng-container *ngSwitchCase="UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS">
46 <my-global-icon iconName="alert" aria-hidden="true"></my-global-icon>
47
48 <div class="message" i18n>
49 <a (click)="markAsRead(notification)" [routerLink]="notification.videoAbuseUrl">A new video abuse</a> has been created on video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.videoAbuse.video.name }}</a>
50 </div>
51 </ng-container>
52
53 <ng-container *ngSwitchCase="UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS">
54 <my-global-icon iconName="no" aria-hidden="true"></my-global-icon>
55
56 <div class="message" i18n>
57 The recently added video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.videoBlacklist.video.name }}</a> has been <a (click)="markAsRead(notification)" [routerLink]="notification.videoAutoBlacklistUrl">automatically blocked</a>
58 </div>
59 </ng-container>
60
61 <ng-container *ngSwitchCase="UserNotificationType.NEW_COMMENT_ON_MY_VIDEO">
62 <ng-container *ngIf="notification.comment; then hasComment; else noComment"></ng-container>
63
64 <ng-template #hasComment>
65 <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">
66 <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.comment.account.avatarUrl" />
67 </a>
68
69 <div class="message" i18n>
70 <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">{{ notification.comment.account.displayName }}</a> commented your video <a (click)="markAsRead(notification)" [routerLink]="notification.commentUrl">{{ notification.comment.video.name }}</a>
71 </div>
72 </ng-template>
73
74 <ng-template #noComment>
75 <my-global-icon iconName="alert" aria-hidden="true"></my-global-icon>
76
77 <div class="message" i18n>
78 The notification concerns a comment now unavailable
79 </div>
80 </ng-template>
81 </ng-container>
82
83 <ng-container *ngSwitchCase="UserNotificationType.MY_VIDEO_PUBLISHED">
84 <my-global-icon iconName="sparkle" aria-hidden="true"></my-global-icon>
85
86 <div class="message" i18n>
87 Your video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a> has been published
88 </div>
89 </ng-container>
90
91 <ng-container *ngSwitchCase="UserNotificationType.MY_VIDEO_IMPORT_SUCCESS">
92 <my-global-icon iconName="cloud-download" aria-hidden="true"></my-global-icon>
93
94 <div class="message" i18n>
95 <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl || notification.videoImportUrl">Your video import</a> {{ notification.videoImportIdentifier }} succeeded
96 </div>
97 </ng-container>
98
99 <ng-container *ngSwitchCase="UserNotificationType.MY_VIDEO_IMPORT_ERROR">
100 <my-global-icon iconName="cloud-error" aria-hidden="true"></my-global-icon>
101
102 <div class="message" i18n>
103 <a (click)="markAsRead(notification)" [routerLink]="notification.videoImportUrl">Your video import</a> {{ notification.videoImportIdentifier }} failed
104 </div>
105 </ng-container>
106
107 <ng-container *ngSwitchCase="UserNotificationType.NEW_USER_REGISTRATION">
108 <my-global-icon iconName="user-add" aria-hidden="true"></my-global-icon>
109
110 <div class="message" i18n>
111 User <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">{{ notification.account.name }}</a> registered on your instance
112 </div>
113 </ng-container>
114
115 <ng-container *ngSwitchCase="UserNotificationType.NEW_FOLLOW">
116 <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">
117 <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.actorFollow.follower.avatarUrl" />
118 </a>
119
120 <div class="message" i18n>
121 <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">{{ notification.actorFollow.follower.displayName }}</a> is following
122
123 <ng-container *ngIf="notification.actorFollow.following.type === 'channel'">your channel {{ notification.actorFollow.following.displayName }}</ng-container>
124 <ng-container *ngIf="notification.actorFollow.following.type === 'account'">your account</ng-container>
125 </div>
126 </ng-container>
127
128 <ng-container *ngSwitchCase="UserNotificationType.COMMENT_MENTION">
129 <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">
130 <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.comment.account.avatarUrl" />
131 </a>
132
133 <div class="message" i18n>
134 <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">{{ notification.comment.account.displayName }}</a> mentioned you on <a (click)="markAsRead(notification)" [routerLink]="notification.commentUrl">video {{ notification.comment.video.name }}</a>
135 </div>
136 </ng-container>
137
138 <ng-container *ngSwitchCase="UserNotificationType.NEW_INSTANCE_FOLLOWER">
139 <my-global-icon iconName="users" aria-hidden="true"></my-global-icon>
140
141 <div class="message" i18n>
142 Your instance has <a (click)="markAsRead(notification)" [routerLink]="notification.instanceFollowUrl">a new follower</a> ({{ notification.actorFollow?.follower.host }})
143 <ng-container *ngIf="notification.actorFollow?.state === 'pending'"> awaiting your approval</ng-container>
144 </div>
145 </ng-container>
146
147 <ng-container *ngSwitchCase="UserNotificationType.AUTO_INSTANCE_FOLLOWING">
148 <my-global-icon iconName="users" aria-hidden="true"></my-global-icon>
149
150 <div class="message" i18n>
151 Your instance automatically followed <a (click)="markAsRead(notification)" [routerLink]="notification.instanceFollowUrl">{{ notification.actorFollow.following.host }}</a>
152 </div>
153 </ng-container>
154
155 <ng-container *ngSwitchDefault>
156 <my-global-icon iconName="alert" aria-hidden="true"></my-global-icon>
157
158 <div class="message" i18n>
159 The notification points to a content now unavailable
160 </div>
161 </ng-container>
162 </ng-container>
163
164 <div [title]="notification.createdAt" class="from-date">{{ notification.createdAt | myFromNow }}</div>
165 </div>
166</div>
diff --git a/client/src/app/shared/shared-main/users/user-notifications.component.scss b/client/src/app/shared/shared-main/users/user-notifications.component.scss
new file mode 100644
index 000000000..5166bd559
--- /dev/null
+++ b/client/src/app/shared/shared-main/users/user-notifications.component.scss
@@ -0,0 +1,53 @@
1@import '_variables';
2@import '_mixins';
3
4.no-notification {
5 display: flex;
6 justify-content: center;
7 align-items: center;
8 padding: 20px 0;
9}
10
11.notification {
12 display: flex;
13 align-items: center;
14 font-size: inherit;
15 padding: 15px 5px 15px 10px;
16 border-bottom: 1px solid $separator-border-color;
17 word-break: break-word;
18
19 &.unread {
20 background-color: rgba(0, 0, 0, 0.05);
21 }
22
23 my-global-icon {
24 width: 24px;
25 margin-right: 11px;
26 margin-left: 3px;
27
28 @include apply-svg-color(#333);
29 }
30
31 .avatar {
32 @include avatar(30px);
33
34 margin-right: 10px;
35 }
36
37 .message {
38 flex-grow: 1;
39
40 a {
41 font-weight: $font-semibold;
42 }
43 }
44
45 .from-date {
46 font-size: 0.85em;
47 color: pvar(--greyForegroundColor);
48 padding-left: 5px;
49 min-width: 70px;
50 text-align: right;
51 margin-left: auto;
52 }
53}
diff --git a/client/src/app/shared/shared-main/users/user-notifications.component.ts b/client/src/app/shared/shared-main/users/user-notifications.component.ts
new file mode 100644
index 000000000..6abd8b7d8
--- /dev/null
+++ b/client/src/app/shared/shared-main/users/user-notifications.component.ts
@@ -0,0 +1,100 @@
1import { Subject } from 'rxjs'
2import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
3import { ComponentPagination, hasMoreItems, Notifier } from '@app/core'
4import { UserNotificationType } from '@shared/models'
5import { UserNotification } from './user-notification.model'
6import { UserNotificationService } from './user-notification.service'
7
8@Component({
9 selector: 'my-user-notifications',
10 templateUrl: 'user-notifications.component.html',
11 styleUrls: [ 'user-notifications.component.scss' ]
12})
13export class UserNotificationsComponent implements OnInit {
14 @Input() ignoreLoadingBar = false
15 @Input() infiniteScroll = true
16 @Input() itemsPerPage = 20
17 @Input() markAllAsReadSubject: Subject<boolean>
18
19 @Output() notificationsLoaded = new EventEmitter()
20
21 notifications: UserNotification[] = []
22
23 // So we can access it in the template
24 UserNotificationType = UserNotificationType
25
26 componentPagination: ComponentPagination
27
28 onDataSubject = new Subject<any[]>()
29
30 constructor (
31 private userNotificationService: UserNotificationService,
32 private notifier: Notifier
33 ) { }
34
35 ngOnInit () {
36 this.componentPagination = {
37 currentPage: 1,
38 itemsPerPage: this.itemsPerPage, // Reset items per page, because of the @Input() variable
39 totalItems: null
40 }
41
42 this.loadMoreNotifications()
43
44 if (this.markAllAsReadSubject) {
45 this.markAllAsReadSubject.subscribe(() => this.markAllAsRead())
46 }
47 }
48
49 loadMoreNotifications () {
50 this.userNotificationService.listMyNotifications(this.componentPagination, undefined, this.ignoreLoadingBar)
51 .subscribe(
52 result => {
53 this.notifications = this.notifications.concat(result.data)
54 this.componentPagination.totalItems = result.total
55
56 this.notificationsLoaded.emit()
57
58 this.onDataSubject.next(result.data)
59 },
60
61 err => this.notifier.error(err.message)
62 )
63 }
64
65 onNearOfBottom () {
66 if (this.infiniteScroll === false) return
67
68 this.componentPagination.currentPage++
69
70 if (hasMoreItems(this.componentPagination)) {
71 this.loadMoreNotifications()
72 }
73 }
74
75 markAsRead (notification: UserNotification) {
76 if (notification.read) return
77
78 this.userNotificationService.markAsRead(notification)
79 .subscribe(
80 () => {
81 notification.read = true
82 },
83
84 err => this.notifier.error(err.message)
85 )
86 }
87
88 markAllAsRead () {
89 this.userNotificationService.markAllAsRead()
90 .subscribe(
91 () => {
92 for (const notification of this.notifications) {
93 notification.read = true
94 }
95 },
96
97 err => this.notifier.error(err.message)
98 )
99 }
100}