diff options
author | Rigel Kent <sendmemail@rigelk.eu> | 2020-07-15 11:15:50 +0200 |
---|---|---|
committer | Rigel Kent <par@rigelk.eu> | 2020-07-29 18:15:53 +0200 |
commit | 654a188f80fc1f089aa14837084664c908fe27d2 (patch) | |
tree | 63855915278c1a3aeb1509c09a7a2f5ee6977893 | |
parent | 292c17b894e430d61f9197fb6fa245f5f9c6fa7c (diff) | |
download | PeerTube-654a188f80fc1f089aa14837084664c908fe27d2.tar.gz PeerTube-654a188f80fc1f089aa14837084664c908fe27d2.tar.zst PeerTube-654a188f80fc1f089aa14837084664c908fe27d2.zip |
allow sorting notifications
8 files changed, 128 insertions, 12 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 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 @@ | |||
5 | Notification preferences | 5 | Notification preferences |
6 | </a> | 6 | </a> |
7 | 7 | ||
8 | <button class="btn" [disabled]="!hasUnreadNotifications()" (click)="markAllAsRead()"> | 8 | <div class="peertube-select-container peertube-select-button ml-2"> |
9 | <select [(ngModel)]="notificationSortType" (ngModelChange)="onNotificationSortTypeChanged()" class="form-control"> | ||
10 | <option value="undefined" disabled>Sort by</option> | ||
11 | <option value="created" i18n>Newest first</option> | ||
12 | <option value="unread-created" i18n>Unread first</option> | ||
13 | </select> | ||
14 | </div> | ||
15 | |||
16 | <button class="btn ml-auto" [disabled]="!hasUnreadNotifications()" (click)="markAllAsRead()"> | ||
9 | <ng-container *ngIf="hasUnreadNotifications()"> | 17 | <ng-container *ngIf="hasUnreadNotifications()"> |
10 | <my-global-icon iconName="inbox-full" aria-hidden="true"></my-global-icon> | 18 | <my-global-icon iconName="inbox-full" aria-hidden="true"></my-global-icon> |
11 | 19 | ||
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 @@ | |||
3 | 3 | ||
4 | .header { | 4 | .header { |
5 | display: flex; | 5 | display: flex; |
6 | justify-content: space-between; | ||
7 | font-size: 15px; | 6 | font-size: 15px; |
8 | margin-bottom: 20px; | 7 | margin-bottom: 20px; |
9 | 8 | ||
@@ -18,8 +17,13 @@ | |||
18 | @include grey-button; | 17 | @include grey-button; |
19 | @include button-with-icon(20px, 3px, -1px); | 18 | @include button-with-icon(20px, 3px, -1px); |
20 | } | 19 | } |
20 | |||
21 | .peertube-select-container { | ||
22 | @include peertube-select-container(auto); | ||
23 | } | ||
21 | } | 24 | } |
22 | 25 | ||
26 | |||
23 | my-user-notifications { | 27 | my-user-notifications { |
24 | font-size: 15px; | 28 | font-size: 15px; |
25 | } | 29 | } |
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' | |||
8 | export class MyAccountNotificationsComponent { | 8 | export class MyAccountNotificationsComponent { |
9 | @ViewChild('userNotification', { static: true }) userNotification: UserNotificationsComponent | 9 | @ViewChild('userNotification', { static: true }) userNotification: UserNotificationsComponent |
10 | 10 | ||
11 | notificationSortType = 'created' | ||
12 | |||
11 | markAllAsRead () { | 13 | markAllAsRead () { |
12 | this.userNotification.markAllAsRead() | 14 | this.userNotification.markAllAsRead() |
13 | } | 15 | } |
@@ -15,4 +17,6 @@ export class MyAccountNotificationsComponent { | |||
15 | hasUnreadNotifications () { | 17 | hasUnreadNotifications () { |
16 | return this.userNotification.notifications.filter(n => n.read === false).length !== 0 | 18 | return this.userNotification.notifications.filter(n => n.read === false).length !== 0 |
17 | } | 19 | } |
20 | |||
21 | onNotificationSortTypeChanged () {} | ||
18 | } | 22 | } |
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 | |||
5 | import { ResultList, UserNotification as UserNotificationServer, UserNotificationSetting } from '@shared/models' | 5 | import { ResultList, UserNotification as UserNotificationServer, UserNotificationSetting } from '@shared/models' |
6 | import { environment } from '../../../../environments/environment' | 6 | import { environment } from '../../../../environments/environment' |
7 | import { UserNotification } from './user-notification.model' | 7 | import { UserNotification } from './user-notification.model' |
8 | import { SortMeta } from 'primeng/api' | ||
8 | 9 | ||
9 | @Injectable() | 10 | @Injectable() |
10 | export class UserNotificationService { | 11 | export class UserNotificationService { |
@@ -18,9 +19,16 @@ export class UserNotificationService { | |||
18 | private userNotificationSocket: UserNotificationSocket | 19 | private userNotificationSocket: UserNotificationSocket |
19 | ) {} | 20 | ) {} |
20 | 21 | ||
21 | listMyNotifications (pagination: ComponentPaginationLight, unread?: boolean, ignoreLoadingBar = false) { | 22 | listMyNotifications (parameters: { |
23 | pagination: ComponentPaginationLight | ||
24 | ignoreLoadingBar?: boolean | ||
25 | unread?: boolean, | ||
26 | sort?: SortMeta | ||
27 | }) { | ||
28 | const { pagination, ignoreLoadingBar, unread, sort } = parameters | ||
29 | |||
22 | let params = new HttpParams() | 30 | let params = new HttpParams() |
23 | params = this.restService.addRestGetParams(params, this.restService.componentPaginationToRestPagination(pagination)) | 31 | params = this.restService.addRestGetParams(params, this.restService.componentPaginationToRestPagination(pagination), sort) |
24 | 32 | ||
25 | if (unread) params = params.append('unread', `${unread}`) | 33 | if (unread) params = params.append('unread', `${unread}`) |
26 | 34 | ||
@@ -35,7 +43,7 @@ export class UserNotificationService { | |||
35 | } | 43 | } |
36 | 44 | ||
37 | countUnreadNotifications () { | 45 | countUnreadNotifications () { |
38 | return this.listMyNotifications({ currentPage: 1, itemsPerPage: 0 }, true) | 46 | return this.listMyNotifications({ pagination: { currentPage: 1, itemsPerPage: 0 }, ignoreLoadingBar: true, unread: true }) |
39 | .pipe(map(n => n.total)) | 47 | .pipe(map(n => n.total)) |
40 | } | 48 | } |
41 | 49 | ||
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 { | |||
19 | @Output() notificationsLoaded = new EventEmitter() | 19 | @Output() notificationsLoaded = new EventEmitter() |
20 | 20 | ||
21 | notifications: UserNotification[] = [] | 21 | notifications: UserNotification[] = [] |
22 | sortField = 'createdAt' | ||
22 | 23 | ||
23 | // So we can access it in the template | 24 | // So we can access it in the template |
24 | UserNotificationType = UserNotificationType | 25 | UserNotificationType = UserNotificationType |
@@ -39,18 +40,25 @@ export class UserNotificationsComponent implements OnInit { | |||
39 | totalItems: null | 40 | totalItems: null |
40 | } | 41 | } |
41 | 42 | ||
42 | this.loadMoreNotifications() | 43 | this.loadNotifications() |
43 | 44 | ||
44 | if (this.markAllAsReadSubject) { | 45 | if (this.markAllAsReadSubject) { |
45 | this.markAllAsReadSubject.subscribe(() => this.markAllAsRead()) | 46 | this.markAllAsReadSubject.subscribe(() => this.markAllAsRead()) |
46 | } | 47 | } |
47 | } | 48 | } |
48 | 49 | ||
49 | loadMoreNotifications () { | 50 | loadNotifications (reset?: boolean) { |
50 | this.userNotificationService.listMyNotifications(this.componentPagination, undefined, this.ignoreLoadingBar) | 51 | this.userNotificationService.listMyNotifications({ |
52 | pagination: this.componentPagination, | ||
53 | ignoreLoadingBar: this.ignoreLoadingBar, | ||
54 | sort: { | ||
55 | field: this.sortField, | ||
56 | order: this.sortField === 'createdAt' ? -1 : 1 | ||
57 | } | ||
58 | }) | ||
51 | .subscribe( | 59 | .subscribe( |
52 | result => { | 60 | result => { |
53 | this.notifications = this.notifications.concat(result.data) | 61 | this.notifications = reset ? result.data : this.notifications.concat(result.data) |
54 | this.componentPagination.totalItems = result.total | 62 | this.componentPagination.totalItems = result.total |
55 | 63 | ||
56 | this.notificationsLoaded.emit() | 64 | this.notificationsLoaded.emit() |
@@ -68,7 +76,7 @@ export class UserNotificationsComponent implements OnInit { | |||
68 | this.componentPagination.currentPage++ | 76 | this.componentPagination.currentPage++ |
69 | 77 | ||
70 | if (hasMoreItems(this.componentPagination)) { | 78 | if (hasMoreItems(this.componentPagination)) { |
71 | this.loadMoreNotifications() | 79 | this.loadNotifications() |
72 | } | 80 | } |
73 | } | 81 | } |
74 | 82 | ||
@@ -97,4 +105,14 @@ export class UserNotificationsComponent implements OnInit { | |||
97 | err => this.notifier.error(err.message) | 105 | err => this.notifier.error(err.message) |
98 | ) | 106 | ) |
99 | } | 107 | } |
108 | |||
109 | changeSortColumn (column: string) { | ||
110 | this.componentPagination = { | ||
111 | currentPage: 1, | ||
112 | itemsPerPage: this.itemsPerPage, | ||
113 | totalItems: null | ||
114 | } | ||
115 | this.sortField = column | ||
116 | this.loadNotifications(true) | ||
117 | } | ||
100 | } | 118 | } |
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 @@ | |||
356 | color: #000; | 356 | color: #000; |
357 | } | 357 | } |
358 | } | 358 | } |
359 | |||
360 | &.peertube-select-button { | ||
361 | @include grey-button; | ||
362 | |||
363 | select, | ||
364 | option { | ||
365 | font-weight: $font-semibold; | ||
366 | color: pvar(--greyForegroundColor); | ||
367 | border: none; | ||
368 | } | ||
369 | } | ||
359 | } | 370 | } |
360 | 371 | ||
361 | // Thanks: https://codepen.io/triss90/pen/XNEdRe/ | 372 | // Thanks: https://codepen.io/triss90/pen/XNEdRe/ |
@@ -454,6 +465,49 @@ | |||
454 | } | 465 | } |
455 | } | 466 | } |
456 | 467 | ||
468 | @mixin table-badge { | ||
469 | border-radius: 2px; | ||
470 | padding: 1/4em 1/2em; | ||
471 | text-transform: uppercase; | ||
472 | font-weight: $font-bold; | ||
473 | font-size: 12px; | ||
474 | letter-spacing: 1/3px; | ||
475 | |||
476 | &.badge-banned, | ||
477 | &.badge-red { | ||
478 | background-color: #ffcdd2; | ||
479 | color: #c63737; | ||
480 | } | ||
481 | |||
482 | &.badge-banned { | ||
483 | text-decoration: line-through; | ||
484 | } | ||
485 | |||
486 | &.badge-yellow { | ||
487 | background-color: #feedaf; | ||
488 | color: #8a5340; | ||
489 | } | ||
490 | |||
491 | &.badge-brown { | ||
492 | background-color: #ffd8b2; | ||
493 | color: #805b36; | ||
494 | } | ||
495 | |||
496 | &.badge-green { | ||
497 | background-color: #c8e6c9; | ||
498 | color: #256029; | ||
499 | } | ||
500 | |||
501 | &.badge-blue { | ||
502 | background-color: #b3e5fc; | ||
503 | color: #23547b; | ||
504 | } | ||
505 | |||
506 | &.badge-purple { | ||
507 | background-color: #eccfff; | ||
508 | color: #694382; | ||
509 | } | ||
510 | } | ||
457 | 511 | ||
458 | @mixin avatar ($size) { | 512 | @mixin avatar ($size) { |
459 | object-fit: cover; | 513 | object-fit: cover; |
@@ -638,6 +692,7 @@ | |||
638 | overflow: hidden; | 692 | overflow: hidden; |
639 | font-size: 0.75rem; | 693 | font-size: 0.75rem; |
640 | border-radius: 0.25rem; | 694 | border-radius: 0.25rem; |
695 | color: gray; | ||
641 | 696 | ||
642 | .progress-bar { | 697 | .progress-bar { |
643 | color: pvar(--mainBackgroundColor); | 698 | color: pvar(--mainBackgroundColor); |
@@ -648,11 +703,25 @@ | |||
648 | text-align: center; | 703 | text-align: center; |
649 | white-space: nowrap; | 704 | white-space: nowrap; |
650 | transition: width 0.6s ease; | 705 | transition: width 0.6s ease; |
706 | isolation: isolate; | ||
707 | |||
708 | &:after { | ||
709 | content: attr(valuenow-formatted); | ||
710 | position: absolute; | ||
711 | margin-left: .2rem; | ||
712 | mix-blend-mode: screen; | ||
713 | color: gray; | ||
714 | } | ||
651 | 715 | ||
652 | &.secondary { | 716 | &.secondary { |
653 | background-color: pvar(--secondaryColor); | 717 | background-color: pvar(--secondaryColor); |
654 | } | 718 | } |
655 | } | 719 | } |
720 | |||
721 | .progress-bar + span { | ||
722 | position: relative; | ||
723 | top: -1px; | ||
724 | } | ||
656 | } | 725 | } |
657 | 726 | ||
658 | @mixin breadcrumb { | 727 | @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 { | |||
92 | &:last-child td { | 92 | &:last-child td { |
93 | border-bottom: none !important; | 93 | border-bottom: none !important; |
94 | } | 94 | } |
95 | |||
96 | &:focus + tr > td, | ||
97 | &:focus > td { | ||
98 | box-shadow: none !important; | ||
99 | } | ||
95 | } | 100 | } |
96 | 101 | ||
97 | .expander { | 102 | .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 = { | |||
55 | 55 | ||
56 | // Sortable columns per schema | 56 | // Sortable columns per schema |
57 | const SORTABLE_COLUMNS = { | 57 | const SORTABLE_COLUMNS = { |
58 | USERS: [ 'id', 'username', 'videoQuotaUsed', 'createdAt' ], | 58 | USERS: [ 'id', 'username', 'videoQuotaUsed', 'createdAt', 'lastLoginDate', 'role' ], |
59 | USER_SUBSCRIPTIONS: [ 'id', 'createdAt' ], | 59 | USER_SUBSCRIPTIONS: [ 'id', 'createdAt' ], |
60 | ACCOUNTS: [ 'createdAt' ], | 60 | ACCOUNTS: [ 'createdAt' ], |
61 | JOBS: [ 'createdAt' ], | 61 | JOBS: [ 'createdAt' ], |
@@ -78,7 +78,7 @@ const SORTABLE_COLUMNS = { | |||
78 | ACCOUNTS_BLOCKLIST: [ 'createdAt' ], | 78 | ACCOUNTS_BLOCKLIST: [ 'createdAt' ], |
79 | SERVERS_BLOCKLIST: [ 'createdAt' ], | 79 | SERVERS_BLOCKLIST: [ 'createdAt' ], |
80 | 80 | ||
81 | USER_NOTIFICATIONS: [ 'createdAt' ], | 81 | USER_NOTIFICATIONS: [ 'createdAt', 'read' ], |
82 | 82 | ||
83 | VIDEO_PLAYLISTS: [ 'displayName', 'createdAt', 'updatedAt' ], | 83 | VIDEO_PLAYLISTS: [ 'displayName', 'createdAt', 'updatedAt' ], |
84 | 84 | ||