aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src
diff options
context:
space:
mode:
authorKimsible <1877318+kimsible@users.noreply.github.com>2020-12-13 14:54:12 +0100
committerGitHub <noreply@github.com>2020-12-13 14:54:12 +0100
commit51a83970061b4005343d2bfc4edb883318ef2ca6 (patch)
treec33753d0dc51bc534706b0839cf567d673294f29 /client/src
parent75594f474a3e04086b37a014d23e89780ca67458 (diff)
downloadPeerTube-51a83970061b4005343d2bfc4edb883318ef2ca6.tar.gz
PeerTube-51a83970061b4005343d2bfc4edb883318ef2ca6.tar.zst
PeerTube-51a83970061b4005343d2bfc4edb883318ef2ca6.zip
User dropdown and notifications popover improvements (#3344)
* hove user dropdown on avatar and username * rename avatar-notification to notification component * use a link on mobile for notification component * add profile user dropdown and mobile notifications link as reusable active link * replace markAllAsRead inbox glyphicon to ok in notification popover * remove keyboard shortcuts from user dropdown on mobile * use common bell icon instead of inbox-full for notifications * remove duplicated notification in user dropdown since the bell appears on the right * adjust sensitive icon in user dropdown * align vertically user buttons popover and dropdown * adjust ellipsis on user display name and username in menu * adjust notification bell for mobile in menu * display background of user dropdown avatar and username for touchscreens * add right arrow indicator on mobile Co-authored-by: kimsible <kimsible@users.noreply.github.com> Co-authored-by: Rigel Kent <sendmemail@rigelk.eu>
Diffstat (limited to 'client/src')
-rw-r--r--client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html2
-rw-r--r--client/src/app/app.module.ts4
-rw-r--r--client/src/app/menu/index.ts2
-rw-r--r--client/src/app/menu/menu.component.html40
-rw-r--r--client/src/app/menu/menu.component.scss133
-rw-r--r--client/src/app/menu/menu.component.ts52
-rw-r--r--client/src/app/menu/notification.component.html (renamed from client/src/app/menu/avatar-notification.component.html)19
-rw-r--r--client/src/app/menu/notification.component.scss (renamed from client/src/app/menu/avatar-notification.component.scss)96
-rw-r--r--client/src/app/menu/notification.component.ts (renamed from client/src/app/menu/avatar-notification.component.ts)36
-rw-r--r--client/src/app/shared/shared-icons/global-icon.component.ts2
-rw-r--r--client/src/assets/images/feather/bell.svg1
-rw-r--r--client/src/assets/images/feather/inbox-full.svg1
12 files changed, 285 insertions, 103 deletions
diff --git a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html
index a60ed885d..f0e9f4010 100644
--- a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html
+++ b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html
@@ -15,7 +15,7 @@
15 15
16 <button class="btn ml-auto" [disabled]="!hasUnreadNotifications()" (click)="markAllAsRead()"> 16 <button class="btn ml-auto" [disabled]="!hasUnreadNotifications()" (click)="markAllAsRead()">
17 <ng-container *ngIf="hasUnreadNotifications()"> 17 <ng-container *ngIf="hasUnreadNotifications()">
18 <my-global-icon iconName="inbox-full" aria-hidden="true"></my-global-icon> 18 <my-global-icon iconName="tick" aria-hidden="true"></my-global-icon>
19 19
20 <span i18n>Mark all as read</span> 20 <span i18n>Mark all as read</span>
21 </ng-container> 21 </ng-container>
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts
index 3f874856d..98dec4244 100644
--- a/client/src/app/app.module.ts
+++ b/client/src/app/app.module.ts
@@ -11,7 +11,7 @@ import { CoreModule } from './core'
11import { EmptyComponent } from './empty.component' 11import { EmptyComponent } from './empty.component'
12import { HeaderComponent, SearchTypeaheadComponent, SuggestionComponent } from './header' 12import { HeaderComponent, SearchTypeaheadComponent, SuggestionComponent } from './header'
13import { HighlightPipe } from './header/highlight.pipe' 13import { HighlightPipe } from './header/highlight.pipe'
14import { AvatarNotificationComponent, LanguageChooserComponent, MenuComponent } from './menu' 14import { NotificationComponent, LanguageChooserComponent, MenuComponent } from './menu'
15import { ConfirmComponent } from './modal/confirm.component' 15import { ConfirmComponent } from './modal/confirm.component'
16import { CustomModalComponent } from './modal/custom-modal.component' 16import { CustomModalComponent } from './modal/custom-modal.component'
17import { InstanceConfigWarningModalComponent } from './modal/instance-config-warning-modal.component' 17import { InstanceConfigWarningModalComponent } from './modal/instance-config-warning-modal.component'
@@ -35,7 +35,7 @@ registerLocaleData(localeOc, 'oc')
35 MenuComponent, 35 MenuComponent,
36 LanguageChooserComponent, 36 LanguageChooserComponent,
37 QuickSettingsModalComponent, 37 QuickSettingsModalComponent,
38 AvatarNotificationComponent, 38 NotificationComponent,
39 HeaderComponent, 39 HeaderComponent,
40 SearchTypeaheadComponent, 40 SearchTypeaheadComponent,
41 SuggestionComponent, 41 SuggestionComponent,
diff --git a/client/src/app/menu/index.ts b/client/src/app/menu/index.ts
index 39dbde750..7f62c1100 100644
--- a/client/src/app/menu/index.ts
+++ b/client/src/app/menu/index.ts
@@ -1,3 +1,3 @@
1export * from './language-chooser.component' 1export * from './language-chooser.component'
2export * from './avatar-notification.component' 2export * from './notification.component'
3export * from './menu.component' 3export * from './menu.component'
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html
index bba5fdadf..5b1c24c7a 100644
--- a/client/src/app/menu/menu.component.html
+++ b/client/src/app/menu/menu.component.html
@@ -3,32 +3,28 @@
3 <div class="top-menu"> 3 <div class="top-menu">
4 <div *ngIf="isLoggedIn" class="logged-in-block"> 4 <div *ngIf="isLoggedIn" class="logged-in-block">
5 <div> 5 <div>
6 <my-avatar-notification [user]="user" (navigate)="onActiveLinkScrollToAnchor($event)"></my-avatar-notification> 6 <div class="logged-in-more" ngbDropdown #dropdown="ngbDropdown" placement="bottom-left" [container]="dropdownContainer" (openChange)="onDropdownOpenChange($event)" autoClose="outside">
7 7 <div ngbDropdownToggle>
8 <div class="logged-in-info"> 8 <img [src]="user.accountAvatarUrl" alt="Avatar" />
9 <a *ngIf="user.account" [routerLink]="[ '/accounts', user.account.nameWithHost ]" class="logged-in-display-name">{{ user.account?.displayName }}</a> 9 <div class="logged-in-info">
10 <a *ngIf="!user.account" routerLink="/my-account/settings" class="logged-in-display-name">{{ user.account?.displayName }}</a> 10 <div class="logged-in-display-name">{{ user.account?.displayName }}</div>
11 11
12 <div class="logged-in-username">@{{ user.username }}</div> 12 <div class="logged-in-username">@{{ user.username }}</div>
13 </div> 13 </div>
14 14
15 <div class="logged-in-more" ngbDropdown [placement]="loggedInMorePlacement" container="body" autoClose="outside"> 15 <div class="dropdown-toggle-indicator">
16 <my-global-icon iconName="more-vertical" ngbDropdownToggle role="button"></my-global-icon> 16 <span class="glyphicon glyphicon-chevron-down"></span>
17 </div>
18 </div>
17 19
18 <div ngbDropdownMenu> 20 <div ngbDropdownMenu>
19 <a *ngIf="user.account" ngbDropdownItem ngbDropdownToggle class="dropdown-item" [routerLink]="[ '/accounts', user.account.nameWithHost ]"> 21 <a *ngIf="user.account" ngbDropdownItem ngbDropdownToggle class="dropdown-item" [routerLink]="[ '/accounts', user.account.nameWithHost ]"
22 #profile (click)="onActiveLinkScrollToAnchor(profile)">
20 <my-global-icon iconName="go" aria-hidden="true"></my-global-icon> <ng-container i18n>Public profile</ng-container> 23 <my-global-icon iconName="go" aria-hidden="true"></my-global-icon> <ng-container i18n>Public profile</ng-container>
21 </a> 24 </a>
22 25
23 <div class="dropdown-divider"></div> 26 <div class="dropdown-divider"></div>
24 27
25 <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" routerLink="/my-account/notifications"
26 #notifications (click)="onActiveLinkScrollToAnchor(notifications)">
27 <my-global-icon iconName="inbox-full" aria-hidden="true"></my-global-icon> <ng-container i18n>My notifications</ng-container>
28 </a>
29
30 <div class="dropdown-divider"></div>
31
32 <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" (click)="openLanguageChooser()"> 28 <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" (click)="openLanguageChooser()">
33 <my-global-icon iconName="language" aria-hidden="true"></my-global-icon> 29 <my-global-icon iconName="language" aria-hidden="true"></my-global-icon>
34 <span i18n>Interface:</span> 30 <span i18n>Interface:</span>
@@ -42,7 +38,7 @@
42 <span class="ml-auto text-muted">{{ videoLanguages.join(', ') }}</span> 38 <span class="ml-auto text-muted">{{ videoLanguages.join(', ') }}</span>
43 </a> 39 </a>
44 40
45 <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" routerLink="/my-account/settings" 41 <a ngbDropdownItem ngbDropdownToggle class="dropdown-item settings-sensitive" routerLink="/my-account/settings"
46 fragment="video-sensitive-content-policy" #settingsSensitiveContentPolicy 42 fragment="video-sensitive-content-policy" #settingsSensitiveContentPolicy
47 (click)="onActiveLinkScrollToAnchor(settingsSensitiveContentPolicy)"> 43 (click)="onActiveLinkScrollToAnchor(settingsSensitiveContentPolicy)">
48 <my-global-icon class="hover-display-toggle" [ngClass]="{ 'not-displayed': user.nsfwPolicy === 'display' }" iconName="sensitive" aria-hidden="true"></my-global-icon> 44 <my-global-icon class="hover-display-toggle" [ngClass]="{ 'not-displayed': user.nsfwPolicy === 'display' }" iconName="sensitive" aria-hidden="true"></my-global-icon>
@@ -60,7 +56,7 @@
60 56
61 <div class="dropdown-divider"></div> 57 <div class="dropdown-divider"></div>
62 58
63 <a ngbDropdownItem ngbDropdownToggle class="dropdown-item" (click)="openHotkeysCheatSheet()"> 59 <a *ngIf="!isInMobileView" ngbDropdownItem ngbDropdownToggle class="dropdown-item" (click)="openHotkeysCheatSheet()">
64 <my-global-icon iconName="command" aria-hidden="true"></my-global-icon> 60 <my-global-icon iconName="command" aria-hidden="true"></my-global-icon>
65 <ng-container i18n>Keyboard shortcuts</ng-container> 61 <ng-container i18n>Keyboard shortcuts</ng-container>
66 </a> 62 </a>
@@ -71,6 +67,8 @@
71 </a> 67 </a>
72 </div> 68 </div>
73 </div> 69 </div>
70
71 <my-notification (navigate)="onActiveLinkScrollToAnchor($event)"></my-notification>
74 </div> 72 </div>
75 73
76 <div class="logged-in-menu"> 74 <div class="logged-in-menu">
diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss
index e79ecb5c7..89dc26e87 100644
--- a/client/src/app/menu/menu.component.scss
+++ b/client/src/app/menu/menu.component.scss
@@ -88,47 +88,118 @@ menu {
88 height: 80px; 88 height: 80px;
89 display: flex; 89 display: flex;
90 align-items: center; 90 align-items: center;
91 justify-content: center; 91 justify-content: left;
92 92
93 .logged-in-info { 93 .logged-in-more {
94 @include ellipsis; 94 $main-radius: 25px;
95 95
96 flex-grow: 1; 96 margin-left: 13px;
97 border-radius: $main-radius;
98 transition: all .1s ease-in-out;
99 cursor: pointer;
97 100
98 .logged-in-display-name { 101 *, & {
99 font-size: 16px; 102 line-height: 1;
100 font-weight: $font-semibold; 103 }
101 color: pvar(--menuForegroundColor);
102 cursor: pointer;
103 104
104 @include disable-default-a-behaviour; 105 &.show {
106 background-color: rgba(255, 255, 255, 0.20);
107 box-shadow: inset 0 3px 5px rgba(0, 0, 0, .325);
105 } 108 }
106 109
107 .logged-in-username { 110 @mixin display-hints($is-mobile: false) {
108 @include ellipsis; 111 background-color: rgba(255, 255, 255, 0.15);
109 112
110 font-size: 13px; 113 @if $is-mobile {
111 color: #C6C6C6; 114 .dropdown-toggle-indicator {
112 max-width: 140px; 115 display: inherit !important;
113 cursor: pointer; 116 }
117 .dropdown-toggle:first-child {
118 padding-right: 30px !important;
119 }
120 }
114 } 121 }
115 }
116 122
117 .logged-in-more { 123 &:hover {
118 margin-right: 20px; 124 @include display-hints;
125 }
119 126
120 my-global-icon.dropdown-toggle { 127 /* smartphones and touchscreens */
121 cursor: pointer; 128 @media (hover: none) and (pointer: coarse) {
129 @include display-hints($is-mobile: true);
122 130
131 /* fill space when on mobile */
132 max-width: calc(100% - 80px);
133 .dropdown-toggle {
134 max-width: 100%;
135 }
136 .logged-in-info {
137 max-width: calc(100% - 45px) !important;
138 }
139
140 }
141
142 .dropdown-toggle-indicator {
143 position: relative;
144 width: 0;
145 display: none;
146
147 span {
148 position: absolute;
149 right: -35px;
150 top: -8px;
151 color: grey;
152 width: $main-radius;
153 }
154 }
155
156 .dropdown-toggle {
123 &::after { 157 &::after {
124 border: none; 158 border: none;
125 } 159 }
160 }
126 161
127 ::ng-deep { 162 .dropdown-toggle:first-child {
128 @include apply-svg-color(pvar(--menuForegroundColor)); 163 display: inline-flex;
164 align-items: center;
165 padding: 5px 7px;
166 }
167
168 img {
169 @include avatar(34px);
170
171 margin-right: 10px;
172 }
173
174 .logged-in-info {
175 max-width: 105px;
176
177 flex-grow: 1;
178
179 .logged-in-display-name,
180 .logged-in-username {
181 @include ellipsis;
182 }
183
184 .logged-in-display-name {
185 font-size: 16px;
186 font-weight: $font-semibold;
187 color: pvar(--menuForegroundColor);
188
189 @include disable-default-a-behaviour;
190 }
191
192 .logged-in-username {
193 font-size: 13px;
194 color: #C6C6C6;
129 } 195 }
130 } 196 }
131 } 197 }
198
199 my-notification {
200 margin-left: auto;
201 margin-right: 15px;
202 }
132 } 203 }
133 204
134 .logged-in-menu { 205 .logged-in-menu {
@@ -343,6 +414,12 @@ menu {
343 my-global-icon.hover-display-toggle { 414 my-global-icon.hover-display-toggle {
344 display: none; 415 display: none;
345 } 416 }
417
418 &.settings-sensitive {
419 my-global-icon ::ng-deep svg {
420 margin-top: 2px !important;
421 }
422 }
346 } 423 }
347} 424}
348 425
@@ -364,4 +441,14 @@ menu {
364 .top-menu, .footer { 441 .top-menu, .footer {
365 width: 100% !important; 442 width: 100% !important;
366 } 443 }
444
445 .dropdown-menu {
446 width: calc(100vw - 30px);
447 }
448
449 .dropdown-item:hover, .dropdown-item:active {
450 &.settings-sensitive my-global-icon ::ng-deep svg {
451 margin-top: 0px !important;
452 }
453 }
367} 454}
diff --git a/client/src/app/menu/menu.component.ts b/client/src/app/menu/menu.component.ts
index bdc95127b..50ff0e2b3 100644
--- a/client/src/app/menu/menu.component.ts
+++ b/client/src/app/menu/menu.component.ts
@@ -9,6 +9,7 @@ import { AuthService, AuthStatus, AuthUser, MenuService, RedirectService, Screen
9import { LanguageChooserComponent } from '@app/menu/language-chooser.component' 9import { LanguageChooserComponent } from '@app/menu/language-chooser.component'
10import { QuickSettingsModalComponent } from '@app/modal/quick-settings-modal.component' 10import { QuickSettingsModalComponent } from '@app/modal/quick-settings-modal.component'
11import { ServerConfig, UserRight, VideoConstant } from '@shared/models' 11import { ServerConfig, UserRight, VideoConstant } from '@shared/models'
12import { NgbDropdown, NgbDropdownConfig } from '@ng-bootstrap/ng-bootstrap'
12 13
13const logger = debug('peertube:menu:MenuComponent') 14const logger = debug('peertube:menu:MenuComponent')
14 15
@@ -20,6 +21,7 @@ const logger = debug('peertube:menu:MenuComponent')
20export class MenuComponent implements OnInit { 21export class MenuComponent implements OnInit {
21 @ViewChild('languageChooserModal', { static: true }) languageChooserModal: LanguageChooserComponent 22 @ViewChild('languageChooserModal', { static: true }) languageChooserModal: LanguageChooserComponent
22 @ViewChild('quickSettingsModal', { static: true }) quickSettingsModal: QuickSettingsModalComponent 23 @ViewChild('quickSettingsModal', { static: true }) quickSettingsModal: QuickSettingsModalComponent
24 @ViewChild('dropdown') dropdown: NgbDropdown
23 25
24 user: AuthUser 26 user: AuthUser
25 isLoggedIn: boolean 27 isLoggedIn: boolean
@@ -30,8 +32,6 @@ export class MenuComponent implements OnInit {
30 videoLanguages: string[] = [] 32 videoLanguages: string[] = []
31 nsfwPolicy: string 33 nsfwPolicy: string
32 34
33 loggedInMorePlacement: string
34
35 currentInterfaceLanguage: string 35 currentInterfaceLanguage: string
36 36
37 private languages: VideoConstant<string>[] = [] 37 private languages: VideoConstant<string>[] = []
@@ -54,8 +54,27 @@ export class MenuComponent implements OnInit {
54 private hotkeysService: HotkeysService, 54 private hotkeysService: HotkeysService,
55 private screenService: ScreenService, 55 private screenService: ScreenService,
56 private menuService: MenuService, 56 private menuService: MenuService,
57 private dropdownConfig: NgbDropdownConfig,
57 private router: Router 58 private router: Router
58 ) { } 59 ) {
60 this.dropdownConfig.container = 'body'
61 }
62
63 get isInMobileView () {
64 return this.screenService.isInMobileView()
65 }
66
67 get dropdownContainer () {
68 if (this.isInMobileView) {
69 return null
70 } else {
71 return this.dropdownConfig.container
72 }
73 }
74
75 get language () {
76 return this.languageChooserModal.getCurrentLanguage()
77 }
59 78
60 get instanceName () { 79 get instanceName () {
61 return this.serverConfig.instance.name 80 return this.serverConfig.instance.name
@@ -76,10 +95,6 @@ export class MenuComponent implements OnInit {
76 95
77 this.computeAdminAccess() 96 this.computeAdminAccess()
78 97
79 this.loggedInMorePlacement = this.screenService.isInMobileView()
80 ? 'left-top auto'
81 : 'right-top auto'
82
83 this.currentInterfaceLanguage = this.languageChooserModal.getCurrentLanguage() 98 this.currentInterfaceLanguage = this.languageChooserModal.getCurrentLanguage()
84 99
85 this.authService.loginChangedSource.subscribe( 100 this.authService.loginChangedSource.subscribe(
@@ -203,6 +218,29 @@ export class MenuComponent implements OnInit {
203 } 218 }
204 } 219 }
205 220
221 // Lock menu scroll when menu scroll to avoid fleeing / detached dropdown
222 onMenuScrollEvent () {
223 document.querySelector('menu').scrollTo(0, 0)
224 }
225
226 onDropdownOpenChange (opened: boolean) {
227 if (this.screenService.isInMobileView()) return
228
229 // Close dropdown when window scroll to avoid dropdown quick jump for re-position
230 const onWindowScroll = () => {
231 this.dropdown.close()
232 window.removeEventListener('scroll', onWindowScroll)
233 }
234
235 if (opened) {
236 window.addEventListener('scroll', onWindowScroll)
237 document.querySelector('menu').scrollTo(0, 0) // Reset menu scroll to easy lock
238 document.querySelector('menu').addEventListener('scroll', this.onMenuScrollEvent)
239 } else {
240 document.querySelector('menu').removeEventListener('scroll', this.onMenuScrollEvent)
241 }
242 }
243
206 private buildUserLanguages () { 244 private buildUserLanguages () {
207 if (!this.user) { 245 if (!this.user) {
208 this.videoLanguages = [] 246 this.videoLanguages = []
diff --git a/client/src/app/menu/avatar-notification.component.html b/client/src/app/menu/notification.component.html
index b24bd0309..beda1c43c 100644
--- a/client/src/app/menu/avatar-notification.component.html
+++ b/client/src/app/menu/notification.component.html
@@ -1,10 +1,19 @@
1<div 1<div
2 [ngbPopover]="popContent" autoClose="outside" placement="bottom-left" container="body" popoverClass="popover-notifications" 2 [ngbPopover]="popContent" autoClose="outside" placement="bottom" container={this} popoverClass="popover-notifications"
3 i18n-title title="View your notifications" class="notification-avatar" #popover="ngbPopover" (hidden)="onPopoverHidden()" 3 i18n-title title="View your notifications" [ngClass]="{ 'notification-inbox-popover': true, 'shown': opened, 'hidden': isInMobileView }"
4 #popover="ngbPopover" (shown)="onPopoverShown()" (hidden)="onPopoverHidden()"
4> 5>
5 <div *ngIf="unreadNotifications > 0" class="unread-notifications">{{ unreadNotifications }}</div> 6 <div *ngIf="unreadNotifications > 0" class="unread-notifications">{{ unreadNotifications }}</div>
6 7
7 <img [src]="user.accountAvatarUrl" alt="Avatar" /> 8 <my-global-icon iconName="bell"></my-global-icon>
9</div>
10
11<div *ngIf="isInMobileView" i18n-title title="View your notifications" class="notification-inbox-link">
12 <div *ngIf="unreadNotifications > 0" class="unread-notifications">{{ unreadNotifications }}</div>
13
14 <a routerLink="/my-account/notifications" routerLinkActive="active" #link (click)="onNavigate(link)">
15 <my-global-icon iconName="bell"></my-global-icon>
16 </a>
8</div> 17</div>
9 18
10<ng-template #popContent> 19<ng-template #popContent>
@@ -15,7 +24,7 @@
15 <div> 24 <div>
16 <button 25 <button
17 *ngIf="unreadNotifications" 26 *ngIf="unreadNotifications"
18 i18n-title title="Mark all as read" class="glyphicon glyphicon-inbox mr-2" 27 i18n-title title="Mark all as read" class="glyphicon glyphicon-ok mr-2"
19 (click)="markAllAsRead()" 28 (click)="markAllAsRead()"
20 ></button> 29 ></button>
21 <a 30 <a
@@ -36,7 +45,7 @@
36 ></my-user-notifications> 45 ></my-user-notifications>
37 46
38 <a *ngIf="loaded" class="all-notifications" routerLink="/my-account/notifications" #notifications (click)="onNavigate(notifications)"> 47 <a *ngIf="loaded" class="all-notifications" routerLink="/my-account/notifications" #notifications (click)="onNavigate(notifications)">
39 <my-global-icon class="mr-1" iconName="inbox-full" aria-hidden="true"></my-global-icon> 48 <my-global-icon class="mr-1" iconName="bell" aria-hidden="true"></my-global-icon>
40 <span i18n>See all your notifications</span> 49 <span i18n>See all your notifications</span>
41 </a> 50 </a>
42 </div> 51 </div>
diff --git a/client/src/app/menu/avatar-notification.component.scss b/client/src/app/menu/notification.component.scss
index 88f2b6296..40feb9e66 100644
--- a/client/src/app/menu/avatar-notification.component.scss
+++ b/client/src/app/menu/notification.component.scss
@@ -1,17 +1,62 @@
1@import '_variables'; 1@import '_variables';
2@import '_mixins'; 2@import '_mixins';
3 3
4
5.notification-inbox-popover {
6 padding: 10px;
7}
8
9.notification-inbox-link a {
10 padding: 13px 10px;
11}
12
13.notification-inbox-popover,
14.notification-inbox-link a {
15 @include apply-svg-color(#808080);
16 ::ng-deep {
17 svg {
18 transition: color .1s ease-in-out;
19 }
20 }
21
22 transition: all .1s ease-in-out;
23 border-radius: 25px;
24 cursor: pointer;
25
26 &:hover, &:active {
27 background-color: rgba(255, 255, 255, 0.15);
28 @include apply-svg-color(#fff);
29 }
30}
31
32.notification-inbox-popover.shown,
33.notification-inbox-link a.active {
34 @include apply-svg-color(#fff);
35
36 background-color: rgba(255, 255, 255, 0.28);
37 box-shadow: inset 0 3px 5px rgba(0, 0, 0, .325);
38}
39
40.notification-inbox-popover.hidden {
41 display: none;
42}
43
4::ng-deep { 44::ng-deep {
5 .popover-notifications.popover { 45 .popover-notifications.popover {
6 max-width: none; 46 max-width: none;
47 top: -6px !important;
7 left: 7px !important; 48 left: 7px !important;
8 49
50 .arrow {
51 display: none;
52 }
53
9 .popover-body { 54 .popover-body {
10 padding: 0; 55 padding: 0;
11 font-size: 14px; 56 font-size: 14px;
12 font-family: $main-fonts; 57 font-family: $main-fonts;
13 width: 400px; 58 width: 400px;
14 box-shadow: 0 6px 14px rgba(0, 0, 0, 0.30); 59 box-shadow: 0 2px 6px rgba(0, 0, 0, 0.30);
15 60
16 .loader { 61 .loader {
17 display: flex; 62 display: flex;
@@ -42,19 +87,22 @@
42 display: flex; 87 display: flex;
43 justify-content: space-between; 88 justify-content: space-between;
44 89
45 background-color: rgba(0, 0, 0, 0.10); 90 border-bottom: 1px solid rgba(0, 0, 0, 0.1);
46 align-items: center; 91 align-items: center;
47 padding: 0 10px; 92 padding: 0 12px;
48 font-size: 16px; 93 font-size: 14px;
49 min-height: 50px; 94 font-weight: bold;
95 color: rgba(0, 0, 0, 0.5);
96 text-transform: uppercase;
97 min-height: 40px;
50 98
51 a { 99 a {
52 @include disable-default-a-behaviour; 100 @include disable-default-a-behaviour;
53 } 101 }
54 102
55 button { 103 button {
56 @include peertube-button; 104 @include peertube-button;
57 105
58 padding: 0; 106 padding: 0;
59 background: transparent; 107 background: transparent;
60 } 108 }
@@ -82,25 +130,23 @@
82 } 130 }
83} 131}
84 132
85.notification-avatar { 133.notification-inbox-popover, .notification-inbox-link {
86 cursor: pointer; 134 cursor: pointer;
87 position: relative; 135 position: relative;
88 136
89 img,
90 .unread-notifications { 137 .unread-notifications {
91 margin-left: 20px; 138 margin-left: 20px;
92 } 139 }
93 140
94 img {
95 @include avatar(34px);
96
97 margin-right: 10px;
98 }
99
100 .unread-notifications { 141 .unread-notifications {
101 position: absolute; 142 position: absolute;
102 top: -5px; 143 top: 6px;
103 left: -5px; 144 left: 0;
145
146 @media screen and (max-width: $mobile-view) {
147 top: -4px;
148 left: -2px;
149 }
104 150
105 display: flex; 151 display: flex;
106 align-items: center; 152 align-items: center;
@@ -116,19 +162,3 @@
116 height: 15px; 162 height: 15px;
117 } 163 }
118} 164}
119
120@media screen and (max-width: $mobile-view) {
121 ::ng-deep {
122 .popover-notifications.popover {
123 left: unset !important;
124
125 .arrow {
126 left: calc(2em + 7px);
127 }
128
129 .popover-body {
130 width: 100vw;
131 }
132 }
133 }
134}
diff --git a/client/src/app/menu/avatar-notification.component.ts b/client/src/app/menu/notification.component.ts
index ed3ffc2d8..b7d9e9abb 100644
--- a/client/src/app/menu/avatar-notification.component.ts
+++ b/client/src/app/menu/notification.component.ts
@@ -1,24 +1,24 @@
1import { Subject, Subscription } from 'rxjs' 1import { Subject, Subscription } from 'rxjs'
2import { filter } from 'rxjs/operators' 2import { filter } from 'rxjs/operators'
3import { Component, EventEmitter, Input, Output, OnDestroy, OnInit, ViewChild } from '@angular/core' 3import { Component, EventEmitter, Output, OnDestroy, OnInit, ViewChild } from '@angular/core'
4import { NavigationEnd, Router } from '@angular/router' 4import { NavigationEnd, Router } from '@angular/router'
5import { Notifier, User, PeerTubeSocket } from '@app/core' 5import { Notifier, PeerTubeSocket, ScreenService } from '@app/core'
6import { UserNotificationService } from '@app/shared/shared-main' 6import { UserNotificationService } from '@app/shared/shared-main'
7import { NgbPopover } from '@ng-bootstrap/ng-bootstrap' 7import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
8 8
9@Component({ 9@Component({
10 selector: 'my-avatar-notification', 10 selector: 'my-notification',
11 templateUrl: './avatar-notification.component.html', 11 templateUrl: './notification.component.html',
12 styleUrls: [ './avatar-notification.component.scss' ] 12 styleUrls: [ './notification.component.scss' ]
13}) 13})
14export class AvatarNotificationComponent implements OnInit, OnDestroy { 14export class NotificationComponent implements OnInit, OnDestroy {
15 @ViewChild('popover', { static: true }) popover: NgbPopover 15 @ViewChild('popover', { static: true }) popover: NgbPopover
16 16
17 @Input() user: User
18 @Output() navigate = new EventEmitter<HTMLAnchorElement>() 17 @Output() navigate = new EventEmitter<HTMLAnchorElement>()
19 18
20 unreadNotifications = 0 19 unreadNotifications = 0
21 loaded = false 20 loaded = false
21 opened = false
22 22
23 markAllAsReadSubject = new Subject<boolean>() 23 markAllAsReadSubject = new Subject<boolean>()
24 24
@@ -27,6 +27,7 @@ export class AvatarNotificationComponent implements OnInit, OnDestroy {
27 27
28 constructor ( 28 constructor (
29 private userNotificationService: UserNotificationService, 29 private userNotificationService: UserNotificationService,
30 private screenService: ScreenService,
30 private peertubeSocket: PeerTubeSocket, 31 private peertubeSocket: PeerTubeSocket,
31 private notifier: Notifier, 32 private notifier: Notifier,
32 private router: Router 33 private router: Router
@@ -54,12 +55,31 @@ export class AvatarNotificationComponent implements OnInit, OnDestroy {
54 if (this.routeSub) this.routeSub.unsubscribe() 55 if (this.routeSub) this.routeSub.unsubscribe()
55 } 56 }
56 57
58 get isInMobileView () {
59 return this.screenService.isInMobileView()
60 }
61
57 closePopover () { 62 closePopover () {
58 this.popover.close() 63 this.popover.close()
59 } 64 }
60 65
66 onPopoverShown () {
67 this.opened = true
68
69 document.querySelector('menu').scrollTo(0, 0) // Reset menu scroll to easy lock
70 document.querySelector('menu').addEventListener('scroll', this.onMenuScrollEvent)
71 }
72
61 onPopoverHidden () { 73 onPopoverHidden () {
62 this.loaded = false 74 this.loaded = false
75 this.opened = false
76
77 document.querySelector('menu').removeEventListener('scroll', this.onMenuScrollEvent)
78 }
79
80 // Lock menu scroll when menu scroll to avoid fleeing / detached dropdown
81 onMenuScrollEvent () {
82 document.querySelector('menu').scrollTo(0, 0)
63 } 83 }
64 84
65 onNotificationLoaded () { 85 onNotificationLoaded () {
@@ -67,6 +87,7 @@ export class AvatarNotificationComponent implements OnInit, OnDestroy {
67 } 87 }
68 88
69 onNavigate (link: HTMLAnchorElement) { 89 onNavigate (link: HTMLAnchorElement) {
90 this.closePopover()
70 this.navigate.emit(link) 91 this.navigate.emit(link)
71 } 92 }
72 93
@@ -83,5 +104,4 @@ export class AvatarNotificationComponent implements OnInit, OnDestroy {
83 if (data.type === 'read-all') return this.unreadNotifications = 0 104 if (data.type === 'read-all') return this.unreadNotifications = 0
84 }) 105 })
85 } 106 }
86
87} 107}
diff --git a/client/src/app/shared/shared-icons/global-icon.component.ts b/client/src/app/shared/shared-icons/global-icon.component.ts
index 53a2aee9a..0924b8119 100644
--- a/client/src/app/shared/shared-icons/global-icon.component.ts
+++ b/client/src/app/shared/shared-icons/global-icon.component.ts
@@ -36,7 +36,7 @@ const icons = {
36 'clock': require('!!raw-loader?!../../../assets/images/feather/clock.svg').default, 36 'clock': require('!!raw-loader?!../../../assets/images/feather/clock.svg').default,
37 'cog': require('!!raw-loader?!../../../assets/images/feather/cog.svg').default, 37 'cog': require('!!raw-loader?!../../../assets/images/feather/cog.svg').default,
38 'delete': require('!!raw-loader?!../../../assets/images/feather/delete.svg').default, 38 'delete': require('!!raw-loader?!../../../assets/images/feather/delete.svg').default,
39 'inbox-full': require('!!raw-loader?!../../../assets/images/feather/inbox-full.svg').default, 39 'bell': require('!!raw-loader?!../../../assets/images/feather/bell.svg').default,
40 'sign-out': require('!!raw-loader?!../../../assets/images/feather/log-out.svg').default, 40 'sign-out': require('!!raw-loader?!../../../assets/images/feather/log-out.svg').default,
41 'sign-in': require('!!raw-loader?!../../../assets/images/feather/log-in.svg').default, 41 'sign-in': require('!!raw-loader?!../../../assets/images/feather/log-in.svg').default,
42 'download': require('!!raw-loader?!../../../assets/images/feather/download.svg').default, 42 'download': require('!!raw-loader?!../../../assets/images/feather/download.svg').default,
diff --git a/client/src/assets/images/feather/bell.svg b/client/src/assets/images/feather/bell.svg
new file mode 100644
index 000000000..bba561c19
--- /dev/null
+++ b/client/src/assets/images/feather/bell.svg
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-bell"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path><path d="M13.73 21a2 2 0 0 1-3.46 0"></path></svg> \ No newline at end of file
diff --git a/client/src/assets/images/feather/inbox-full.svg b/client/src/assets/images/feather/inbox-full.svg
deleted file mode 100644
index 03a13b4e4..000000000
--- a/client/src/assets/images/feather/inbox-full.svg
+++ /dev/null
@@ -1 +0,0 @@
1<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-inbox"><polyline points="22 12 16 12 14 15 10 15 8 12 2 12"></polyline><path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"></path></svg> \ No newline at end of file