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 8e4480ca6..0727f90e8 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 @@ -5,7 +5,15 @@ Notification preferences </a> - <button class="btn" [disabled]="!hasUnreadNotifications()" (click)="markAllAsRead()"> + <div class="peertube-select-container peertube-select-button ml-2"> + <select [(ngModel)]="notificationSortType" (ngModelChange)="onNotificationSortTypeChanged()" class="form-control"> + <option value="undefined" disabled>Sort by</option> + <option value="created" i18n>Newest first</option> + <option value="unread-created" i18n>Unread first</option> + </select> + </div> + + <button class="btn ml-auto" [disabled]="!hasUnreadNotifications()" (click)="markAllAsRead()"> <ng-container *ngIf="hasUnreadNotifications()"> <my-global-icon iconName="inbox-full" aria-hidden="true"></my-global-icon> diff --git a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss index 73f7c7b24..d586eeb0d 100644 --- a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss +++ b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss @@ -3,7 +3,6 @@ .header { display: flex; - justify-content: space-between; font-size: 15px; margin-bottom: 20px; @@ -18,8 +17,13 @@ @include grey-button; @include button-with-icon(20px, 3px, -1px); } + + .peertube-select-container { + @include peertube-select-container(auto); + } } + my-user-notifications { font-size: 15px; } diff --git a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.ts b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.ts index 0c1427d96..03b91e050 100644 --- a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.ts +++ b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.ts @@ -8,6 +8,8 @@ import { UserNotificationsComponent } from '@app/shared/shared-main' export class MyAccountNotificationsComponent { @ViewChild('userNotification', { static: true }) userNotification: UserNotificationsComponent + notificationSortType = 'created' + markAllAsRead () { this.userNotification.markAllAsRead() } @@ -15,4 +17,6 @@ export class MyAccountNotificationsComponent { hasUnreadNotifications () { return this.userNotification.notifications.filter(n => n.read === false).length !== 0 } + + onNotificationSortTypeChanged () {} } 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 index 8dd9472fe..ecc66ecdb 100644 --- a/client/src/app/shared/shared-main/users/user-notification.service.ts +++ b/client/src/app/shared/shared-main/users/user-notification.service.ts @@ -5,6 +5,7 @@ import { ComponentPaginationLight, RestExtractor, RestService, User, UserNotific import { ResultList, UserNotification as UserNotificationServer, UserNotificationSetting } from '@shared/models' import { environment } from '../../../../environments/environment' import { UserNotification } from './user-notification.model' +import { SortMeta } from 'primeng/api' @Injectable() export class UserNotificationService { @@ -18,9 +19,16 @@ export class UserNotificationService { private userNotificationSocket: UserNotificationSocket ) {} - listMyNotifications (pagination: ComponentPaginationLight, unread?: boolean, ignoreLoadingBar = false) { + listMyNotifications (parameters: { + pagination: ComponentPaginationLight + ignoreLoadingBar?: boolean + unread?: boolean, + sort?: SortMeta + }) { + const { pagination, ignoreLoadingBar, unread, sort } = parameters + let params = new HttpParams() - params = this.restService.addRestGetParams(params, this.restService.componentPaginationToRestPagination(pagination)) + params = this.restService.addRestGetParams(params, this.restService.componentPaginationToRestPagination(pagination), sort) if (unread) params = params.append('unread', `${unread}`) @@ -35,7 +43,7 @@ export class UserNotificationService { } countUnreadNotifications () { - return this.listMyNotifications({ currentPage: 1, itemsPerPage: 0 }, true) + return this.listMyNotifications({ pagination: { currentPage: 1, itemsPerPage: 0 }, ignoreLoadingBar: true, unread: true }) .pipe(map(n => n.total)) } 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 index 6abd8b7d8..48be80e3f 100644 --- a/client/src/app/shared/shared-main/users/user-notifications.component.ts +++ b/client/src/app/shared/shared-main/users/user-notifications.component.ts @@ -19,6 +19,7 @@ export class UserNotificationsComponent implements OnInit { @Output() notificationsLoaded = new EventEmitter() notifications: UserNotification[] = [] + sortField = 'createdAt' // So we can access it in the template UserNotificationType = UserNotificationType @@ -39,18 +40,25 @@ export class UserNotificationsComponent implements OnInit { totalItems: null } - this.loadMoreNotifications() + this.loadNotifications() if (this.markAllAsReadSubject) { this.markAllAsReadSubject.subscribe(() => this.markAllAsRead()) } } - loadMoreNotifications () { - this.userNotificationService.listMyNotifications(this.componentPagination, undefined, this.ignoreLoadingBar) + loadNotifications (reset?: boolean) { + this.userNotificationService.listMyNotifications({ + pagination: this.componentPagination, + ignoreLoadingBar: this.ignoreLoadingBar, + sort: { + field: this.sortField, + order: this.sortField === 'createdAt' ? -1 : 1 + } + }) .subscribe( result => { - this.notifications = this.notifications.concat(result.data) + this.notifications = reset ? result.data : this.notifications.concat(result.data) this.componentPagination.totalItems = result.total this.notificationsLoaded.emit() @@ -68,7 +76,7 @@ export class UserNotificationsComponent implements OnInit { this.componentPagination.currentPage++ if (hasMoreItems(this.componentPagination)) { - this.loadMoreNotifications() + this.loadNotifications() } } @@ -97,4 +105,14 @@ export class UserNotificationsComponent implements OnInit { err => this.notifier.error(err.message) ) } + + changeSortColumn (column: string) { + this.componentPagination = { + currentPage: 1, + itemsPerPage: this.itemsPerPage, + totalItems: null + } + this.sortField = column + this.loadNotifications(true) + } } diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss index 3471d694c..2de5ce7f1 100644 --- a/client/src/sass/include/_mixins.scss +++ b/client/src/sass/include/_mixins.scss @@ -356,6 +356,17 @@ color: #000; } } + + &.peertube-select-button { + @include grey-button; + + select, + option { + font-weight: $font-semibold; + color: pvar(--greyForegroundColor); + border: none; + } + } } // Thanks: https://codepen.io/triss90/pen/XNEdRe/ @@ -454,6 +465,49 @@ } } +@mixin table-badge { + border-radius: 2px; + padding: 1/4em 1/2em; + text-transform: uppercase; + font-weight: $font-bold; + font-size: 12px; + letter-spacing: 1/3px; + + &.badge-banned, + &.badge-red { + background-color: #ffcdd2; + color: #c63737; + } + + &.badge-banned { + text-decoration: line-through; + } + + &.badge-yellow { + background-color: #feedaf; + color: #8a5340; + } + + &.badge-brown { + background-color: #ffd8b2; + color: #805b36; + } + + &.badge-green { + background-color: #c8e6c9; + color: #256029; + } + + &.badge-blue { + background-color: #b3e5fc; + color: #23547b; + } + + &.badge-purple { + background-color: #eccfff; + color: #694382; + } +} @mixin avatar ($size) { object-fit: cover; @@ -638,6 +692,7 @@ overflow: hidden; font-size: 0.75rem; border-radius: 0.25rem; + color: gray; .progress-bar { color: pvar(--mainBackgroundColor); @@ -648,11 +703,25 @@ text-align: center; white-space: nowrap; transition: width 0.6s ease; + isolation: isolate; + + &:after { + content: attr(valuenow-formatted); + position: absolute; + margin-left: .2rem; + mix-blend-mode: screen; + color: gray; + } &.secondary { background-color: pvar(--secondaryColor); } } + + .progress-bar + span { + position: relative; + top: -1px; + } } @mixin breadcrumb { diff --git a/client/src/sass/primeng-custom.scss b/client/src/sass/primeng-custom.scss index 2388c0469..bf49639f5 100644 --- a/client/src/sass/primeng-custom.scss +++ b/client/src/sass/primeng-custom.scss @@ -92,6 +92,11 @@ p-table { &:last-child td { border-bottom: none !important; } + + &:focus + tr > td, + &:focus > td { + box-shadow: none !important; + } } .expander { diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 2e9d3956e..fd5bf5868 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts @@ -55,7 +55,7 @@ const WEBSERVER = { // Sortable columns per schema const SORTABLE_COLUMNS = { - USERS: [ 'id', 'username', 'videoQuotaUsed', 'createdAt' ], + USERS: [ 'id', 'username', 'videoQuotaUsed', 'createdAt', 'lastLoginDate', 'role' ], USER_SUBSCRIPTIONS: [ 'id', 'createdAt' ], ACCOUNTS: [ 'createdAt' ], JOBS: [ 'createdAt' ], @@ -78,7 +78,7 @@ const SORTABLE_COLUMNS = { ACCOUNTS_BLOCKLIST: [ 'createdAt' ], SERVERS_BLOCKLIST: [ 'createdAt' ], - USER_NOTIFICATIONS: [ 'createdAt' ], + USER_NOTIFICATIONS: [ 'createdAt', 'read' ], VIDEO_PLAYLISTS: [ 'displayName', 'createdAt', 'updatedAt' ],