Pull request #57: Notifications

Merge in RED/ui from notifications to master

* commit '836354d89acf3f904901751d531b0b3212f7adf1':
  Dummy data notifications
This commit is contained in:
Timo Bejan 2020-12-08 15:34:03 +01:00
commit 3279e56f6b
11 changed files with 206 additions and 12 deletions

View File

@ -91,6 +91,7 @@ import { AceEditorModule } from 'ng2-ace-editor';
import { TeamMembersComponent } from './components/team-members/team-members.component';
import { AdminBreadcrumbsComponent } from './components/admin-page-header/admin-breadcrumbs.component';
import { UserListingScreenComponent } from './screens/admin/users/user-listing-screen.component';
import { NotificationsComponent } from './components/notifications/notifications.component';
export function HttpLoaderFactory(httpClient: HttpClient) {
return new TranslateHttpLoader(httpClient, '/assets/i18n/', '.json');
@ -246,7 +247,8 @@ const matImports = [
DictionaryOverviewScreenComponent,
TeamMembersComponent,
AdminBreadcrumbsComponent,
UserListingScreenComponent
UserListingScreenComponent,
NotificationsComponent
],
imports: [
BrowserModule,

View File

@ -6,8 +6,10 @@
[class.warn]="type === 'warn'"
[disabled]="disabled"
[class.small]="small"
[class.overlay]="showDot"
mat-icon-button
>
<mat-icon [svgIcon]="icon"></mat-icon>
</button>
<div class="dot" *ngIf="showDot"></div>
</div>

View File

@ -8,6 +8,7 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
export class CircleButtonComponent implements OnInit {
@Input() icon: string;
@Input() tooltip: string;
@Input() showDot = false;
@Input() tooltipPosition: 'above' | 'below' | 'before' | 'after' = 'above';
@Input() tooltipClass: string;
@Input() disabled = false;

View File

@ -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>

View File

@ -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;
}
}
}
}
}

View File

@ -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;
}
}

View File

@ -41,6 +41,7 @@ export class IconsModule {
'logout',
'menu',
'needs-work',
'notification',
'pages',
'plus',
'preview',

View File

@ -56,8 +56,9 @@
<span class="dev-mode" *ngIf="userPreferenceService.areDevFeaturesEnabled" translate="dev-mode"></span>
</div>
<div class="menu right flex-2">
<redaction-user-button [user]="user" [matMenuTriggerFor]="menu"></redaction-user-button>
<mat-menu #menu="matMenu">
<redaction-notifications class="mr-8"></redaction-notifications>
<redaction-user-button [user]="user" [matMenuTriggerFor]="userMenu"></redaction-user-button>
<mat-menu #userMenu="matMenu">
<button
*ngIf="permissionsService.isManager()"
(click)="appStateService.reset()"

View File

@ -547,5 +547,12 @@
"search": "Search..."
},
"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"
}
}

View 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

View File

@ -74,6 +74,7 @@ redaction-circle-button {
}
redaction-chevron-button,
redaction-circle-button,
redaction-icon-button {
&[aria-expanded='true'] {
button {
@ -81,11 +82,3 @@ redaction-icon-button {
}
}
}
redaction-circle-button {
&[aria-expanded='true'] {
button {
background: $grey-4;
}
}
}