Pull request #57: Notifications
Merge in RED/ui from notifications to master * commit '836354d89acf3f904901751d531b0b3212f7adf1': Dummy data notifications
This commit is contained in:
commit
3279e56f6b
@ -91,6 +91,7 @@ import { AceEditorModule } from 'ng2-ace-editor';
|
|||||||
import { TeamMembersComponent } from './components/team-members/team-members.component';
|
import { TeamMembersComponent } from './components/team-members/team-members.component';
|
||||||
import { AdminBreadcrumbsComponent } from './components/admin-page-header/admin-breadcrumbs.component';
|
import { AdminBreadcrumbsComponent } from './components/admin-page-header/admin-breadcrumbs.component';
|
||||||
import { UserListingScreenComponent } from './screens/admin/users/user-listing-screen.component';
|
import { UserListingScreenComponent } from './screens/admin/users/user-listing-screen.component';
|
||||||
|
import { NotificationsComponent } from './components/notifications/notifications.component';
|
||||||
|
|
||||||
export function HttpLoaderFactory(httpClient: HttpClient) {
|
export function HttpLoaderFactory(httpClient: HttpClient) {
|
||||||
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
|
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
|
||||||
@ -246,7 +247,8 @@ const matImports = [
|
|||||||
DictionaryOverviewScreenComponent,
|
DictionaryOverviewScreenComponent,
|
||||||
TeamMembersComponent,
|
TeamMembersComponent,
|
||||||
AdminBreadcrumbsComponent,
|
AdminBreadcrumbsComponent,
|
||||||
UserListingScreenComponent
|
UserListingScreenComponent,
|
||||||
|
NotificationsComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
|
|||||||
@ -6,8 +6,10 @@
|
|||||||
[class.warn]="type === 'warn'"
|
[class.warn]="type === 'warn'"
|
||||||
[disabled]="disabled"
|
[disabled]="disabled"
|
||||||
[class.small]="small"
|
[class.small]="small"
|
||||||
|
[class.overlay]="showDot"
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
>
|
>
|
||||||
<mat-icon [svgIcon]="icon"></mat-icon>
|
<mat-icon [svgIcon]="icon"></mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
<div class="dot" *ngIf="showDot"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
|||||||
export class CircleButtonComponent implements OnInit {
|
export class CircleButtonComponent implements OnInit {
|
||||||
@Input() icon: string;
|
@Input() icon: string;
|
||||||
@Input() tooltip: string;
|
@Input() tooltip: string;
|
||||||
|
@Input() showDot = false;
|
||||||
@Input() tooltipPosition: 'above' | 'below' | 'before' | 'after' = 'above';
|
@Input() tooltipPosition: 'above' | 'below' | 'before' | 'after' = 'above';
|
||||||
@Input() tooltipClass: string;
|
@Input() tooltipClass: string;
|
||||||
@Input() disabled = false;
|
@Input() disabled = false;
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
<redaction-circle-button [matMenuTriggerFor]="overlay" icon="red:notification" [showDot]="hasUnread"></redaction-circle-button>
|
||||||
|
<mat-menu #overlay="matMenu" class="notifications-menu" backdropClass="notifications-backdrop">
|
||||||
|
<div *ngFor="let group of groupedNotifications | sortBy: 'desc':'dateString'">
|
||||||
|
<div class="all-caps-label">{{ day(group) }}</div>
|
||||||
|
<div
|
||||||
|
class="notification"
|
||||||
|
mat-menu-item
|
||||||
|
*ngFor="let notification of group.notifications | sortBy: 'desc':'eventTime'"
|
||||||
|
[class.unread]="!notification.read"
|
||||||
|
>
|
||||||
|
<redaction-initials-avatar></redaction-initials-avatar>
|
||||||
|
<div class="notification-content">
|
||||||
|
<div [innerHTML]="notification.message"></div>
|
||||||
|
<div class="small-label mt-2">{{ eventTime(notification.eventTime) }}</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="dot"
|
||||||
|
(click)="toggleRead(notification, $event)"
|
||||||
|
[matTooltip]="(notification.read ? 'notifications.mark-unread' : 'notifications.mark-read') | translate"
|
||||||
|
matTooltipPosition="before"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-menu>
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
@import '../../../assets/styles/red-variables';
|
||||||
|
|
||||||
|
.mt-2 {
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .notifications-backdrop + .cdk-overlay-connected-position-bounding-box {
|
||||||
|
right: 0 !important;
|
||||||
|
|
||||||
|
.notifications-menu {
|
||||||
|
min-width: 400px !important;
|
||||||
|
padding: 0 8px 16px;
|
||||||
|
max-height: calc(100vh - 200px);
|
||||||
|
|
||||||
|
.all-caps-label {
|
||||||
|
margin: 16px 0 6px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-menu-item.notification {
|
||||||
|
padding: 8px 26px 10px 8px;
|
||||||
|
margin: 2px 0 0 0;
|
||||||
|
height: fit-content;
|
||||||
|
position: relative;
|
||||||
|
line-height: 18px;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
.notification-content {
|
||||||
|
margin-top: 4px;
|
||||||
|
margin-left: 8px;
|
||||||
|
max-width: 320px;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
white-space: normal;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $accent;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
height: 8px;
|
||||||
|
width: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: $grey-4;
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $grey-6;
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
background-color: $grey-5;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.unread {
|
||||||
|
background-color: rgba($primary, 0.1);
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
background-color: $primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
interface Notification {
|
||||||
|
message: string;
|
||||||
|
eventTime: number;
|
||||||
|
read: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'redaction-notifications',
|
||||||
|
templateUrl: './notifications.component.html',
|
||||||
|
styleUrls: ['./notifications.component.scss']
|
||||||
|
})
|
||||||
|
export class NotificationsComponent implements OnInit {
|
||||||
|
public notifications: Notification[] = [
|
||||||
|
{ message: 'This is a notification with longer text wrapping on multiple lines', eventTime: 1607340971000, read: false },
|
||||||
|
{ message: 'This is a <a>link</a>', eventTime: 1607254981000, read: true },
|
||||||
|
{ message: 'This is a <b>notification 1</b>', eventTime: 1607254571000, read: false },
|
||||||
|
{ message: 'Notification', eventTime: 1607385727000, read: true },
|
||||||
|
{ message: 'Another notification', eventTime: 1606829412000, read: false }
|
||||||
|
];
|
||||||
|
public groupedNotifications: { dateString: string; notifications: Notification[] }[] = [];
|
||||||
|
|
||||||
|
constructor(private _translateService: TranslateService) {
|
||||||
|
this._groupNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {}
|
||||||
|
|
||||||
|
public get hasUnread() {
|
||||||
|
return this.notifications.filter((notification) => !notification.read).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _groupNotifications() {
|
||||||
|
const res = {};
|
||||||
|
for (const notification of this.notifications) {
|
||||||
|
const date = moment(notification.eventTime).format('YYYY-MM-DD');
|
||||||
|
if (!res[date]) res[date] = [];
|
||||||
|
res[date].push(notification);
|
||||||
|
}
|
||||||
|
for (const key of Object.keys(res)) {
|
||||||
|
this.groupedNotifications.push({ dateString: key, notifications: res[key] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public day(group: { dateString: string; notifications: Notification[] }): string {
|
||||||
|
moment.locale(this._translateService.currentLang);
|
||||||
|
return moment(group.notifications[0].eventTime).calendar({
|
||||||
|
sameDay: `[${this._translateService.instant('notifications.today')}]`,
|
||||||
|
lastDay: `[${this._translateService.instant('notifications.yesterday')}]`,
|
||||||
|
nextDay: `[${this._translateService.instant('notifications.tomorrow')}]`,
|
||||||
|
nextWeek: 'D MMMM',
|
||||||
|
lastWeek: 'D MMMM',
|
||||||
|
sameElse: 'D MMMM'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public eventTime(eventTime: number): string {
|
||||||
|
moment.locale(this._translateService.currentLang);
|
||||||
|
if (moment().isSame(eventTime, 'day')) {
|
||||||
|
return moment(eventTime).fromNow();
|
||||||
|
} else {
|
||||||
|
return moment(eventTime).format('hh:mm A');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggleRead(notification: Notification, $event) {
|
||||||
|
$event.stopPropagation();
|
||||||
|
notification.read = !notification.read;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -41,6 +41,7 @@ export class IconsModule {
|
|||||||
'logout',
|
'logout',
|
||||||
'menu',
|
'menu',
|
||||||
'needs-work',
|
'needs-work',
|
||||||
|
'notification',
|
||||||
'pages',
|
'pages',
|
||||||
'plus',
|
'plus',
|
||||||
'preview',
|
'preview',
|
||||||
|
|||||||
@ -56,8 +56,9 @@
|
|||||||
<span class="dev-mode" *ngIf="userPreferenceService.areDevFeaturesEnabled" translate="dev-mode"></span>
|
<span class="dev-mode" *ngIf="userPreferenceService.areDevFeaturesEnabled" translate="dev-mode"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu right flex-2">
|
<div class="menu right flex-2">
|
||||||
<redaction-user-button [user]="user" [matMenuTriggerFor]="menu"></redaction-user-button>
|
<redaction-notifications class="mr-8"></redaction-notifications>
|
||||||
<mat-menu #menu="matMenu">
|
<redaction-user-button [user]="user" [matMenuTriggerFor]="userMenu"></redaction-user-button>
|
||||||
|
<mat-menu #userMenu="matMenu">
|
||||||
<button
|
<button
|
||||||
*ngIf="permissionsService.isManager()"
|
*ngIf="permissionsService.isManager()"
|
||||||
(click)="appStateService.reset()"
|
(click)="appStateService.reset()"
|
||||||
|
|||||||
@ -547,5 +547,12 @@
|
|||||||
"search": "Search..."
|
"search": "Search..."
|
||||||
},
|
},
|
||||||
"dictionaries": "Dictionaries",
|
"dictionaries": "Dictionaries",
|
||||||
"user-management": "User Management"
|
"user-management": "User Management",
|
||||||
|
"notifications": {
|
||||||
|
"today": "Today",
|
||||||
|
"yesterday": "Yesterday",
|
||||||
|
"tomorrow": "Tomorrow",
|
||||||
|
"mark-read": "Mark as read",
|
||||||
|
"mark-unread": "Mark as unread"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
apps/red-ui/src/assets/icons/general/notification.svg
Normal file
17
apps/red-ui/src/assets/icons/general/notification.svg
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>AAFF7BC7-3583-4A2B-A91D-8A2F1A75D50E</title>
|
||||||
|
<g id="Notifications" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="01.-Notifications-Dropdown" transform="translate(-1258.000000, -23.000000)">
|
||||||
|
<rect x="0" y="0" width="1440" height="980"></rect>
|
||||||
|
<g id="Header-Project-Details">
|
||||||
|
<rect id="Rectangle" x="0" y="0" width="1440" height="60"></rect>
|
||||||
|
<g id="Group-6" transform="translate(1248.000000, 13.000000)">
|
||||||
|
<g id="status" transform="translate(10.000000, 10.000000)" fill="currentColor" fill-rule="nonzero">
|
||||||
|
<path d="M12.6,10.5 L12.32,10.5 C11.55,8.89 11.2,7.07 11.2,5.25 L11.2,4.2 C11.2,1.89 9.31,0 7,0 C4.69,0 2.8,1.89 2.8,4.2 L2.8,5.25 C2.8,7.07 2.38,8.82 1.68,10.5 L1.4,10.5 C0.98,10.5 0.7,10.78 0.7,11.2 C0.7,11.62 0.98,11.9 1.4,11.9 L4.9,11.9 L4.9,11.9 C4.9,13.09 5.81,14 7,14 C8.19,14 9.1,13.09 9.1,11.9 L9.1,11.9 L12.6,11.9 C13.02,11.9 13.3,11.62 13.3,11.2 C13.3,10.78 13.02,10.5 12.6,10.5 Z M7.7,11.9 C7.7,12.32 7.42,12.6 7,12.6 C6.58,12.6 6.3,12.32 6.3,11.9 L6.3,11.9 L7.7,11.9 C7.7,11.9 7.7,11.9 7.7,11.9 Z M3.15,10.5 C3.85,8.82 4.2,7.07 4.2,5.25 L4.2,4.2 C4.2,2.66 5.46,1.4 7,1.4 C8.54,1.4 9.8,2.66 9.8,4.2 L9.8,5.25 C9.8,7.07 10.15,8.82 10.85,10.5 L3.15,10.5 Z" id="Shape"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
@ -74,6 +74,7 @@ redaction-circle-button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
redaction-chevron-button,
|
redaction-chevron-button,
|
||||||
|
redaction-circle-button,
|
||||||
redaction-icon-button {
|
redaction-icon-button {
|
||||||
&[aria-expanded='true'] {
|
&[aria-expanded='true'] {
|
||||||
button {
|
button {
|
||||||
@ -81,11 +82,3 @@ redaction-icon-button {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
redaction-circle-button {
|
|
||||||
&[aria-expanded='true'] {
|
|
||||||
button {
|
|
||||||
background: $grey-4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user