diff options
author | Chocobozzz <me@florianbigard.com> | 2019-02-20 10:16:04 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2019-02-20 10:16:04 +0100 |
commit | b28e4e5e080646ec67363cb0a16c9bd97ccffb35 (patch) | |
tree | a1af72cd2b0c7138bcaa4cb5f44e5db6d168e05d | |
parent | 28c8e63e55cad24b024fc1d05aa1cfc0257434e5 (diff) | |
download | PeerTube-b28e4e5e080646ec67363cb0a16c9bd97ccffb35.tar.gz PeerTube-b28e4e5e080646ec67363cb0a16c9bd97ccffb35.tar.zst PeerTube-b28e4e5e080646ec67363cb0a16c9bd97ccffb35.zip |
Add user notification animation
13 files changed, 123 insertions, 20 deletions
diff --git a/client/src/app/core/notification/user-notification-socket.service.ts b/client/src/app/core/notification/user-notification-socket.service.ts index 29337d3a7..493f03e35 100644 --- a/client/src/app/core/notification/user-notification-socket.service.ts +++ b/client/src/app/core/notification/user-notification-socket.service.ts | |||
@@ -2,7 +2,6 @@ import { Injectable } from '@angular/core' | |||
2 | import { environment } from '../../../environments/environment' | 2 | import { environment } from '../../../environments/environment' |
3 | import { UserNotification as UserNotificationServer } from '../../../../../shared' | 3 | import { UserNotification as UserNotificationServer } from '../../../../../shared' |
4 | import { Subject } from 'rxjs' | 4 | import { Subject } from 'rxjs' |
5 | import * as io from 'socket.io-client' | ||
6 | import { AuthService } from '../auth' | 5 | import { AuthService } from '../auth' |
7 | 6 | ||
8 | export type NotificationEvent = 'new' | 'read' | 'read-all' | 7 | export type NotificationEvent = 'new' | 'read' | 'read-all' |
diff --git a/client/src/app/menu/avatar-notification.component.html b/client/src/app/menu/avatar-notification.component.html index 4ef3f0e89..a5ef43d42 100644 --- a/client/src/app/menu/avatar-notification.component.html +++ b/client/src/app/menu/avatar-notification.component.html | |||
@@ -1,6 +1,6 @@ | |||
1 | <div | 1 | <div |
2 | [ngbPopover]="popContent" autoClose="outside" placement="bottom-left" container="body" popoverClass="popover-notifications" | 2 | [ngbPopover]="popContent" autoClose="outside" placement="bottom-left" container="body" popoverClass="popover-notifications" |
3 | i18n-title title="View your notifications" class="notification-avatar" #popover="ngbPopover" | 3 | i18n-title title="View your notifications" class="notification-avatar" #popover="ngbPopover" (hidden)="onPopoverHidden()" |
4 | > | 4 | > |
5 | <div *ngIf="unreadNotifications > 0" class="unread-notifications">{{ unreadNotifications }}</div> | 5 | <div *ngIf="unreadNotifications > 0" class="unread-notifications">{{ unreadNotifications }}</div> |
6 | 6 | ||
@@ -8,16 +8,25 @@ | |||
8 | </div> | 8 | </div> |
9 | 9 | ||
10 | <ng-template #popContent> | 10 | <ng-template #popContent> |
11 | <div class="notifications-header"> | 11 | <div class="content" [ngClass]="{ loaded: loaded }"> |
12 | <div i18n>Notifications</div> | 12 | <div class="notifications-header"> |
13 | <div i18n>Notifications</div> | ||
13 | 14 | ||
14 | <a | 15 | <a |
15 | i18n-title title="Update your notification preferences" class="glyphicon glyphicon-cog" | 16 | i18n-title title="Update your notification preferences" class="glyphicon glyphicon-cog" |
16 | routerLink="/my-account/settings" fragment="notifications" | 17 | routerLink="/my-account/settings" fragment="notifications" |
17 | ></a> | 18 | ></a> |
18 | </div> | 19 | </div> |
20 | |||
21 | <div *ngIf="!loaded" class="loader"> | ||
22 | <my-loader [loading]="!loaded"></my-loader> | ||
23 | </div> | ||
19 | 24 | ||
20 | <my-user-notifications [ignoreLoadingBar]="true" [infiniteScroll]="false" itemsPerPage="10"></my-user-notifications> | 25 | <my-user-notifications |
26 | [ignoreLoadingBar]="true" [infiniteScroll]="false" itemsPerPage="10" | ||
27 | (notificationsLoaded)="onNotificationLoaded()" | ||
28 | ></my-user-notifications> | ||
21 | 29 | ||
22 | <a class="all-notifications" routerLink="/my-account/notifications" i18n>See all your notifications</a> | 30 | <a *ngIf="loaded" class="all-notifications" routerLink="/my-account/notifications" i18n>See all your notifications</a> |
31 | </div> | ||
23 | </ng-template> | 32 | </ng-template> |
diff --git a/client/src/app/menu/avatar-notification.component.scss b/client/src/app/menu/avatar-notification.component.scss index e785db788..201668b6e 100644 --- a/client/src/app/menu/avatar-notification.component.scss +++ b/client/src/app/menu/avatar-notification.component.scss | |||
@@ -9,11 +9,27 @@ | |||
9 | padding: 0; | 9 | padding: 0; |
10 | font-size: 14px; | 10 | font-size: 14px; |
11 | font-family: $main-fonts; | 11 | font-family: $main-fonts; |
12 | overflow-y: auto; | 12 | overflow-y: scroll; |
13 | max-height: 500px; | ||
14 | width: 400px; | 13 | width: 400px; |
15 | box-shadow: 0 6px 14px rgba(0, 0, 0, 0.30); | 14 | box-shadow: 0 6px 14px rgba(0, 0, 0, 0.30); |
16 | 15 | ||
16 | .loader { | ||
17 | display: flex; | ||
18 | align-items: center; | ||
19 | justify-content: center; | ||
20 | |||
21 | padding: 5px 0; | ||
22 | } | ||
23 | |||
24 | .content { | ||
25 | max-height: 150px; | ||
26 | transition: max-height 0.15s ease-out; | ||
27 | |||
28 | &.loaded { | ||
29 | max-height: 500px; | ||
30 | } | ||
31 | } | ||
32 | |||
17 | .notifications-header { | 33 | .notifications-header { |
18 | display: flex; | 34 | display: flex; |
19 | justify-content: space-between; | 35 | justify-content: space-between; |
diff --git a/client/src/app/menu/avatar-notification.component.ts b/client/src/app/menu/avatar-notification.component.ts index 878c5c88c..a77a001ca 100644 --- a/client/src/app/menu/avatar-notification.component.ts +++ b/client/src/app/menu/avatar-notification.component.ts | |||
@@ -17,6 +17,7 @@ export class AvatarNotificationComponent implements OnInit, OnDestroy { | |||
17 | @Input() user: User | 17 | @Input() user: User |
18 | 18 | ||
19 | unreadNotifications = 0 | 19 | unreadNotifications = 0 |
20 | loaded = false | ||
20 | 21 | ||
21 | private notificationSub: Subscription | 22 | private notificationSub: Subscription |
22 | private routeSub: Subscription | 23 | private routeSub: Subscription |
@@ -54,6 +55,14 @@ export class AvatarNotificationComponent implements OnInit, OnDestroy { | |||
54 | this.popover.close() | 55 | this.popover.close() |
55 | } | 56 | } |
56 | 57 | ||
58 | onPopoverHidden () { | ||
59 | this.loaded = false | ||
60 | } | ||
61 | |||
62 | onNotificationLoaded () { | ||
63 | this.loaded = true | ||
64 | } | ||
65 | |||
57 | private async subscribeToNotifications () { | 66 | private async subscribeToNotifications () { |
58 | const obs = await this.userNotificationSocket.getMyNotificationsSocket() | 67 | const obs = await this.userNotificationSocket.getMyNotificationsSocket() |
59 | 68 | ||
diff --git a/client/src/app/shared/misc/loader.component.html b/client/src/app/shared/misc/loader.component.html index 38d06950e..b8b7ad343 100644 --- a/client/src/app/shared/misc/loader.component.html +++ b/client/src/app/shared/misc/loader.component.html | |||
@@ -1,3 +1,8 @@ | |||
1 | <div id="video-loading" *ngIf="loading"> | 1 | <div *ngIf="loading"> |
2 | <div class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></div> | 2 | <div class="lds-ring"> |
3 | <div></div> | ||
4 | <div></div> | ||
5 | <div></div> | ||
6 | <div></div> | ||
7 | </div> | ||
3 | </div> | 8 | </div> |
diff --git a/client/src/app/shared/misc/loader.component.scss b/client/src/app/shared/misc/loader.component.scss new file mode 100644 index 000000000..ddb64f07a --- /dev/null +++ b/client/src/app/shared/misc/loader.component.scss | |||
@@ -0,0 +1,45 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | // Thanks to https://loading.io/css/ (CC0 License) | ||
5 | |||
6 | .lds-ring { | ||
7 | display: inline-block; | ||
8 | position: relative; | ||
9 | width: 50px; | ||
10 | height: 50px; | ||
11 | } | ||
12 | |||
13 | .lds-ring div { | ||
14 | box-sizing: border-box; | ||
15 | display: block; | ||
16 | position: absolute; | ||
17 | width: 44px; | ||
18 | height: 44px; | ||
19 | margin: 6px; | ||
20 | border: 4px solid; | ||
21 | border-radius: 50%; | ||
22 | animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; | ||
23 | border-color: #999999 transparent transparent transparent; | ||
24 | } | ||
25 | |||
26 | .lds-ring div:nth-child(1) { | ||
27 | animation-delay: -0.45s; | ||
28 | } | ||
29 | |||
30 | .lds-ring div:nth-child(2) { | ||
31 | animation-delay: -0.3s; | ||
32 | } | ||
33 | |||
34 | .lds-ring div:nth-child(3) { | ||
35 | animation-delay: -0.15s; | ||
36 | } | ||
37 | |||
38 | @keyframes lds-ring { | ||
39 | 0% { | ||
40 | transform: rotate(0deg); | ||
41 | } | ||
42 | 100% { | ||
43 | transform: rotate(360deg); | ||
44 | } | ||
45 | } | ||
diff --git a/client/src/app/shared/misc/loader.component.ts b/client/src/app/shared/misc/loader.component.ts index f37d70c85..e3b1eea3a 100644 --- a/client/src/app/shared/misc/loader.component.ts +++ b/client/src/app/shared/misc/loader.component.ts | |||
@@ -2,10 +2,9 @@ import { Component, Input } from '@angular/core' | |||
2 | 2 | ||
3 | @Component({ | 3 | @Component({ |
4 | selector: 'my-loader', | 4 | selector: 'my-loader', |
5 | styleUrls: [ ], | 5 | styleUrls: [ './loader.component.scss' ], |
6 | templateUrl: './loader.component.html' | 6 | templateUrl: './loader.component.html' |
7 | }) | 7 | }) |
8 | |||
9 | export class LoaderComponent { | 8 | export class LoaderComponent { |
10 | @Input() loading: boolean | 9 | @Input() loading: boolean |
11 | } | 10 | } |
diff --git a/client/src/app/shared/misc/small-loader.component.html b/client/src/app/shared/misc/small-loader.component.html new file mode 100644 index 000000000..5a7cea738 --- /dev/null +++ b/client/src/app/shared/misc/small-loader.component.html | |||
@@ -0,0 +1,3 @@ | |||
1 | <div *ngIf="loading"> | ||
2 | <div class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></div> | ||
3 | </div> | ||
diff --git a/client/src/app/shared/misc/small-loader.component.ts b/client/src/app/shared/misc/small-loader.component.ts new file mode 100644 index 000000000..191877f14 --- /dev/null +++ b/client/src/app/shared/misc/small-loader.component.ts | |||
@@ -0,0 +1,11 @@ | |||
1 | import { Component, Input } from '@angular/core' | ||
2 | |||
3 | @Component({ | ||
4 | selector: 'my-small-loader', | ||
5 | styleUrls: [ ], | ||
6 | templateUrl: './small-loader.component.html' | ||
7 | }) | ||
8 | |||
9 | export class SmallLoaderComponent { | ||
10 | @Input() loading: boolean | ||
11 | } | ||
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index 6f8625c7e..1c4e3df1a 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts | |||
@@ -69,6 +69,7 @@ import { InstanceService } from '@app/shared/instance/instance.service' | |||
69 | import { HtmlRendererService, LinkifierService, MarkdownService } from '@app/shared/renderer' | 69 | import { HtmlRendererService, LinkifierService, MarkdownService } from '@app/shared/renderer' |
70 | import { ConfirmComponent } from '@app/shared/confirm/confirm.component' | 70 | import { ConfirmComponent } from '@app/shared/confirm/confirm.component' |
71 | import { GlobalIconComponent } from '@app/shared/icons/global-icon.component' | 71 | import { GlobalIconComponent } from '@app/shared/icons/global-icon.component' |
72 | import { SmallLoaderComponent } from '@app/shared/misc/small-loader.component' | ||
72 | 73 | ||
73 | @NgModule({ | 74 | @NgModule({ |
74 | imports: [ | 75 | imports: [ |
@@ -90,6 +91,7 @@ import { GlobalIconComponent } from '@app/shared/icons/global-icon.component' | |||
90 | 91 | ||
91 | declarations: [ | 92 | declarations: [ |
92 | LoaderComponent, | 93 | LoaderComponent, |
94 | SmallLoaderComponent, | ||
93 | VideoThumbnailComponent, | 95 | VideoThumbnailComponent, |
94 | VideoMiniatureComponent, | 96 | VideoMiniatureComponent, |
95 | FeedComponent, | 97 | FeedComponent, |
@@ -135,6 +137,7 @@ import { GlobalIconComponent } from '@app/shared/icons/global-icon.component' | |||
135 | KeysPipe, | 137 | KeysPipe, |
136 | 138 | ||
137 | LoaderComponent, | 139 | LoaderComponent, |
140 | SmallLoaderComponent, | ||
138 | VideoThumbnailComponent, | 141 | VideoThumbnailComponent, |
139 | VideoMiniatureComponent, | 142 | VideoMiniatureComponent, |
140 | FeedComponent, | 143 | FeedComponent, |
diff --git a/client/src/app/shared/users/user-notifications.component.ts b/client/src/app/shared/users/user-notifications.component.ts index b5f9fd399..ce43b604a 100644 --- a/client/src/app/shared/users/user-notifications.component.ts +++ b/client/src/app/shared/users/user-notifications.component.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | 1 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' |
2 | import { UserNotificationService } from '@app/shared/users/user-notification.service' | 2 | import { UserNotificationService } from '@app/shared/users/user-notification.service' |
3 | import { UserNotificationType } from '../../../../../shared' | 3 | import { UserNotificationType } from '../../../../../shared' |
4 | import { ComponentPagination, hasMoreItems } from '@app/shared/rest/component-pagination.model' | 4 | import { ComponentPagination, hasMoreItems } from '@app/shared/rest/component-pagination.model' |
@@ -15,6 +15,8 @@ export class UserNotificationsComponent implements OnInit { | |||
15 | @Input() infiniteScroll = true | 15 | @Input() infiniteScroll = true |
16 | @Input() itemsPerPage = 20 | 16 | @Input() itemsPerPage = 20 |
17 | 17 | ||
18 | @Output() notificationsLoaded = new EventEmitter() | ||
19 | |||
18 | notifications: UserNotification[] = [] | 20 | notifications: UserNotification[] = [] |
19 | 21 | ||
20 | // So we can access it in the template | 22 | // So we can access it in the template |
@@ -43,6 +45,8 @@ export class UserNotificationsComponent implements OnInit { | |||
43 | result => { | 45 | result => { |
44 | this.notifications = this.notifications.concat(result.data) | 46 | this.notifications = this.notifications.concat(result.data) |
45 | this.componentPagination.totalItems = result.total | 47 | this.componentPagination.totalItems = result.total |
48 | |||
49 | this.notificationsLoaded.emit() | ||
46 | }, | 50 | }, |
47 | 51 | ||
48 | err => this.notifier.error(err.message) | 52 | err => this.notifier.error(err.message) |
diff --git a/client/src/app/videos/+video-watch/comment/video-comments.component.html b/client/src/app/videos/+video-watch/comment/video-comments.component.html index 44016d8ad..7b941454a 100644 --- a/client/src/app/videos/+video-watch/comment/video-comments.component.html +++ b/client/src/app/videos/+video-watch/comment/video-comments.component.html | |||
@@ -54,7 +54,7 @@ | |||
54 | <ng-container i18n>View all {{ comment.totalReplies }} replies</ng-container> | 54 | <ng-container i18n>View all {{ comment.totalReplies }} replies</ng-container> |
55 | 55 | ||
56 | <span *ngIf="!threadLoading[comment.id]" class="glyphicon glyphicon-menu-down"></span> | 56 | <span *ngIf="!threadLoading[comment.id]" class="glyphicon glyphicon-menu-down"></span> |
57 | <my-loader class="comment-thread-loading" [loading]="threadLoading[comment.id]"></my-loader> | 57 | <my-small-loader class="comment-thread-loading" [loading]="threadLoading[comment.id]"></my-small-loader> |
58 | </div> | 58 | </div> |
59 | </div> | 59 | </div> |
60 | </div> | 60 | </div> |
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html index 6e18ab6a6..fffcc1275 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.html +++ b/client/src/app/videos/+video-watch/video-watch.component.html | |||
@@ -146,7 +146,7 @@ | |||
146 | <div class="video-info-description-more" *ngIf="completeDescriptionShown === false && video.description?.length >= 250" (click)="showMoreDescription()"> | 146 | <div class="video-info-description-more" *ngIf="completeDescriptionShown === false && video.description?.length >= 250" (click)="showMoreDescription()"> |
147 | <ng-container i18n>Show more</ng-container> | 147 | <ng-container i18n>Show more</ng-container> |
148 | <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-down"></span> | 148 | <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-down"></span> |
149 | <my-loader class="description-loading" [loading]="descriptionLoading"></my-loader> | 149 | <my-small-loader class="description-loading" [loading]="descriptionLoading"></my-small-loader> |
150 | </div> | 150 | </div> |
151 | 151 | ||
152 | <div *ngIf="completeDescriptionShown === true" (click)="showLessDescription()" class="video-info-description-more"> | 152 | <div *ngIf="completeDescriptionShown === true" (click)="showLessDescription()" class="video-info-description-more"> |