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>
This commit is contained in:
parent
75594f474a
commit
51a8397006
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
<button class="btn ml-auto" [disabled]="!hasUnreadNotifications()" (click)="markAllAsRead()">
|
<button class="btn ml-auto" [disabled]="!hasUnreadNotifications()" (click)="markAllAsRead()">
|
||||||
<ng-container *ngIf="hasUnreadNotifications()">
|
<ng-container *ngIf="hasUnreadNotifications()">
|
||||||
<my-global-icon iconName="inbox-full" aria-hidden="true"></my-global-icon>
|
<my-global-icon iconName="tick" aria-hidden="true"></my-global-icon>
|
||||||
|
|
||||||
<span i18n>Mark all as read</span>
|
<span i18n>Mark all as read</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { CoreModule } from './core'
|
||||||
import { EmptyComponent } from './empty.component'
|
import { EmptyComponent } from './empty.component'
|
||||||
import { HeaderComponent, SearchTypeaheadComponent, SuggestionComponent } from './header'
|
import { HeaderComponent, SearchTypeaheadComponent, SuggestionComponent } from './header'
|
||||||
import { HighlightPipe } from './header/highlight.pipe'
|
import { HighlightPipe } from './header/highlight.pipe'
|
||||||
import { AvatarNotificationComponent, LanguageChooserComponent, MenuComponent } from './menu'
|
import { NotificationComponent, LanguageChooserComponent, MenuComponent } from './menu'
|
||||||
import { ConfirmComponent } from './modal/confirm.component'
|
import { ConfirmComponent } from './modal/confirm.component'
|
||||||
import { CustomModalComponent } from './modal/custom-modal.component'
|
import { CustomModalComponent } from './modal/custom-modal.component'
|
||||||
import { InstanceConfigWarningModalComponent } from './modal/instance-config-warning-modal.component'
|
import { InstanceConfigWarningModalComponent } from './modal/instance-config-warning-modal.component'
|
||||||
|
@ -35,7 +35,7 @@ registerLocaleData(localeOc, 'oc')
|
||||||
MenuComponent,
|
MenuComponent,
|
||||||
LanguageChooserComponent,
|
LanguageChooserComponent,
|
||||||
QuickSettingsModalComponent,
|
QuickSettingsModalComponent,
|
||||||
AvatarNotificationComponent,
|
NotificationComponent,
|
||||||
HeaderComponent,
|
HeaderComponent,
|
||||||
SearchTypeaheadComponent,
|
SearchTypeaheadComponent,
|
||||||
SuggestionComponent,
|
SuggestionComponent,
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export * from './language-chooser.component'
|
export * from './language-chooser.component'
|
||||||
export * from './avatar-notification.component'
|
export * from './notification.component'
|
||||||
export * from './menu.component'
|
export * from './menu.component'
|
||||||
|
|
|
@ -3,32 +3,28 @@
|
||||||
<div class="top-menu">
|
<div class="top-menu">
|
||||||
<div *ngIf="isLoggedIn" class="logged-in-block">
|
<div *ngIf="isLoggedIn" class="logged-in-block">
|
||||||
<div>
|
<div>
|
||||||
<my-avatar-notification [user]="user" (navigate)="onActiveLinkScrollToAnchor($event)"></my-avatar-notification>
|
<div class="logged-in-more" ngbDropdown #dropdown="ngbDropdown" placement="bottom-left" [container]="dropdownContainer" (openChange)="onDropdownOpenChange($event)" autoClose="outside">
|
||||||
|
<div ngbDropdownToggle>
|
||||||
|
<img [src]="user.accountAvatarUrl" alt="Avatar" />
|
||||||
<div class="logged-in-info">
|
<div class="logged-in-info">
|
||||||
<a *ngIf="user.account" [routerLink]="[ '/accounts', user.account.nameWithHost ]" class="logged-in-display-name">{{ user.account?.displayName }}</a>
|
<div class="logged-in-display-name">{{ user.account?.displayName }}</div>
|
||||||
<a *ngIf="!user.account" routerLink="/my-account/settings" class="logged-in-display-name">{{ user.account?.displayName }}</a>
|
|
||||||
|
|
||||||
<div class="logged-in-username">@{{ user.username }}</div>
|
<div class="logged-in-username">@{{ user.username }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="logged-in-more" ngbDropdown [placement]="loggedInMorePlacement" container="body" autoClose="outside">
|
<div class="dropdown-toggle-indicator">
|
||||||
<my-global-icon iconName="more-vertical" ngbDropdownToggle role="button"></my-global-icon>
|
<span class="glyphicon glyphicon-chevron-down"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div ngbDropdownMenu>
|
<div ngbDropdownMenu>
|
||||||
<a *ngIf="user.account" ngbDropdownItem ngbDropdownToggle class="dropdown-item" [routerLink]="[ '/accounts', user.account.nameWithHost ]">
|
<a *ngIf="user.account" ngbDropdownItem ngbDropdownToggle class="dropdown-item" [routerLink]="[ '/accounts', user.account.nameWithHost ]"
|
||||||
|
#profile (click)="onActiveLinkScrollToAnchor(profile)">
|
||||||
<my-global-icon iconName="go" aria-hidden="true"></my-global-icon> <ng-container i18n>Public profile</ng-container>
|
<my-global-icon iconName="go" aria-hidden="true"></my-global-icon> <ng-container i18n>Public profile</ng-container>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
|
|
||||||
<a ngbDropdownItem ngbDropdownToggle class="dropdown-item" routerLink="/my-account/notifications"
|
|
||||||
#notifications (click)="onActiveLinkScrollToAnchor(notifications)">
|
|
||||||
<my-global-icon iconName="inbox-full" aria-hidden="true"></my-global-icon> <ng-container i18n>My notifications</ng-container>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
|
|
||||||
<a ngbDropdownItem ngbDropdownToggle class="dropdown-item" (click)="openLanguageChooser()">
|
<a ngbDropdownItem ngbDropdownToggle class="dropdown-item" (click)="openLanguageChooser()">
|
||||||
<my-global-icon iconName="language" aria-hidden="true"></my-global-icon>
|
<my-global-icon iconName="language" aria-hidden="true"></my-global-icon>
|
||||||
<span i18n>Interface:</span>
|
<span i18n>Interface:</span>
|
||||||
|
@ -42,7 +38,7 @@
|
||||||
<span class="ml-auto text-muted">{{ videoLanguages.join(', ') }}</span>
|
<span class="ml-auto text-muted">{{ videoLanguages.join(', ') }}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a ngbDropdownItem ngbDropdownToggle class="dropdown-item" routerLink="/my-account/settings"
|
<a ngbDropdownItem ngbDropdownToggle class="dropdown-item settings-sensitive" routerLink="/my-account/settings"
|
||||||
fragment="video-sensitive-content-policy" #settingsSensitiveContentPolicy
|
fragment="video-sensitive-content-policy" #settingsSensitiveContentPolicy
|
||||||
(click)="onActiveLinkScrollToAnchor(settingsSensitiveContentPolicy)">
|
(click)="onActiveLinkScrollToAnchor(settingsSensitiveContentPolicy)">
|
||||||
<my-global-icon class="hover-display-toggle" [ngClass]="{ 'not-displayed': user.nsfwPolicy === 'display' }" iconName="sensitive" aria-hidden="true"></my-global-icon>
|
<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 @@
|
||||||
|
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
|
|
||||||
<a ngbDropdownItem ngbDropdownToggle class="dropdown-item" (click)="openHotkeysCheatSheet()">
|
<a *ngIf="!isInMobileView" ngbDropdownItem ngbDropdownToggle class="dropdown-item" (click)="openHotkeysCheatSheet()">
|
||||||
<my-global-icon iconName="command" aria-hidden="true"></my-global-icon>
|
<my-global-icon iconName="command" aria-hidden="true"></my-global-icon>
|
||||||
<ng-container i18n>Keyboard shortcuts</ng-container>
|
<ng-container i18n>Keyboard shortcuts</ng-container>
|
||||||
</a>
|
</a>
|
||||||
|
@ -71,6 +67,8 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<my-notification (navigate)="onActiveLinkScrollToAnchor($event)"></my-notification>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="logged-in-menu">
|
<div class="logged-in-menu">
|
||||||
|
|
|
@ -88,46 +88,117 @@ menu {
|
||||||
height: 80px;
|
height: 80px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: left;
|
||||||
|
|
||||||
|
.logged-in-more {
|
||||||
|
$main-radius: 25px;
|
||||||
|
|
||||||
|
margin-left: 13px;
|
||||||
|
border-radius: $main-radius;
|
||||||
|
transition: all .1s ease-in-out;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
*, & {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.show {
|
||||||
|
background-color: rgba(255, 255, 255, 0.20);
|
||||||
|
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .325);
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin display-hints($is-mobile: false) {
|
||||||
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
|
|
||||||
|
@if $is-mobile {
|
||||||
|
.dropdown-toggle-indicator {
|
||||||
|
display: inherit !important;
|
||||||
|
}
|
||||||
|
.dropdown-toggle:first-child {
|
||||||
|
padding-right: 30px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
@include display-hints;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* smartphones and touchscreens */
|
||||||
|
@media (hover: none) and (pointer: coarse) {
|
||||||
|
@include display-hints($is-mobile: true);
|
||||||
|
|
||||||
|
/* fill space when on mobile */
|
||||||
|
max-width: calc(100% - 80px);
|
||||||
|
.dropdown-toggle {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
.logged-in-info {
|
||||||
|
max-width: calc(100% - 45px) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-toggle-indicator {
|
||||||
|
position: relative;
|
||||||
|
width: 0;
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
span {
|
||||||
|
position: absolute;
|
||||||
|
right: -35px;
|
||||||
|
top: -8px;
|
||||||
|
color: grey;
|
||||||
|
width: $main-radius;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-toggle {
|
||||||
|
&::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-toggle:first-child {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 5px 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
@include avatar(34px);
|
||||||
|
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.logged-in-info {
|
.logged-in-info {
|
||||||
@include ellipsis;
|
max-width: 105px;
|
||||||
|
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
||||||
|
.logged-in-display-name,
|
||||||
|
.logged-in-username {
|
||||||
|
@include ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
.logged-in-display-name {
|
.logged-in-display-name {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: $font-semibold;
|
font-weight: $font-semibold;
|
||||||
color: pvar(--menuForegroundColor);
|
color: pvar(--menuForegroundColor);
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
@include disable-default-a-behaviour;
|
@include disable-default-a-behaviour;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logged-in-username {
|
.logged-in-username {
|
||||||
@include ellipsis;
|
|
||||||
|
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #C6C6C6;
|
color: #C6C6C6;
|
||||||
max-width: 140px;
|
}
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.logged-in-more {
|
my-notification {
|
||||||
margin-right: 20px;
|
margin-left: auto;
|
||||||
|
margin-right: 15px;
|
||||||
my-global-icon.dropdown-toggle {
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
::ng-deep {
|
|
||||||
@include apply-svg-color(pvar(--menuForegroundColor));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,6 +414,12 @@ menu {
|
||||||
my-global-icon.hover-display-toggle {
|
my-global-icon.hover-display-toggle {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.settings-sensitive {
|
||||||
|
my-global-icon ::ng-deep svg {
|
||||||
|
margin-top: 2px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,4 +441,14 @@ menu {
|
||||||
.top-menu, .footer {
|
.top-menu, .footer {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
width: calc(100vw - 30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item:hover, .dropdown-item:active {
|
||||||
|
&.settings-sensitive my-global-icon ::ng-deep svg {
|
||||||
|
margin-top: 0px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { AuthService, AuthStatus, AuthUser, MenuService, RedirectService, Screen
|
||||||
import { LanguageChooserComponent } from '@app/menu/language-chooser.component'
|
import { LanguageChooserComponent } from '@app/menu/language-chooser.component'
|
||||||
import { QuickSettingsModalComponent } from '@app/modal/quick-settings-modal.component'
|
import { QuickSettingsModalComponent } from '@app/modal/quick-settings-modal.component'
|
||||||
import { ServerConfig, UserRight, VideoConstant } from '@shared/models'
|
import { ServerConfig, UserRight, VideoConstant } from '@shared/models'
|
||||||
|
import { NgbDropdown, NgbDropdownConfig } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
|
||||||
const logger = debug('peertube:menu:MenuComponent')
|
const logger = debug('peertube:menu:MenuComponent')
|
||||||
|
|
||||||
|
@ -20,6 +21,7 @@ const logger = debug('peertube:menu:MenuComponent')
|
||||||
export class MenuComponent implements OnInit {
|
export class MenuComponent implements OnInit {
|
||||||
@ViewChild('languageChooserModal', { static: true }) languageChooserModal: LanguageChooserComponent
|
@ViewChild('languageChooserModal', { static: true }) languageChooserModal: LanguageChooserComponent
|
||||||
@ViewChild('quickSettingsModal', { static: true }) quickSettingsModal: QuickSettingsModalComponent
|
@ViewChild('quickSettingsModal', { static: true }) quickSettingsModal: QuickSettingsModalComponent
|
||||||
|
@ViewChild('dropdown') dropdown: NgbDropdown
|
||||||
|
|
||||||
user: AuthUser
|
user: AuthUser
|
||||||
isLoggedIn: boolean
|
isLoggedIn: boolean
|
||||||
|
@ -30,8 +32,6 @@ export class MenuComponent implements OnInit {
|
||||||
videoLanguages: string[] = []
|
videoLanguages: string[] = []
|
||||||
nsfwPolicy: string
|
nsfwPolicy: string
|
||||||
|
|
||||||
loggedInMorePlacement: string
|
|
||||||
|
|
||||||
currentInterfaceLanguage: string
|
currentInterfaceLanguage: string
|
||||||
|
|
||||||
private languages: VideoConstant<string>[] = []
|
private languages: VideoConstant<string>[] = []
|
||||||
|
@ -54,8 +54,27 @@ export class MenuComponent implements OnInit {
|
||||||
private hotkeysService: HotkeysService,
|
private hotkeysService: HotkeysService,
|
||||||
private screenService: ScreenService,
|
private screenService: ScreenService,
|
||||||
private menuService: MenuService,
|
private menuService: MenuService,
|
||||||
|
private dropdownConfig: NgbDropdownConfig,
|
||||||
private router: Router
|
private router: Router
|
||||||
) { }
|
) {
|
||||||
|
this.dropdownConfig.container = 'body'
|
||||||
|
}
|
||||||
|
|
||||||
|
get isInMobileView () {
|
||||||
|
return this.screenService.isInMobileView()
|
||||||
|
}
|
||||||
|
|
||||||
|
get dropdownContainer () {
|
||||||
|
if (this.isInMobileView) {
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
return this.dropdownConfig.container
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get language () {
|
||||||
|
return this.languageChooserModal.getCurrentLanguage()
|
||||||
|
}
|
||||||
|
|
||||||
get instanceName () {
|
get instanceName () {
|
||||||
return this.serverConfig.instance.name
|
return this.serverConfig.instance.name
|
||||||
|
@ -76,10 +95,6 @@ export class MenuComponent implements OnInit {
|
||||||
|
|
||||||
this.computeAdminAccess()
|
this.computeAdminAccess()
|
||||||
|
|
||||||
this.loggedInMorePlacement = this.screenService.isInMobileView()
|
|
||||||
? 'left-top auto'
|
|
||||||
: 'right-top auto'
|
|
||||||
|
|
||||||
this.currentInterfaceLanguage = this.languageChooserModal.getCurrentLanguage()
|
this.currentInterfaceLanguage = this.languageChooserModal.getCurrentLanguage()
|
||||||
|
|
||||||
this.authService.loginChangedSource.subscribe(
|
this.authService.loginChangedSource.subscribe(
|
||||||
|
@ -203,6 +218,29 @@ export class MenuComponent implements OnInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lock menu scroll when menu scroll to avoid fleeing / detached dropdown
|
||||||
|
onMenuScrollEvent () {
|
||||||
|
document.querySelector('menu').scrollTo(0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
onDropdownOpenChange (opened: boolean) {
|
||||||
|
if (this.screenService.isInMobileView()) return
|
||||||
|
|
||||||
|
// Close dropdown when window scroll to avoid dropdown quick jump for re-position
|
||||||
|
const onWindowScroll = () => {
|
||||||
|
this.dropdown.close()
|
||||||
|
window.removeEventListener('scroll', onWindowScroll)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opened) {
|
||||||
|
window.addEventListener('scroll', onWindowScroll)
|
||||||
|
document.querySelector('menu').scrollTo(0, 0) // Reset menu scroll to easy lock
|
||||||
|
document.querySelector('menu').addEventListener('scroll', this.onMenuScrollEvent)
|
||||||
|
} else {
|
||||||
|
document.querySelector('menu').removeEventListener('scroll', this.onMenuScrollEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private buildUserLanguages () {
|
private buildUserLanguages () {
|
||||||
if (!this.user) {
|
if (!this.user) {
|
||||||
this.videoLanguages = []
|
this.videoLanguages = []
|
||||||
|
|
|
@ -1,10 +1,19 @@
|
||||||
<div
|
<div
|
||||||
[ngbPopover]="popContent" autoClose="outside" placement="bottom-left" container="body" popoverClass="popover-notifications"
|
[ngbPopover]="popContent" autoClose="outside" placement="bottom" container={this} popoverClass="popover-notifications"
|
||||||
i18n-title title="View your notifications" class="notification-avatar" #popover="ngbPopover" (hidden)="onPopoverHidden()"
|
i18n-title title="View your notifications" [ngClass]="{ 'notification-inbox-popover': true, 'shown': opened, 'hidden': isInMobileView }"
|
||||||
|
#popover="ngbPopover" (shown)="onPopoverShown()" (hidden)="onPopoverHidden()"
|
||||||
>
|
>
|
||||||
<div *ngIf="unreadNotifications > 0" class="unread-notifications">{{ unreadNotifications }}</div>
|
<div *ngIf="unreadNotifications > 0" class="unread-notifications">{{ unreadNotifications }}</div>
|
||||||
|
|
||||||
<img [src]="user.accountAvatarUrl" alt="Avatar" />
|
<my-global-icon iconName="bell"></my-global-icon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="isInMobileView" i18n-title title="View your notifications" class="notification-inbox-link">
|
||||||
|
<div *ngIf="unreadNotifications > 0" class="unread-notifications">{{ unreadNotifications }}</div>
|
||||||
|
|
||||||
|
<a routerLink="/my-account/notifications" routerLinkActive="active" #link (click)="onNavigate(link)">
|
||||||
|
<my-global-icon iconName="bell"></my-global-icon>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #popContent>
|
<ng-template #popContent>
|
||||||
|
@ -15,7 +24,7 @@
|
||||||
<div>
|
<div>
|
||||||
<button
|
<button
|
||||||
*ngIf="unreadNotifications"
|
*ngIf="unreadNotifications"
|
||||||
i18n-title title="Mark all as read" class="glyphicon glyphicon-inbox mr-2"
|
i18n-title title="Mark all as read" class="glyphicon glyphicon-ok mr-2"
|
||||||
(click)="markAllAsRead()"
|
(click)="markAllAsRead()"
|
||||||
></button>
|
></button>
|
||||||
<a
|
<a
|
||||||
|
@ -36,7 +45,7 @@
|
||||||
></my-user-notifications>
|
></my-user-notifications>
|
||||||
|
|
||||||
<a *ngIf="loaded" class="all-notifications" routerLink="/my-account/notifications" #notifications (click)="onNavigate(notifications)">
|
<a *ngIf="loaded" class="all-notifications" routerLink="/my-account/notifications" #notifications (click)="onNavigate(notifications)">
|
||||||
<my-global-icon class="mr-1" iconName="inbox-full" aria-hidden="true"></my-global-icon>
|
<my-global-icon class="mr-1" iconName="bell" aria-hidden="true"></my-global-icon>
|
||||||
<span i18n>See all your notifications</span>
|
<span i18n>See all your notifications</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
|
@ -1,17 +1,62 @@
|
||||||
@import '_variables';
|
@import '_variables';
|
||||||
@import '_mixins';
|
@import '_mixins';
|
||||||
|
|
||||||
|
|
||||||
|
.notification-inbox-popover {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-inbox-link a {
|
||||||
|
padding: 13px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-inbox-popover,
|
||||||
|
.notification-inbox-link a {
|
||||||
|
@include apply-svg-color(#808080);
|
||||||
|
::ng-deep {
|
||||||
|
svg {
|
||||||
|
transition: color .1s ease-in-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transition: all .1s ease-in-out;
|
||||||
|
border-radius: 25px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover, &:active {
|
||||||
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
|
@include apply-svg-color(#fff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-inbox-popover.shown,
|
||||||
|
.notification-inbox-link a.active {
|
||||||
|
@include apply-svg-color(#fff);
|
||||||
|
|
||||||
|
background-color: rgba(255, 255, 255, 0.28);
|
||||||
|
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .325);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-inbox-popover.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
::ng-deep {
|
::ng-deep {
|
||||||
.popover-notifications.popover {
|
.popover-notifications.popover {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
|
top: -6px !important;
|
||||||
left: 7px !important;
|
left: 7px !important;
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.popover-body {
|
.popover-body {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-family: $main-fonts;
|
font-family: $main-fonts;
|
||||||
width: 400px;
|
width: 400px;
|
||||||
box-shadow: 0 6px 14px rgba(0, 0, 0, 0.30);
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.30);
|
||||||
|
|
||||||
.loader {
|
.loader {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -42,11 +87,14 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
background-color: rgba(0, 0, 0, 0.10);
|
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 10px;
|
padding: 0 12px;
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
min-height: 50px;
|
font-weight: bold;
|
||||||
|
color: rgba(0, 0, 0, 0.5);
|
||||||
|
text-transform: uppercase;
|
||||||
|
min-height: 40px;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@include disable-default-a-behaviour;
|
@include disable-default-a-behaviour;
|
||||||
|
@ -82,25 +130,23 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-avatar {
|
.notification-inbox-popover, .notification-inbox-link {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
img,
|
|
||||||
.unread-notifications {
|
.unread-notifications {
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
|
||||||
@include avatar(34px);
|
|
||||||
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.unread-notifications {
|
.unread-notifications {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -5px;
|
top: 6px;
|
||||||
left: -5px;
|
left: 0;
|
||||||
|
|
||||||
|
@media screen and (max-width: $mobile-view) {
|
||||||
|
top: -4px;
|
||||||
|
left: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -116,19 +162,3 @@
|
||||||
height: 15px;
|
height: 15px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: $mobile-view) {
|
|
||||||
::ng-deep {
|
|
||||||
.popover-notifications.popover {
|
|
||||||
left: unset !important;
|
|
||||||
|
|
||||||
.arrow {
|
|
||||||
left: calc(2em + 7px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.popover-body {
|
|
||||||
width: 100vw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +1,24 @@
|
||||||
import { Subject, Subscription } from 'rxjs'
|
import { Subject, Subscription } from 'rxjs'
|
||||||
import { filter } from 'rxjs/operators'
|
import { filter } from 'rxjs/operators'
|
||||||
import { Component, EventEmitter, Input, Output, OnDestroy, OnInit, ViewChild } from '@angular/core'
|
import { Component, EventEmitter, Output, OnDestroy, OnInit, ViewChild } from '@angular/core'
|
||||||
import { NavigationEnd, Router } from '@angular/router'
|
import { NavigationEnd, Router } from '@angular/router'
|
||||||
import { Notifier, User, PeerTubeSocket } from '@app/core'
|
import { Notifier, PeerTubeSocket, ScreenService } from '@app/core'
|
||||||
import { UserNotificationService } from '@app/shared/shared-main'
|
import { UserNotificationService } from '@app/shared/shared-main'
|
||||||
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-avatar-notification',
|
selector: 'my-notification',
|
||||||
templateUrl: './avatar-notification.component.html',
|
templateUrl: './notification.component.html',
|
||||||
styleUrls: [ './avatar-notification.component.scss' ]
|
styleUrls: [ './notification.component.scss' ]
|
||||||
})
|
})
|
||||||
export class AvatarNotificationComponent implements OnInit, OnDestroy {
|
export class NotificationComponent implements OnInit, OnDestroy {
|
||||||
@ViewChild('popover', { static: true }) popover: NgbPopover
|
@ViewChild('popover', { static: true }) popover: NgbPopover
|
||||||
|
|
||||||
@Input() user: User
|
|
||||||
@Output() navigate = new EventEmitter<HTMLAnchorElement>()
|
@Output() navigate = new EventEmitter<HTMLAnchorElement>()
|
||||||
|
|
||||||
unreadNotifications = 0
|
unreadNotifications = 0
|
||||||
loaded = false
|
loaded = false
|
||||||
|
opened = false
|
||||||
|
|
||||||
markAllAsReadSubject = new Subject<boolean>()
|
markAllAsReadSubject = new Subject<boolean>()
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ export class AvatarNotificationComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private userNotificationService: UserNotificationService,
|
private userNotificationService: UserNotificationService,
|
||||||
|
private screenService: ScreenService,
|
||||||
private peertubeSocket: PeerTubeSocket,
|
private peertubeSocket: PeerTubeSocket,
|
||||||
private notifier: Notifier,
|
private notifier: Notifier,
|
||||||
private router: Router
|
private router: Router
|
||||||
|
@ -54,12 +55,31 @@ export class AvatarNotificationComponent implements OnInit, OnDestroy {
|
||||||
if (this.routeSub) this.routeSub.unsubscribe()
|
if (this.routeSub) this.routeSub.unsubscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isInMobileView () {
|
||||||
|
return this.screenService.isInMobileView()
|
||||||
|
}
|
||||||
|
|
||||||
closePopover () {
|
closePopover () {
|
||||||
this.popover.close()
|
this.popover.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onPopoverShown () {
|
||||||
|
this.opened = true
|
||||||
|
|
||||||
|
document.querySelector('menu').scrollTo(0, 0) // Reset menu scroll to easy lock
|
||||||
|
document.querySelector('menu').addEventListener('scroll', this.onMenuScrollEvent)
|
||||||
|
}
|
||||||
|
|
||||||
onPopoverHidden () {
|
onPopoverHidden () {
|
||||||
this.loaded = false
|
this.loaded = false
|
||||||
|
this.opened = false
|
||||||
|
|
||||||
|
document.querySelector('menu').removeEventListener('scroll', this.onMenuScrollEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock menu scroll when menu scroll to avoid fleeing / detached dropdown
|
||||||
|
onMenuScrollEvent () {
|
||||||
|
document.querySelector('menu').scrollTo(0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
onNotificationLoaded () {
|
onNotificationLoaded () {
|
||||||
|
@ -67,6 +87,7 @@ export class AvatarNotificationComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
onNavigate (link: HTMLAnchorElement) {
|
onNavigate (link: HTMLAnchorElement) {
|
||||||
|
this.closePopover()
|
||||||
this.navigate.emit(link)
|
this.navigate.emit(link)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,5 +104,4 @@ export class AvatarNotificationComponent implements OnInit, OnDestroy {
|
||||||
if (data.type === 'read-all') return this.unreadNotifications = 0
|
if (data.type === 'read-all') return this.unreadNotifications = 0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -36,7 +36,7 @@ const icons = {
|
||||||
'clock': require('!!raw-loader?!../../../assets/images/feather/clock.svg').default,
|
'clock': require('!!raw-loader?!../../../assets/images/feather/clock.svg').default,
|
||||||
'cog': require('!!raw-loader?!../../../assets/images/feather/cog.svg').default,
|
'cog': require('!!raw-loader?!../../../assets/images/feather/cog.svg').default,
|
||||||
'delete': require('!!raw-loader?!../../../assets/images/feather/delete.svg').default,
|
'delete': require('!!raw-loader?!../../../assets/images/feather/delete.svg').default,
|
||||||
'inbox-full': require('!!raw-loader?!../../../assets/images/feather/inbox-full.svg').default,
|
'bell': require('!!raw-loader?!../../../assets/images/feather/bell.svg').default,
|
||||||
'sign-out': require('!!raw-loader?!../../../assets/images/feather/log-out.svg').default,
|
'sign-out': require('!!raw-loader?!../../../assets/images/feather/log-out.svg').default,
|
||||||
'sign-in': require('!!raw-loader?!../../../assets/images/feather/log-in.svg').default,
|
'sign-in': require('!!raw-loader?!../../../assets/images/feather/log-in.svg').default,
|
||||||
'download': require('!!raw-loader?!../../../assets/images/feather/download.svg').default,
|
'download': require('!!raw-loader?!../../../assets/images/feather/download.svg').default,
|
||||||
|
|
|
@ -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>
|
After Width: | Height: | Size: 321 B |
|
@ -1 +0,0 @@
|
||||||
<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>
|
|
Before Width: | Height: | Size: 405 B |
Loading…
Reference in New Issue