17 changed files with 915 additions and 14 deletions
@ -0,0 +1,46 @@ |
|||
///
|
|||
/// Copyright © 2016-2023 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Injectable } from '@angular/core'; |
|||
import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; |
|||
import { Observable } from 'rxjs'; |
|||
import { HttpClient } from '@angular/common/http'; |
|||
import { PageLink } from '@shared/models/page/page-link'; |
|||
import { PageData } from '@shared/models/page/page-data'; |
|||
import { AlarmComment, AlarmCommentInfo } from '@shared/models/alarm.models'; |
|||
|
|||
@Injectable({ |
|||
providedIn: 'root' |
|||
}) |
|||
export class AlarmCommentService { |
|||
|
|||
constructor( |
|||
private http: HttpClient |
|||
) { } |
|||
|
|||
public saveAlarmComment(alarmId: string, alarmComment: AlarmComment, config?: RequestConfig): Observable<AlarmComment> { |
|||
return this.http.post<AlarmComment>(`/api/alarm/${alarmId}/comment`, alarmComment, defaultHttpOptionsFromConfig(config)); |
|||
} |
|||
|
|||
public getAlarmComments(alarmId: string, pageLink: PageLink, config?: RequestConfig): Observable<PageData<AlarmCommentInfo>> { |
|||
return this.http.get<PageData<AlarmComment>>(`/api/alarm/${alarmId}/comment${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); |
|||
} |
|||
|
|||
public deleteAlarmComments(alarmId: string, commentId: string, config?: RequestConfig): Observable<void> { |
|||
return this.http.delete<void>(`/api/alarm/${alarmId}/comment/${commentId}`, defaultHttpOptionsFromConfig(config)); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2023 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<div style="min-width: 600px;"> |
|||
<mat-toolbar color="primary"> |
|||
<h2>{{ 'alarm.comments' | translate }}</h2> |
|||
<span fxFlex></span> |
|||
<button mat-icon-button |
|||
(click)="close()" |
|||
type="button"> |
|||
<mat-icon class="material-icons">close</mat-icon> |
|||
</button> |
|||
</mat-toolbar> |
|||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
|||
</mat-progress-bar> |
|||
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> |
|||
<div mat-dialog-content style="padding: 0"> |
|||
<tb-alarm-comment #alarmCommentComponent [alarmId]="alarmId" |
|||
[commentsHeaderEnabled]="commentsHeaderEnabled"> |
|||
</tb-alarm-comment> |
|||
</div> |
|||
<div mat-dialog-actions fxLayout="row"> |
|||
<button mat-button color="primary" |
|||
type="button" |
|||
[disabled]="(isLoading$ | async)" |
|||
(click)="close()" cdkFocusInitial> |
|||
{{ 'action.close' | translate }} |
|||
</button> |
|||
<span fxFlex></span> |
|||
<button mat-raised-button color="primary" |
|||
type="button" |
|||
(click)="changeSortDirection()"> |
|||
{{ 'alarm-comment.sort-direction' | translate}} |
|||
</button> |
|||
<button mat-raised-button color="primary" |
|||
type="button" |
|||
(click)="refresh()"> |
|||
{{ 'alarm-comment.refresh' | translate}} |
|||
</button> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,64 @@ |
|||
///
|
|||
/// Copyright © 2016-2023 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component, Inject, ViewChild } from '@angular/core'; |
|||
import { DialogComponent } from '@shared/components/dialog.component'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; |
|||
import { Router } from '@angular/router'; |
|||
import { AlarmInfo } from '@shared/models/alarm.models'; |
|||
import { AlarmCommentComponent } from '@home/components/alarm/alarm-comment.component'; |
|||
|
|||
export interface AlarmCommentDialogData { |
|||
alarmId?: string; |
|||
alarm?: AlarmInfo; |
|||
commentsHeaderEnabled: boolean; |
|||
} |
|||
|
|||
@Component({ |
|||
selector: 'tb-alarm-comment-dialog', |
|||
templateUrl: './alarm-comment-dialog.component.html', |
|||
styleUrls: [] |
|||
}) |
|||
export class AlarmCommentDialogComponent extends DialogComponent<AlarmCommentDialogComponent, void> { |
|||
|
|||
alarmId: string; |
|||
commentsHeaderEnabled: boolean = false; |
|||
|
|||
@ViewChild('alarmCommentComponent', { static: true }) alarmCommentComponent: AlarmCommentComponent; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
protected router: Router, |
|||
@Inject(MAT_DIALOG_DATA) public data: AlarmCommentDialogData, |
|||
public dialogRef: MatDialogRef<AlarmCommentDialogComponent, void>) { |
|||
super(store, router, dialogRef); |
|||
this.commentsHeaderEnabled = this.data.commentsHeaderEnabled |
|||
this.alarmId = this.data.alarmId; |
|||
} |
|||
|
|||
changeSortDirection() { |
|||
this.alarmCommentComponent.changeSortDirection(); |
|||
} |
|||
|
|||
refresh() { |
|||
this.alarmCommentComponent.loadAlarmComments(); |
|||
} |
|||
|
|||
close(): void { |
|||
this.dialogRef.close(); |
|||
} |
|||
} |
|||
@ -0,0 +1,161 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2023 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
|
|||
<div fxFlexFill [formGroup]="alarmCommentFormGroup" class="tb-alarm-comments" fxLayout="column"> |
|||
<div *ngIf="commentsHeaderEnabled" class="tb-alarm-comments-header" fxLayout="row" fxLayoutAlign="space-between baseline"> |
|||
<span class="tb-alarm-comments-header-title">{{ 'alarm-comment.comments' | translate }}</span> |
|||
<div> |
|||
<button mat-icon-button |
|||
type="button" |
|||
(click)="changeSortDirection()" |
|||
matTooltip="{{ 'alarm-comment.sort-direction' | translate }}" |
|||
matTooltipPosition="above"> |
|||
<mat-icon class="material-icons">{{ getSortDirectionIcon() }}</mat-icon> |
|||
</button> |
|||
<button mat-icon-button |
|||
type="button" |
|||
(click)="loadAlarmComments()" |
|||
matTooltip="{{ 'alarm-comment.refresh' | translate }}" |
|||
matTooltipPosition="above"> |
|||
<mat-icon class="material-icons">refresh</mat-icon> |
|||
</button> |
|||
</div> |
|||
</div> |
|||
<div fxLayout="column" fxLayoutGap="32px"> |
|||
<ng-container *ngIf="isDirectionDescending()"> |
|||
<ng-container *ngTemplateOutlet="commentInput"></ng-container> |
|||
</ng-container> |
|||
<div fxFlex *ngFor="let displayDataElement of displayData; let i = index"> |
|||
<div style="margin-left: 38px" |
|||
*ngIf="displayDataElement.isSystemComment; else userComment"> |
|||
<span class="tb-alarm-comments-system-text" |
|||
style="margin-right: 8px"> |
|||
{{ displayDataElement.commentText }} |
|||
</span> |
|||
<span class="tb-alarm-comments-time"> |
|||
{{ displayDataElement.createdDateAgo }} |
|||
</span> |
|||
</div> |
|||
<ng-template #userComment> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" |
|||
*ngIf="!displayDataElement.edit; else commentEditing" |
|||
(mouseenter)="onCommentMouseEnter(displayDataElement.commentId, i)" |
|||
(mouseleave)="onCommentMouseLeave(i)"> |
|||
<div fxLayout="row" fxLayoutAlign="center center" fxFlexAlign="start" |
|||
class="tb-alarm-comments-user-avatar" |
|||
[style.background-color]="displayDataElement.avatarBgColor"> |
|||
{{ getUserInitials(displayDataElement.displayName) }} |
|||
</div> |
|||
<div fxFlex fxLayout="column" fxLayoutGap="5px"> |
|||
<div fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center"> |
|||
<span class="tb-alarm-comments-user-name">{{ displayDataElement.displayName }}</span> |
|||
<span class="tb-alarm-comments-time" *ngIf="displayDataElement.isEdited"> |
|||
edited {{ displayDataElement.editedDateAgo }} |
|||
</span> |
|||
<span class="tb-alarm-comments-time" *ngIf="!displayDataElement.isEdited"> |
|||
{{ displayDataElement.createdDateAgo }} |
|||
</span> |
|||
</div> |
|||
<span class="tb-alarm-comments-text">{{ displayDataElement.commentText }}</span> |
|||
</div> |
|||
<div fxLayout="row" class="tb-alarm-comments-action-buttons" |
|||
[ngClass]="{ 'show-buttons': displayDataElement.showActions }"> |
|||
<button mat-button mat-icon-button |
|||
type="button" |
|||
(click)="editComment(displayDataElement.commentId)"> |
|||
<mat-icon class="material-icons">edit</mat-icon> |
|||
</button> |
|||
<button mat-button mat-icon-button |
|||
type="button" |
|||
(click)="deleteComment(displayDataElement.commentId)"> |
|||
<mat-icon class="material-icons">delete</mat-icon> |
|||
</button> |
|||
</div> |
|||
</div> |
|||
<ng-template #commentEditing> |
|||
<div fxLayoutAlign="row center" fxLayoutGap="8px"> |
|||
<div fxLayout="row" fxLayoutAlign="center center" |
|||
class="tb-alarm-comments-user-avatar" |
|||
[style.background-color]="displayDataElement.avatarBgColor"> |
|||
{{ getUserInitials(displayDataElement.displayName) }} |
|||
</div> |
|||
<mat-form-field fxFlex appearance="standard" class="mat-block"> |
|||
<textarea matInput |
|||
type="text" |
|||
placeholder="{{ 'alarm-comment.add' | translate }}" |
|||
cdkTextareaAutosize |
|||
cdkAutosizeMinRows="1" |
|||
cols="1" |
|||
formControlName="alarmCommentEdit" |
|||
(keyup.enter)="saveEditedComment(displayDataElement.commentId)" |
|||
(keydown.enter)="$event.preventDefault()"> |
|||
</textarea> |
|||
<div matSuffix fxLayout="row"> |
|||
<button mat-icon-button |
|||
(click)="cancelEdit(displayDataElement.commentId)" |
|||
type="button"> |
|||
<mat-icon class="material-icons red-button">close</mat-icon> |
|||
</button> |
|||
<button mat-icon-button |
|||
type="button" |
|||
(click)="saveEditedComment(displayDataElement.commentId)"> |
|||
<mat-icon class="material-icons green-button">check</mat-icon> |
|||
</button> |
|||
</div> |
|||
</mat-form-field> |
|||
</div> |
|||
</ng-template> |
|||
</ng-template> |
|||
</div> |
|||
<ng-container *ngIf="isDirectionAscending()"> |
|||
<ng-container *ngTemplateOutlet="commentInput"></ng-container> |
|||
</ng-container> |
|||
</div> |
|||
<ng-template #commentInput> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" fxFlex> |
|||
<div fxLayout="row" fxLayoutAlign="center center" |
|||
class="tb-alarm-comments-user-avatar" |
|||
*ngIf="userDisplayName$ | async; let userDisplayName" |
|||
[style.background-color]="getCurrentUserBgColor(userDisplayName)"> |
|||
{{ getUserInitials(userDisplayName) }} |
|||
</div> |
|||
<mat-form-field appearance="standard" fxFlex class="mat-block"> |
|||
<textarea matInput |
|||
type="text" |
|||
placeholder="{{ 'alarm-comment.add' | translate }}" |
|||
cdkTextareaAutosize |
|||
cdkAutosizeMinRows="1" |
|||
cols="1" |
|||
formControlName="alarmComment" |
|||
(keyup.enter)="saveComment()" |
|||
(keydown.enter)="$event.preventDefault()"> |
|||
</textarea> |
|||
<button mat-button |
|||
mat-icon-button |
|||
type="button" |
|||
matSuffix |
|||
*ngIf="getAlarmCommentFormControl().value" |
|||
(click)="saveComment()"> |
|||
<mat-icon color="primary"> |
|||
send |
|||
</mat-icon> |
|||
</button> |
|||
</mat-form-field> |
|||
</div> |
|||
</ng-template> |
|||
</div> |
|||
@ -0,0 +1,107 @@ |
|||
/** |
|||
* Copyright © 2016-2023 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
:host { |
|||
.tb-alarm-comments { |
|||
padding: 16px 24px 24px 24px; |
|||
background-color: #fafafa; |
|||
max-width: 600px; |
|||
|
|||
&-header { |
|||
background-color: #fafafa; |
|||
position: sticky; |
|||
top: -25px; |
|||
z-index: 1; |
|||
margin-bottom: 10px; |
|||
|
|||
&-title { |
|||
color: rgba(0, 0, 0, 0.76); |
|||
letter-spacing: 0.25px; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.mat-icon { |
|||
color: rgba(0, 0, 0, 0.38); |
|||
} |
|||
} |
|||
|
|||
&-user-avatar { |
|||
width: 28px; |
|||
min-width: 28px; |
|||
height: 28px; |
|||
min-height: 28px; |
|||
border-radius: 50%; |
|||
font-weight: 700; |
|||
color: #FFFFFF; |
|||
font-size: 13px; |
|||
} |
|||
|
|||
&-user-name { |
|||
font-size: 16px; |
|||
color: rgba(0, 0, 0, 0.76); |
|||
font-weight: 500; |
|||
letter-spacing: 0.25px; |
|||
} |
|||
|
|||
&-time { |
|||
font-size: 14px; |
|||
font-weight: 400; |
|||
color: rgba(0, 0, 0, 0.38); |
|||
letter-spacing: 0.2px |
|||
} |
|||
|
|||
&-system-text { |
|||
color: rgba(0, 0, 0, 0.38); |
|||
font-weight: 500; |
|||
letter-spacing: 0.25px; |
|||
} |
|||
|
|||
&-text { |
|||
white-space: pre-line; |
|||
word-break: break-word; |
|||
color: rgba(0, 0, 0, 0.54); |
|||
letter-spacing: 0.15px; |
|||
} |
|||
|
|||
&-action-buttons { |
|||
visibility: hidden; |
|||
.mat-icon { |
|||
color: rgba(0, 0, 0, 0.38); |
|||
} |
|||
} |
|||
|
|||
.show-buttons { |
|||
visibility: visible; |
|||
} |
|||
|
|||
.green-button { |
|||
color: #00695C; |
|||
} |
|||
|
|||
.red-button { |
|||
color: #D12730; |
|||
} |
|||
|
|||
.mat-form-field { |
|||
font-size: 16px; |
|||
letter-spacing: 0.15px; |
|||
color: rgba(0, 0, 0, 0.76); |
|||
} |
|||
|
|||
textarea { |
|||
letter-spacing: 0.15px; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,295 @@ |
|||
///
|
|||
/// Copyright © 2016-2023 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component, Input, OnInit } from '@angular/core'; |
|||
import { select, Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { AlarmCommentService } from '@core/http/alarm-comment.service'; |
|||
import { AbstractControl, FormBuilder, FormGroup } from '@angular/forms'; |
|||
import { DialogService } from '@core/services/dialog.service'; |
|||
import { AuthUser, User } from '@shared/models/user.model'; |
|||
import { getCurrentAuthUser, selectUserDetails } from '@core/auth/auth.selectors'; |
|||
import { Direction, SortOrder } from '@shared/models/page/sort-order'; |
|||
import { MAX_SAFE_PAGE_SIZE, PageLink } from '@shared/models/page/page-link'; |
|||
import { DateAgoPipe } from '@shared/pipe/date-ago.pipe'; |
|||
import { map } from 'rxjs/operators'; |
|||
import { AlarmComment, AlarmCommentInfo, AlarmCommentType } from '@shared/models/alarm.models'; |
|||
import { UtilsService } from '@core/services/utils.service'; |
|||
import { EntityType } from '@shared/models/entity-type.models'; |
|||
|
|||
interface AlarmCommentsDisplayData { |
|||
commentId?: string, |
|||
displayName?: string, |
|||
createdDateAgo?: string, |
|||
edit?: boolean, |
|||
isEdited?: boolean, |
|||
editedDateAgo?: string, |
|||
showActions?: boolean, |
|||
commentText?: string, |
|||
isSystemComment?: boolean, |
|||
avatarBgColor?: string |
|||
} |
|||
|
|||
@Component({ |
|||
selector: 'tb-alarm-comment', |
|||
templateUrl: './alarm-comment.component.html', |
|||
styleUrls: ['./alarm-comment.component.scss'] |
|||
}) |
|||
export class AlarmCommentComponent implements OnInit { |
|||
@Input() |
|||
alarmId: string; |
|||
|
|||
@Input() |
|||
commentsHeaderEnabled: boolean = true; |
|||
|
|||
authUser: AuthUser; |
|||
|
|||
alarmCommentFormGroup: FormGroup; |
|||
|
|||
alarmComments: Array<AlarmComment>; |
|||
|
|||
displayData: Array<AlarmCommentsDisplayData> = new Array<AlarmCommentsDisplayData>(); |
|||
|
|||
alarmCommentSortOrder: SortOrder = { |
|||
property: 'createdTime', |
|||
direction: Direction.DESC |
|||
}; |
|||
|
|||
editMode: boolean = false; |
|||
|
|||
userDisplayName$ = this.store.pipe( |
|||
select(selectUserDetails), |
|||
map((user) => this.getUserDisplayName(user)) |
|||
); |
|||
|
|||
currentUserDisplayName: string; |
|||
currentUserAvatarColor: string; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
private translate: TranslateService, |
|||
private alarmCommentService: AlarmCommentService, |
|||
public fb: FormBuilder, |
|||
private dialogService: DialogService, |
|||
public dateAgoPipe: DateAgoPipe, |
|||
private utilsService: UtilsService) { |
|||
|
|||
this.authUser = getCurrentAuthUser(store); |
|||
|
|||
this.alarmCommentFormGroup = this.fb.group( |
|||
{ |
|||
alarmCommentEdit: [''], |
|||
alarmComment: [''] |
|||
} |
|||
); |
|||
} |
|||
|
|||
ngOnInit() { |
|||
this.loadAlarmComments(); |
|||
this.currentUserAvatarColor = this.utilsService.stringToHslColor(this.currentUserDisplayName, |
|||
60, 40); |
|||
} |
|||
|
|||
loadAlarmComments(): void { |
|||
this.alarmCommentService.getAlarmComments(this.alarmId, new PageLink(MAX_SAFE_PAGE_SIZE, 0, null, |
|||
this.alarmCommentSortOrder)).subscribe( |
|||
(pagedData) => { |
|||
this.alarmComments = pagedData.data; |
|||
this.displayData.length = 0; |
|||
for (let alarmComment of pagedData.data) { |
|||
let displayDataElement: AlarmCommentsDisplayData = {}; |
|||
displayDataElement.createdDateAgo = this.dateAgoPipe.transform(alarmComment.createdTime); |
|||
displayDataElement.commentText = alarmComment.comment.text; |
|||
displayDataElement.isSystemComment = alarmComment.type === AlarmCommentType.SYSTEM; |
|||
if (alarmComment.type === AlarmCommentType.OTHER) { |
|||
displayDataElement.commentId = alarmComment.id.id; |
|||
displayDataElement.displayName = this.getUserDisplayName(alarmComment); |
|||
displayDataElement.edit = false; |
|||
displayDataElement.isEdited = alarmComment.comment.edited; |
|||
displayDataElement.editedDateAgo = this.dateAgoPipe.transform(alarmComment.comment.editedOn).toLowerCase(); |
|||
displayDataElement.showActions = false; |
|||
displayDataElement.isSystemComment = false; |
|||
displayDataElement.avatarBgColor = this.utilsService.stringToHslColor(displayDataElement.displayName, |
|||
40, 60); |
|||
} |
|||
this.displayData.push(displayDataElement); |
|||
} |
|||
} |
|||
) |
|||
} |
|||
|
|||
changeSortDirection() { |
|||
let currentDirection = this.alarmCommentSortOrder.direction; |
|||
this.alarmCommentSortOrder.direction = currentDirection === Direction.DESC ? Direction.ASC : Direction.DESC; |
|||
this.loadAlarmComments(); |
|||
} |
|||
|
|||
saveComment(): void { |
|||
const commentInputValue: string = this.getAlarmCommentFormControl().value; |
|||
if (commentInputValue) { |
|||
const comment: AlarmComment = { |
|||
alarmId: { |
|||
id: this.alarmId, |
|||
entityType: EntityType.ALARM |
|||
}, |
|||
type: AlarmCommentType.OTHER, |
|||
comment: { |
|||
text: commentInputValue |
|||
} |
|||
} |
|||
this.doSave(comment); |
|||
this.clearCommentInput(); |
|||
} |
|||
} |
|||
|
|||
saveEditedComment(commentId: string): void { |
|||
const commentEditInputValue: string = this.getAlarmCommentEditFormControl().value; |
|||
if (commentEditInputValue) { |
|||
const editedComment: AlarmComment = this.getAlarmCommentById(commentId); |
|||
editedComment.comment.text = commentEditInputValue; |
|||
this.doSave(editedComment); |
|||
this.clearCommentEditInput(); |
|||
this.editMode = false; |
|||
this.getAlarmCommentFormControl().enable({emitEvent: false}); |
|||
} |
|||
} |
|||
|
|||
private doSave(comment: AlarmComment): void { |
|||
this.alarmCommentService.saveAlarmComment(this.alarmId, comment).subscribe( |
|||
() => { |
|||
this.loadAlarmComments(); |
|||
} |
|||
) |
|||
} |
|||
|
|||
editComment(commentId: string): void { |
|||
const commentDisplayData = this.getDataElementByCommentId(commentId); |
|||
commentDisplayData.edit = true; |
|||
this.editMode = true; |
|||
this.getAlarmCommentEditFormControl().patchValue(commentDisplayData.commentText); |
|||
this.getAlarmCommentFormControl().disable({emitEvent: false}); |
|||
} |
|||
|
|||
cancelEdit(commentId: string): void { |
|||
const commentDisplayData = this.getDataElementByCommentId(commentId); |
|||
commentDisplayData.edit = false; |
|||
this.editMode = false; |
|||
this.getAlarmCommentFormControl().enable({emitEvent: false}); |
|||
} |
|||
|
|||
deleteComment(commentId: string): void { |
|||
const alarmCommentInfo: AlarmComment = this.getAlarmCommentById(commentId); |
|||
const commentText: string = alarmCommentInfo.comment.text; |
|||
this.dialogService.confirm( |
|||
this.translate.instant('alarm-comment.delete-alarm-comment'), |
|||
commentText, |
|||
this.translate.instant('action.cancel'), |
|||
this.translate.instant('action.delete')).subscribe( |
|||
(result) => { |
|||
if (result) { |
|||
this.alarmCommentService.deleteAlarmComments(this.alarmId, commentId).subscribe( |
|||
() => { |
|||
this.loadAlarmComments(); |
|||
} |
|||
) |
|||
} |
|||
} |
|||
) |
|||
} |
|||
|
|||
getSortDirectionIcon() { |
|||
return this.alarmCommentSortOrder.direction === Direction.DESC ? 'arrow_downward' : 'arrow_upward' |
|||
} |
|||
|
|||
isDirectionAscending() { |
|||
return this.alarmCommentSortOrder.direction === Direction.ASC; |
|||
} |
|||
|
|||
isDirectionDescending() { |
|||
return this.alarmCommentSortOrder.direction === Direction.DESC; |
|||
} |
|||
|
|||
onCommentMouseEnter(commentId: string, displayDataIndex: number): void { |
|||
if (!this.editMode) { |
|||
const alarmUserId = this.getAlarmCommentById(commentId).userId.id; |
|||
if (this.authUser.userId === alarmUserId) { |
|||
this.displayData[displayDataIndex].showActions = true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
onCommentMouseLeave(displayDataIndex: number): void { |
|||
this.displayData[displayDataIndex].showActions = false; |
|||
} |
|||
|
|||
getUserInitials(userName: string): string { |
|||
let initials = ''; |
|||
const userNameSplit = userName.split(' '); |
|||
initials += userNameSplit[0].charAt(0).toUpperCase(); |
|||
if (userNameSplit.length > 1) { |
|||
initials += userNameSplit[userNameSplit.length - 1].charAt(0).toUpperCase(); |
|||
} |
|||
return initials; |
|||
} |
|||
|
|||
getCurrentUserBgColor(userDisplayName: string) { |
|||
return this.utilsService.stringToHslColor(userDisplayName, 40, 60); |
|||
} |
|||
|
|||
private getUserDisplayName(alarmCommentInfo: AlarmCommentInfo | User): string { |
|||
let name = ''; |
|||
if ((alarmCommentInfo.firstName && alarmCommentInfo.firstName.length > 0) || |
|||
(alarmCommentInfo.lastName && alarmCommentInfo.lastName.length > 0)) { |
|||
if (alarmCommentInfo.firstName) { |
|||
name += alarmCommentInfo.firstName; |
|||
} |
|||
if (alarmCommentInfo.lastName) { |
|||
if (name.length > 0) { |
|||
name += ' '; |
|||
} |
|||
name += alarmCommentInfo.lastName; |
|||
} |
|||
} else { |
|||
name = alarmCommentInfo.email; |
|||
} |
|||
return name; |
|||
} |
|||
|
|||
getAlarmCommentFormControl(): AbstractControl { |
|||
return this.alarmCommentFormGroup.get('alarmComment'); |
|||
} |
|||
|
|||
getAlarmCommentEditFormControl(): AbstractControl { |
|||
return this.alarmCommentFormGroup.get('alarmCommentEdit'); |
|||
} |
|||
|
|||
private clearCommentInput(): void { |
|||
this.getAlarmCommentFormControl().patchValue(''); |
|||
} |
|||
|
|||
private clearCommentEditInput(): void { |
|||
this.getAlarmCommentEditFormControl().patchValue(''); |
|||
} |
|||
|
|||
private getAlarmCommentById(id: string): AlarmComment { |
|||
return this.alarmComments.find(comment => comment.id.id === id); |
|||
} |
|||
|
|||
private getDataElementByCommentId(commentId: string): AlarmCommentsDisplayData { |
|||
return this.displayData.find(commentDisplayData => commentDisplayData.commentId === commentId); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
///
|
|||
/// Copyright © 2016-2023 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { HasUUID } from '@shared/models/id/has-uuid'; |
|||
|
|||
export class AlarmCommentId implements HasUUID { |
|||
id: string; |
|||
constructor(id: string) { |
|||
this.id = id; |
|||
} |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
///
|
|||
/// Copyright © 2016-2023 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Inject, Pipe, PipeTransform } from '@angular/core'; |
|||
import { DAY, HOUR, MINUTE, SECOND, WEEK, YEAR } from '@shared/models/time/time.models'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
|
|||
const intervals = { |
|||
years: YEAR, |
|||
months: DAY * 30, |
|||
weeks: WEEK, |
|||
days: DAY, |
|||
hr: HOUR, |
|||
min: MINUTE, |
|||
sec: SECOND |
|||
}; |
|||
|
|||
@Pipe({ |
|||
name: 'dateAgo' |
|||
}) |
|||
export class DateAgoPipe implements PipeTransform { |
|||
|
|||
constructor(@Inject(TranslateService) private translate: TranslateService) { |
|||
|
|||
} |
|||
|
|||
transform(value: number): string { |
|||
if (value) { |
|||
const ms = Math.floor((+new Date() - +new Date(value))); |
|||
if (ms < 29 * SECOND) { // less than 30 seconds ago will show as 'Just now'
|
|||
return this.translate.instant('timewindow.just-now'); |
|||
} |
|||
let counter; |
|||
// tslint:disable-next-line:forin
|
|||
for (const i in intervals) { |
|||
counter = Math.floor(ms / intervals[i]); |
|||
if (counter > 0) { |
|||
return this.translate.instant(`timewindow.${i}`, {[i]: counter}); |
|||
} |
|||
} |
|||
} |
|||
return ''; |
|||
} |
|||
|
|||
} |
|||
Loading…
Reference in new issue