committed by
GitHub
25 changed files with 1301 additions and 19 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,20 @@ |
|||
/** |
|||
* Copyright © 2016-2024 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 '../../../../../scss/constants'; |
|||
|
|||
.tb-no-notification-svg-color { |
|||
color: $tb-primary-color; |
|||
} |
|||
@ -0,0 +1,128 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2024 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. |
|||
|
|||
--> |
|||
<ng-container [formGroup]="unreadNotificationWidgetConfigForm"> |
|||
|
|||
<div class="tb-form-panel"> |
|||
<div class="tb-form-panel-title" translate>widget-config.appearance</div> |
|||
<div class="tb-form-row column-xs"> |
|||
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTitle"> |
|||
{{ 'widget-config.title' | translate }} |
|||
</mat-slide-toggle> |
|||
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic"> |
|||
<input matInput formControlName="title" placeholder="{{ 'widget-config.set' | translate }}"> |
|||
</mat-form-field> |
|||
<tb-font-settings formControlName="titleFont" |
|||
clearButton |
|||
[previewText]="unreadNotificationWidgetConfigForm.get('title').value" |
|||
[initialPreviewStyle]="widgetConfig.config.titleStyle"> |
|||
</tb-font-settings> |
|||
<tb-color-input asBoxInput |
|||
colorClearButton |
|||
formControlName="titleColor"> |
|||
</tb-color-input> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-row column-xs"> |
|||
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showIcon"> |
|||
{{ 'widgets.notification.icon' | translate }} |
|||
</mat-slide-toggle> |
|||
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<mat-form-field appearance="outline" class="flex number" subscriptSizing="dynamic"> |
|||
<input matInput type="number" min="0" formControlName="iconSize" placeholder="{{ 'widget-config.set' | translate }}"> |
|||
</mat-form-field> |
|||
<tb-css-unit-select fxFlex formControlName="iconSizeUnit"></tb-css-unit-select> |
|||
<tb-material-icon-select asBoxInput |
|||
iconClearButton |
|||
[color]="unreadNotificationWidgetConfigForm.get('iconColor').value" |
|||
formControlName="icon"> |
|||
</tb-material-icon-select> |
|||
<tb-color-input asBoxInput |
|||
colorClearButton |
|||
formControlName="iconColor"> |
|||
</tb-color-input> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div>{{ 'widgets.notification.max-notification-display' | translate }}</div> |
|||
<mat-form-field appearance="outline" subscriptSizing="dynamic"> |
|||
<input matInput type="number" min="1" formControlName="maxNotificationDisplay" placeholder="{{ 'widget-config.set' | translate }}"> |
|||
</mat-form-field> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="tb-form-panel"> |
|||
<div class="tb-form-row no-padding no-border"> |
|||
<mat-slide-toggle class="mat-slide" formControlName="showCounter"> |
|||
{{ 'widgets.notification.counter' | translate }} |
|||
</mat-slide-toggle> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div>{{ 'widgets.notification.counter-value' | translate }}</div> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<tb-font-settings formControlName="counterValueFont" |
|||
clearButton |
|||
[previewText]="unreadNotificationWidgetConfigForm.get('maxNotificationDisplay').value.toString()" |
|||
[initialPreviewStyle]="widgetConfig.config.titleStyle"> |
|||
</tb-font-settings> |
|||
<tb-color-input asBoxInput |
|||
formControlName="counterValueColor"> |
|||
</tb-color-input> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div>{{ 'widgets.notification.counter-color' | translate }}</div> |
|||
<tb-color-input asBoxInput |
|||
formControlName="counterColor"> |
|||
</tb-color-input> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="tb-form-panel"> |
|||
<div class="tb-form-panel-title" translate>widget-config.appearance</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div>{{ 'widgets.background.background' | translate }}</div> |
|||
<tb-background-settings formControlName="background"> |
|||
</tb-background-settings> |
|||
</div> |
|||
<div class="tb-form-row space-between column-lt-md"> |
|||
<div translate>widget-config.show-card-buttons</div> |
|||
<mat-chip-listbox multiple formControlName="cardButtons"> |
|||
<mat-chip-option value="viewAll">{{ 'widgets.notification.button-view-all' | translate }}</mat-chip-option> |
|||
<mat-chip-option value="filter">{{ 'widgets.notification.button-filter' | translate }}</mat-chip-option> |
|||
<mat-chip-option value="markAsRead">{{ 'widgets.notification.button-mark-read' | translate }}</mat-chip-option> |
|||
<mat-chip-option value="fullscreen">{{ 'fullscreen.fullscreen' | translate }}</mat-chip-option> |
|||
</mat-chip-listbox> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div>{{ 'widget-config.card-border-radius' | translate }}</div> |
|||
<mat-form-field appearance="outline" subscriptSizing="dynamic"> |
|||
<input matInput formControlName="borderRadius" placeholder="{{ 'widget-config.set' | translate }}"> |
|||
</mat-form-field> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div>{{ 'widget-config.card-padding' | translate }}</div> |
|||
<mat-form-field appearance="outline" subscriptSizing="dynamic"> |
|||
<input matInput formControlName="padding" placeholder="{{ 'widget-config.set' | translate }}"> |
|||
</mat-form-field> |
|||
</div> |
|||
</div> |
|||
<tb-widget-actions-panel |
|||
formControlName="actions"> |
|||
</tb-widget-actions-panel> |
|||
</ng-container> |
|||
@ -0,0 +1,177 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 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 } from '@angular/core'; |
|||
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models'; |
|||
import { WidgetConfigComponentData } from '@home/models/widget-component.models'; |
|||
import { WidgetConfig, } from '@shared/models/widget.models'; |
|||
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; |
|||
import { isUndefined } from '@core/utils'; |
|||
import { cssSizeToStrSize, resolveCssSize } from '@shared/models/widget-settings.models'; |
|||
import { |
|||
unreadNotificationDefaultSettings, |
|||
UnreadNotificationWidgetSettings |
|||
} from '@home/components/widget/lib/cards/unread-notification-widget.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-unread-notification-basic-config', |
|||
templateUrl: './unread-notification-basic-config.component.html', |
|||
styleUrls: ['../basic-config.scss'] |
|||
}) |
|||
export class UnreadNotificationBasicConfigComponent extends BasicWidgetConfigComponent { |
|||
|
|||
unreadNotificationWidgetConfigForm: UntypedFormGroup; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
protected widgetConfigComponent: WidgetConfigComponent, |
|||
private fb: UntypedFormBuilder) { |
|||
super(store, widgetConfigComponent); |
|||
} |
|||
|
|||
protected configForm(): UntypedFormGroup { |
|||
return this.unreadNotificationWidgetConfigForm; |
|||
} |
|||
|
|||
protected onConfigSet(configData: WidgetConfigComponentData) { |
|||
const iconSize = resolveCssSize(configData.config.iconSize); |
|||
const settings: UnreadNotificationWidgetSettings = {...unreadNotificationDefaultSettings, ...(configData.config.settings || {})}; |
|||
this.unreadNotificationWidgetConfigForm = this.fb.group({ |
|||
|
|||
showTitle: [configData.config.showTitle, []], |
|||
title: [configData.config.title, []], |
|||
titleFont: [configData.config.titleFont, []], |
|||
titleColor: [configData.config.titleColor, []], |
|||
|
|||
showIcon: [configData.config.showTitleIcon, []], |
|||
iconSize: [iconSize[0], [Validators.min(0)]], |
|||
iconSizeUnit: [iconSize[1], []], |
|||
icon: [configData.config.titleIcon, []], |
|||
iconColor: [configData.config.iconColor, []], |
|||
|
|||
maxNotificationDisplay: [settings.maxNotificationDisplay, [Validators.required, Validators.min(1)]], |
|||
showCounter: [settings.showCounter, []], |
|||
counterValueFont: [settings.counterValueFont, []], |
|||
counterValueColor: [settings.counterValueColor, []], |
|||
counterColor: [settings.counterColor, []], |
|||
|
|||
background: [settings.background, []], |
|||
padding: [settings.padding, []], |
|||
|
|||
cardButtons: [this.getCardButtons(configData.config), []], |
|||
borderRadius: [configData.config.borderRadius, []], |
|||
actions: [configData.config.actions || {}, []] |
|||
}); |
|||
} |
|||
protected validatorTriggers(): string[] { |
|||
return ['showCounter', 'showTitle', 'showIcon']; |
|||
} |
|||
|
|||
protected updateValidators(emitEvent: boolean, trigger?: string) { |
|||
const showCounter: boolean = this.unreadNotificationWidgetConfigForm.get('showCounter').value; |
|||
const showTitle: boolean = this.unreadNotificationWidgetConfigForm.get('showTitle').value; |
|||
const showIcon: boolean = this.unreadNotificationWidgetConfigForm.get('showIcon').value; |
|||
|
|||
if (showTitle) { |
|||
this.unreadNotificationWidgetConfigForm.get('title').enable({emitEvent}); |
|||
this.unreadNotificationWidgetConfigForm.get('titleFont').enable({emitEvent}); |
|||
this.unreadNotificationWidgetConfigForm.get('titleColor').enable({emitEvent}); |
|||
} else { |
|||
this.unreadNotificationWidgetConfigForm.get('title').disable({emitEvent}); |
|||
this.unreadNotificationWidgetConfigForm.get('titleFont').disable({emitEvent}); |
|||
this.unreadNotificationWidgetConfigForm.get('titleColor').disable({emitEvent}); |
|||
} |
|||
|
|||
if (showIcon) { |
|||
this.unreadNotificationWidgetConfigForm.get('iconSize').enable({emitEvent}); |
|||
this.unreadNotificationWidgetConfigForm.get('iconSizeUnit').enable({emitEvent}); |
|||
this.unreadNotificationWidgetConfigForm.get('icon').enable({emitEvent}); |
|||
this.unreadNotificationWidgetConfigForm.get('iconColor').enable({emitEvent}); |
|||
} else { |
|||
this.unreadNotificationWidgetConfigForm.get('iconSize').disable({emitEvent}); |
|||
this.unreadNotificationWidgetConfigForm.get('iconSizeUnit').disable({emitEvent}); |
|||
this.unreadNotificationWidgetConfigForm.get('icon').disable({emitEvent}); |
|||
this.unreadNotificationWidgetConfigForm.get('iconColor').disable({emitEvent}); |
|||
} |
|||
|
|||
if (showCounter) { |
|||
this.unreadNotificationWidgetConfigForm.get('counterValueFont').enable({emitEvent}); |
|||
this.unreadNotificationWidgetConfigForm.get('counterValueColor').enable({emitEvent}); |
|||
this.unreadNotificationWidgetConfigForm.get('counterColor').enable({emitEvent}); |
|||
} else { |
|||
this.unreadNotificationWidgetConfigForm.get('counterValueFont').disable({emitEvent}); |
|||
this.unreadNotificationWidgetConfigForm.get('counterValueColor').disable({emitEvent}); |
|||
this.unreadNotificationWidgetConfigForm.get('counterColor').disable({emitEvent}); |
|||
} |
|||
} |
|||
|
|||
protected prepareOutputConfig(config: any): WidgetConfigComponentData { |
|||
|
|||
this.widgetConfig.config.showTitle = config.showTitle; |
|||
this.widgetConfig.config.title = config.title; |
|||
this.widgetConfig.config.titleFont = config.titleFont; |
|||
this.widgetConfig.config.titleColor = config.titleColor; |
|||
|
|||
this.widgetConfig.config.showTitleIcon = config.showIcon; |
|||
this.widgetConfig.config.iconSize = cssSizeToStrSize(config.iconSize, config.iconSizeUnit); |
|||
this.widgetConfig.config.titleIcon = config.icon; |
|||
this.widgetConfig.config.iconColor = config.iconColor; |
|||
|
|||
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {}; |
|||
|
|||
this.widgetConfig.config.settings.maxNotificationDisplay = config.maxNotificationDisplay; |
|||
this.widgetConfig.config.settings.showCounter = config.showCounter; |
|||
this.widgetConfig.config.settings.counterValueFont = config.counterValueFont; |
|||
this.widgetConfig.config.settings.counterValueColor = config.counterValueColor; |
|||
this.widgetConfig.config.settings.counterColor = config.counterColor; |
|||
|
|||
this.widgetConfig.config.settings.background = config.background; |
|||
this.widgetConfig.config.settings.padding = config.padding; |
|||
|
|||
this.widgetConfig.config.actions = config.actions; |
|||
this.setCardButtons(config.cardButtons, this.widgetConfig.config); |
|||
this.widgetConfig.config.borderRadius = config.borderRadius; |
|||
return this.widgetConfig; |
|||
} |
|||
|
|||
private getCardButtons(config: WidgetConfig): string[] { |
|||
const buttons: string[] = []; |
|||
if (isUndefined(config.settings?.enableViewAll) || config.settings?.enableViewAll) { |
|||
buttons.push('viewAll'); |
|||
} |
|||
if (isUndefined(config.settings?.enableFilter) || config.settings?.enableFilter) { |
|||
buttons.push('filter'); |
|||
} |
|||
if (isUndefined(config.settings?.enableMarkAsRead) || config.settings?.enableMarkAsRead) { |
|||
buttons.push('markAsRead'); |
|||
} |
|||
if (isUndefined(config.enableFullscreen) || config.enableFullscreen) { |
|||
buttons.push('fullscreen'); |
|||
} |
|||
return buttons; |
|||
} |
|||
|
|||
private setCardButtons(buttons: string[], config: WidgetConfig) { |
|||
config.settings.enableViewAll = buttons.includes('viewAll'); |
|||
config.settings.enableFilter = buttons.includes('filter'); |
|||
config.settings.enableMarkAsRead = buttons.includes('markAsRead'); |
|||
|
|||
config.enableFullscreen = buttons.includes('fullscreen'); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2024 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. |
|||
|
|||
--> |
|||
<form fxLayout="column" fxFlex class="mat-content mat-padding" (ngSubmit)="update()"> |
|||
<div class="tb-form-panel no-padding"> |
|||
<div class="tb-form-row column-xs"> |
|||
<div class="fixed-title-width" ngClass.xs="filters-title-mobile" translate>widgets.notification.notification-types</div> |
|||
<mat-form-field floatLabel="auto" appearance="outline" subscriptSizing="dynamic" class="flex fb-chips"> |
|||
<mat-chip-grid #chipList> |
|||
<mat-chip-row |
|||
*ngFor="let type of selectedNotificationTypes" |
|||
removable |
|||
(removed)="remove(type)"> |
|||
{{ notificationTypesTranslateMap.get(type).name | translate }} |
|||
<mat-icon matChipRemove>close</mat-icon> |
|||
</mat-chip-row> |
|||
<input matInput type="text" |
|||
placeholder="{{ ( selectedNotificationTypes.length ? 'widgets.notification.notification-type' : 'widgets.notification.any-type' ) | translate }}" |
|||
style="max-width: 200px;" |
|||
[formControl]="searchControlName" |
|||
#notificationTypeInput |
|||
(focusin)="onFocus()" |
|||
matAutocompleteOrigin |
|||
#origin="matAutocompleteOrigin" |
|||
[matAutocompleteConnectedTo]="origin" |
|||
[matAutocomplete]="NotificationTypeAutocomplete" |
|||
[matChipInputFor]="chipList" |
|||
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" |
|||
(matChipInputTokenEnd)="chipAdd($event)"> |
|||
</mat-chip-grid> |
|||
<mat-autocomplete #NotificationTypeAutocomplete="matAutocomplete" |
|||
class="tb-autocomplete" |
|||
(optionSelected)="selected($event)" |
|||
[displayWith]="displayTypeFn"> |
|||
<mat-option *ngFor="let type of filteredNotificationTypesList | async" [value]="type"> |
|||
<span [innerHTML]="notificationTypesTranslateMap.get(type).name | translate | highlight:searchText"></span> |
|||
</mat-option> |
|||
</mat-autocomplete> |
|||
</mat-form-field> |
|||
</div> |
|||
</div> |
|||
|
|||
<div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center"> |
|||
<button type="button" |
|||
mat-button |
|||
(click)="reset()" |
|||
color="primary"> |
|||
{{ 'action.reset' | translate }} |
|||
</button> |
|||
<span fxFlex></span> |
|||
<button type="button" |
|||
(click)="cancel()" |
|||
mat-button> |
|||
{{ 'action.cancel' | translate }} |
|||
</button> |
|||
<button type="submit" |
|||
mat-raised-button |
|||
color="primary"> |
|||
{{ 'action.update' | translate }} |
|||
</button> |
|||
</div> |
|||
</form> |
|||
|
|||
@ -0,0 +1,25 @@ |
|||
/** |
|||
* Copyright © 2016-2024 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 { |
|||
display: flex; |
|||
width: 100%; |
|||
max-width: 100%; |
|||
|
|||
.mdc-button { |
|||
max-width: 100%; |
|||
} |
|||
} |
|||
@ -0,0 +1,140 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 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, ElementRef, Inject, InjectionToken, OnInit, ViewChild } from '@angular/core'; |
|||
import { NotificationTemplateTypeTranslateMap, NotificationType } from '@shared/models/notification.models'; |
|||
import { MatChipInputEvent } from '@angular/material/chips'; |
|||
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes'; |
|||
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; |
|||
import { Observable } from 'rxjs'; |
|||
import { FormControl } from '@angular/forms'; |
|||
import { debounceTime, map } from 'rxjs/operators'; |
|||
import { OverlayRef } from '@angular/cdk/overlay'; |
|||
|
|||
export const NOTIFICATION_TYPE_FILTER_PANEL_DATA = new InjectionToken<any>('NotificationTypeFilterPanelData'); |
|||
|
|||
export interface NotificationTypeFilterPanelData { |
|||
notificationTypes: Array<NotificationType>; |
|||
notificationTypesUpdated: (notificationTypes: Array<NotificationType>) => void; |
|||
} |
|||
|
|||
@Component({ |
|||
selector: 'tb-notification-type-filter-panel', |
|||
templateUrl: './notification-type-filter-panel.component.html', |
|||
styleUrls: ['notification-type-filter-panel.component.scss'] |
|||
}) |
|||
export class NotificationTypeFilterPanelComponent implements OnInit{ |
|||
|
|||
@ViewChild('searchInput') searchInputField: ElementRef; |
|||
|
|||
searchText = ''; |
|||
searchControlName = new FormControl(''); |
|||
|
|||
filteredNotificationTypesList: Observable<Array<NotificationType>>; |
|||
selectedNotificationTypes: Array<NotificationType> = []; |
|||
notificationTypesTranslateMap = NotificationTemplateTypeTranslateMap; |
|||
|
|||
separatorKeysCodes: number[] = [ENTER, COMMA, SEMICOLON]; |
|||
|
|||
private notificationType = NotificationType; |
|||
private notificationTypes = Object.keys(NotificationType) as Array<NotificationType>; |
|||
|
|||
private dirty = false; |
|||
|
|||
@ViewChild('notificationTypeInput') notificationTypeInput: ElementRef<HTMLInputElement>; |
|||
|
|||
constructor(@Inject(NOTIFICATION_TYPE_FILTER_PANEL_DATA) public data: NotificationTypeFilterPanelData, |
|||
private overlayRef: OverlayRef) { |
|||
this.selectedNotificationTypes = this.data.notificationTypes; |
|||
this.dirty = true; |
|||
} |
|||
|
|||
ngOnInit() { |
|||
this.filteredNotificationTypesList = this.searchControlName.valueChanges.pipe( |
|||
debounceTime(150), |
|||
map(value => { |
|||
this.searchText = value; |
|||
return this.notificationTypes.filter(type => !this.selectedNotificationTypes.includes(type)) |
|||
.filter(type => value ? type.toUpperCase().startsWith(value.toUpperCase()) : true); |
|||
}) |
|||
); |
|||
} |
|||
|
|||
public update() { |
|||
this.data.notificationTypesUpdated(this.selectedNotificationTypes); |
|||
if (this.overlayRef) { |
|||
this.overlayRef.dispose(); |
|||
} |
|||
} |
|||
|
|||
cancel() { |
|||
if (this.overlayRef) { |
|||
this.overlayRef.dispose(); |
|||
} |
|||
} |
|||
|
|||
public reset() { |
|||
this.selectedNotificationTypes.length = 0; |
|||
this.searchControlName.updateValueAndValidity({emitEvent: true}); |
|||
} |
|||
|
|||
remove(type: NotificationType) { |
|||
const index = this.selectedNotificationTypes.indexOf(type); |
|||
if (index >= 0) { |
|||
this.selectedNotificationTypes.splice(index, 1); |
|||
this.searchControlName.updateValueAndValidity({emitEvent: true}); |
|||
} |
|||
} |
|||
|
|||
onFocus() { |
|||
if (this.dirty) { |
|||
this.searchControlName.updateValueAndValidity({emitEvent: true}); |
|||
this.dirty = false; |
|||
} |
|||
} |
|||
|
|||
private add(type: NotificationType): void { |
|||
this.selectedNotificationTypes.push(type); |
|||
} |
|||
|
|||
chipAdd(event: MatChipInputEvent): void { |
|||
const value = (event.value || '').trim(); |
|||
if (value && this.notificationType[value]) { |
|||
this.add(this.notificationType[value]); |
|||
this.clear(''); |
|||
} |
|||
} |
|||
|
|||
selected(event: MatAutocompleteSelectedEvent): void { |
|||
if (this.notificationType[event.option.value]) { |
|||
this.add(this.notificationType[event.option.value]); |
|||
} |
|||
this.clear(''); |
|||
} |
|||
|
|||
clear(value: string = '') { |
|||
this.notificationTypeInput.nativeElement.value = value; |
|||
this.searchControlName.patchValue(value, {emitEvent: true}); |
|||
setTimeout(() => { |
|||
this.notificationTypeInput.nativeElement.blur(); |
|||
this.notificationTypeInput.nativeElement.focus(); |
|||
}, 0); |
|||
} |
|||
|
|||
displayTypeFn(type?: string): string | undefined { |
|||
return type ? type : undefined; |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2024 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 class="tb-unread-notification-panel" [style.padding]="padding" [style]="backgroundStyle$ | async"> |
|||
<div class="tb-unread-notification-overlay" [style]="overlayStyle"></div> |
|||
<ng-template #counter> |
|||
<div *ngIf="showCounter" class="notification-counter" [style.background-color]="counterBackground"> |
|||
<span class="notification-counter-value" [style]="counterValueStyle">{{ count$ | async }}</span> |
|||
</div> |
|||
</ng-template> |
|||
<ng-container *ngTemplateOutlet="widgetTitlePanel; context:{ titleSuffixTemplate: counter }"></ng-container> |
|||
|
|||
<div class="tb-unread-notification-content"> |
|||
<ng-container *ngIf="loadNotification; else loadingNotification"> |
|||
<div *ngIf="notifications.length; else emptyNotification" style="overflow: auto; width: 100%;"> |
|||
<section style="min-height: 100px; overflow: auto; padding: 6px 0;"> |
|||
<div *ngFor="let notification of notifications; let last = last; trackBy: trackById"> |
|||
<tb-notification [notification]="notification" |
|||
(markAsRead)="markAsRead($event)"> |
|||
</tb-notification> |
|||
</div> |
|||
</section> |
|||
</div> |
|||
</ng-container> |
|||
<ng-template #emptyNotification> |
|||
<div class="tb-no-notification-svg-color" style="height: 85%;"> |
|||
<svg height="100%" preserveAspectRatio="xMidYMid meet" viewBox="0 0 149 156" width="100%"> |
|||
<use [attr.xlink:href]="'assets/notification-bell.svg#CHECK_ICON'"></use> |
|||
</svg> |
|||
</div> |
|||
<span class="tb-no-notification-text" translate>notification.no-notifications-yet</span> |
|||
</ng-template> |
|||
<ng-template #loadingNotification> |
|||
<div class="tb-no-data-available" style="margin: 20px; gap: 16px;"> |
|||
<mat-spinner color="accent" diameter="65" strokeWidth="4"></mat-spinner> |
|||
<div class="tb-no-data-text" translate>notification.loading-notifications</div> |
|||
</div> |
|||
</ng-template> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,65 @@ |
|||
/** |
|||
* Copyright © 2016-2024 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 "../../../../../../../scss/constants"; |
|||
|
|||
.tb-no-notification-svg-color { |
|||
color: $tb-primary-color; |
|||
} |
|||
|
|||
.tb-unread-notification-panel { |
|||
width: 100%; |
|||
height: 100%; |
|||
position: relative; |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 8px; |
|||
padding: 20px 24px 24px 24px; |
|||
> div:not(.tb-unread-notification-overlay) { |
|||
z-index: 1; |
|||
} |
|||
div.tb-widget-title { |
|||
padding: 0; |
|||
} |
|||
.tb-unread-notification-overlay { |
|||
position: absolute; |
|||
top: 12px; |
|||
left: 12px; |
|||
bottom: 12px; |
|||
right: 12px; |
|||
} |
|||
.tb-unread-notification-content { |
|||
height: 100%; |
|||
min-height: 0; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
.tb-no-notification-text { |
|||
text-align: center; |
|||
margin-bottom: 12px; |
|||
color: rgba(0, 0, 0, 0.38); |
|||
} |
|||
} |
|||
.notification-counter { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
width: 24px; |
|||
height: 22px; |
|||
background-color: green; |
|||
border-radius: 7px; |
|||
} |
|||
} |
|||
@ -0,0 +1,284 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 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 { |
|||
ChangeDetectorRef, |
|||
Component, |
|||
Injector, |
|||
Input, |
|||
NgZone, |
|||
OnDestroy, |
|||
OnInit, |
|||
StaticProvider, |
|||
TemplateRef, |
|||
ViewContainerRef, |
|||
ViewEncapsulation |
|||
} from '@angular/core'; |
|||
import { WidgetAction, WidgetContext } from '@home/models/widget-component.models'; |
|||
import { isDefined } from '@core/utils'; |
|||
import { backgroundStyle, ComponentStyle, overlayStyle, textStyle } from '@shared/models/widget-settings.models'; |
|||
import { ResizeObserver } from '@juggle/resize-observer'; |
|||
import { BehaviorSubject, fromEvent, Observable, ReplaySubject, Subscription } from 'rxjs'; |
|||
import { ImagePipe } from '@shared/pipe/image.pipe'; |
|||
import { DomSanitizer } from '@angular/platform-browser'; |
|||
import { |
|||
unreadNotificationDefaultSettings, |
|||
UnreadNotificationWidgetSettings |
|||
} from '@home/components/widget/lib/cards/unread-notification-widget.models'; |
|||
import { Notification, NotificationRequest, NotificationType } from '@shared/models/notification.models'; |
|||
import { NotificationSubscriber } from '@shared/models/telemetry/telemetry.models'; |
|||
import { NotificationWebsocketService } from '@core/ws/notification-websocket.service'; |
|||
import { distinctUntilChanged, map, share, skip, take, tap } from 'rxjs/operators'; |
|||
import { Router } from '@angular/router'; |
|||
import { Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; |
|||
import { DEFAULT_OVERLAY_POSITIONS } from '@shared/models/overlay.models'; |
|||
import { ComponentPortal } from '@angular/cdk/portal'; |
|||
import { |
|||
NOTIFICATION_TYPE_FILTER_PANEL_DATA, |
|||
NotificationTypeFilterPanelComponent |
|||
} from '@home/components/widget/lib/cards/notification-type-filter-panel.component'; |
|||
import { selectUserDetails } from '@core/auth/auth.selectors'; |
|||
import { select, Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-unread-notification-widget', |
|||
templateUrl: './unread-notification-widget.component.html', |
|||
styleUrls: ['unread-notification-widget.component.scss'], |
|||
encapsulation: ViewEncapsulation.None |
|||
}) |
|||
export class UnreadNotificationWidgetComponent implements OnInit, OnDestroy { |
|||
|
|||
settings: UnreadNotificationWidgetSettings; |
|||
|
|||
@Input() |
|||
ctx: WidgetContext; |
|||
|
|||
@Input() |
|||
widgetTitlePanel: TemplateRef<any>; |
|||
|
|||
showCounter = true; |
|||
counterValueStyle: ComponentStyle; |
|||
counterBackground: string; |
|||
|
|||
notifications: Notification[]; |
|||
loadNotification = false; |
|||
|
|||
backgroundStyle$: Observable<ComponentStyle>; |
|||
overlayStyle: ComponentStyle = {}; |
|||
padding: string; |
|||
|
|||
private counterValue: BehaviorSubject<number> = new BehaviorSubject(0); |
|||
|
|||
count$ = this.counterValue.asObservable().pipe( |
|||
distinctUntilChanged(), |
|||
map((value) => value >= 100 ? '99+' : value), |
|||
tap(() => Promise.resolve().then(() => this.cd.markForCheck())), |
|||
share({ |
|||
connector: () => new ReplaySubject(1) |
|||
}) |
|||
); |
|||
|
|||
|
|||
private notificationTypes: Array<NotificationType> = []; |
|||
|
|||
private notificationSubscriber: NotificationSubscriber; |
|||
private notificationCountSubscriber: Subscription; |
|||
private notification: Subscription; |
|||
|
|||
private contentResize$: ResizeObserver; |
|||
|
|||
private defaultDashboardFullscreen = false; |
|||
|
|||
private viewAllAction: WidgetAction = { |
|||
name: 'widgets.notification.button-view-all', |
|||
show: !this.defaultDashboardFullscreen, |
|||
icon: 'open_in_new', |
|||
onAction: ($event) => { |
|||
this.viewAll($event); |
|||
} |
|||
}; |
|||
|
|||
private filterAction: WidgetAction = { |
|||
name: 'widgets.notification.button-filter', |
|||
show: true, |
|||
icon: 'filter_list', |
|||
onAction: ($event) => { |
|||
this.editNotificationTypeFilter($event); |
|||
} |
|||
}; |
|||
|
|||
private markAsReadAction: WidgetAction = { |
|||
name: 'widgets.notification.button-mark-read', |
|||
show: true, |
|||
icon: 'done_all', |
|||
onAction: ($event) => { |
|||
this.markAsAllRead($event); |
|||
} |
|||
}; |
|||
|
|||
constructor(private store: Store<AppState>, |
|||
private imagePipe: ImagePipe, |
|||
private notificationWsService: NotificationWebsocketService, |
|||
private sanitizer: DomSanitizer, |
|||
private router: Router, |
|||
private zone: NgZone, |
|||
private overlay: Overlay, |
|||
private viewContainerRef: ViewContainerRef, |
|||
private cd: ChangeDetectorRef) { |
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
this.ctx.$scope.unreadNotificationWidget = this; |
|||
this.settings = {...unreadNotificationDefaultSettings, ...this.ctx.settings}; |
|||
|
|||
this.showCounter = this.settings.showCounter; |
|||
this.counterValueStyle = textStyle(this.settings.counterValueFont); |
|||
this.counterValueStyle.color = this.settings.counterValueColor; |
|||
this.counterBackground = this.settings.counterColor; |
|||
|
|||
this.ctx.widgetActions = [this.viewAllAction, this.filterAction, this.markAsReadAction]; |
|||
|
|||
this.viewAllAction.show = isDefined(this.settings.enableViewAll) ? this.settings.enableViewAll : true; |
|||
this.store.pipe(select(selectUserDetails), take(1)).subscribe( |
|||
user => this.viewAllAction.show = !user.additionalInfo?.defaultDashboardFullscreen |
|||
); |
|||
this.filterAction.show = isDefined(this.settings.enableFilter) ? this.settings.enableFilter : true; |
|||
this.markAsReadAction.show = isDefined(this.settings.enableMarkAsRead) ? this.settings.enableMarkAsRead : true; |
|||
|
|||
this.initSubscription(); |
|||
|
|||
this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); |
|||
this.overlayStyle = overlayStyle(this.settings.background.overlay); |
|||
this.padding = this.settings.background.overlay.enabled ? undefined : this.settings.padding; |
|||
} |
|||
|
|||
|
|||
ngOnDestroy() { |
|||
if (this.contentResize$) { |
|||
this.contentResize$.disconnect(); |
|||
} |
|||
this.unsubscribeSubscription(); |
|||
} |
|||
|
|||
private initSubscription() { |
|||
this.notificationSubscriber = NotificationSubscriber.createNotificationsSubscription( |
|||
this.notificationWsService, this.zone, this.settings.maxNotificationDisplay, this.notificationTypes); |
|||
this.notification = this.notificationSubscriber.notifications$.subscribe(value => { |
|||
if (Array.isArray(value)) { |
|||
this.loadNotification = true; |
|||
this.notifications = value; |
|||
this.cd.markForCheck(); |
|||
} |
|||
}); |
|||
this.notificationCountSubscriber = this.notificationSubscriber.notificationCount$.pipe( |
|||
skip(1), |
|||
).subscribe(value => this.counterValue.next(value)); |
|||
this.notificationSubscriber.subscribe(); |
|||
} |
|||
|
|||
private unsubscribeSubscription() { |
|||
this.notificationSubscriber.unsubscribe(); |
|||
this.notificationCountSubscriber.unsubscribe(); |
|||
this.notification.unsubscribe(); |
|||
} |
|||
|
|||
public onInit() { |
|||
const borderRadius = this.ctx.$widgetElement.css('borderRadius'); |
|||
this.overlayStyle = {...this.overlayStyle, ...{borderRadius}}; |
|||
this.cd.detectChanges(); |
|||
} |
|||
|
|||
markAsRead(id: string) { |
|||
const cmd = NotificationSubscriber.createMarkAsReadCommand(this.notificationWsService, [id]); |
|||
cmd.subscribe(); |
|||
} |
|||
|
|||
markAsAllRead($event: Event) { |
|||
if ($event) { |
|||
$event.stopPropagation(); |
|||
} |
|||
const cmd = NotificationSubscriber.createMarkAllAsReadCommand(this.notificationWsService); |
|||
cmd.subscribe(); |
|||
} |
|||
|
|||
viewAll($event: Event) { |
|||
if ($event) { |
|||
$event.stopPropagation(); |
|||
} |
|||
this.router.navigateByUrl(this.router.parseUrl('/notification/inbox')).then(() => {}); |
|||
} |
|||
|
|||
trackById(index: number, item: NotificationRequest): string { |
|||
return item.id.id; |
|||
} |
|||
|
|||
private editNotificationTypeFilter($event: Event) { |
|||
if ($event) { |
|||
$event.stopPropagation(); |
|||
} |
|||
const target = $event.target || $event.srcElement || $event.currentTarget; |
|||
const config = new OverlayConfig({ |
|||
panelClass: 'tb-panel-container', |
|||
backdropClass: 'cdk-overlay-transparent-backdrop', |
|||
hasBackdrop: true, |
|||
height: 'fit-content', |
|||
maxHeight: '75vh', |
|||
width: '100%', |
|||
maxWidth: 700 |
|||
}); |
|||
config.positionStrategy = this.overlay.position() |
|||
.flexibleConnectedTo(target as HTMLElement) |
|||
.withPositions(DEFAULT_OVERLAY_POSITIONS); |
|||
|
|||
const overlayRef = this.overlay.create(config); |
|||
overlayRef.backdropClick().subscribe(() => { |
|||
overlayRef.dispose(); |
|||
}); |
|||
|
|||
const providers: StaticProvider[] = [ |
|||
{ |
|||
provide: NOTIFICATION_TYPE_FILTER_PANEL_DATA, |
|||
useValue: { |
|||
notificationTypes: this.notificationTypes, |
|||
notificationTypesUpdated: (notificationTypes: Array<NotificationType>) => { |
|||
this.notificationTypes = notificationTypes; |
|||
this.unsubscribeSubscription(); |
|||
this.initSubscription(); |
|||
} |
|||
} |
|||
}, |
|||
{ |
|||
provide: OverlayRef, |
|||
useValue: overlayRef |
|||
} |
|||
]; |
|||
|
|||
const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); |
|||
const componentRef = overlayRef.attach(new ComponentPortal(NotificationTypeFilterPanelComponent, |
|||
this.viewContainerRef, injector)); |
|||
|
|||
const resizeWindows$ = fromEvent(window, 'resize').subscribe(() => { |
|||
overlayRef.updatePosition(); |
|||
}); |
|||
componentRef.onDestroy(() => { |
|||
resizeWindows$.unsubscribe(); |
|||
}); |
|||
|
|||
this.ctx.detectChanges(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 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 { BackgroundSettings, BackgroundType, Font } from '@shared/models/widget-settings.models'; |
|||
|
|||
export interface UnreadNotificationWidgetSettings { |
|||
maxNotificationDisplay: number; |
|||
showCounter: boolean; |
|||
counterValueFont: Font; |
|||
counterValueColor: string; |
|||
counterColor: string; |
|||
|
|||
enableViewAll: boolean; |
|||
enableFilter: boolean; |
|||
enableMarkAsRead: boolean; |
|||
background: BackgroundSettings; |
|||
padding: string; |
|||
} |
|||
|
|||
export const unreadNotificationDefaultSettings: UnreadNotificationWidgetSettings = { |
|||
maxNotificationDisplay: 6, |
|||
showCounter: true, |
|||
counterValueFont: { |
|||
family: 'Roboto', |
|||
size: 14, |
|||
sizeUnit: 'px', |
|||
style: 'normal', |
|||
weight: '600', |
|||
lineHeight: '' |
|||
}, |
|||
counterValueColor: '#fff', |
|||
counterColor: '#305680', |
|||
enableViewAll: true, |
|||
enableFilter: true, |
|||
enableMarkAsRead: true, |
|||
background: { |
|||
type: BackgroundType.color, |
|||
color: '#fff', |
|||
overlay: { |
|||
enabled: false, |
|||
color: 'rgba(255,255,255,0.72)', |
|||
blur: 3 |
|||
} |
|||
}, |
|||
padding: '12px' |
|||
}; |
|||
@ -0,0 +1,79 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2024 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. |
|||
|
|||
--> |
|||
<ng-container [formGroup]="unreadNotificationWidgetSettingsForm"> |
|||
<div class="tb-form-panel"> |
|||
<div class="tb-form-panel-title" translate>widget-config.appearance</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div>{{ 'widgets.notification.max-notification-display' | translate }}</div> |
|||
<mat-form-field appearance="outline" subscriptSizing="dynamic"> |
|||
<input matInput type="number" min="1" formControlName="maxNotificationDisplay" placeholder="{{ 'widget-config.set' | translate }}"> |
|||
</mat-form-field> |
|||
</div> |
|||
<div class="tb-form-panel"> |
|||
<div class="tb-form-panel-title" translate>widget-config.appearance</div> |
|||
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="enableFilter"> |
|||
{{ 'widgets.notification.type-filter' | translate }} |
|||
</mat-slide-toggle> |
|||
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="enableMarkAsRead"> |
|||
{{ 'widgets.notification.button-mark-read' | translate }} |
|||
</mat-slide-toggle> |
|||
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="enableViewAll"> |
|||
{{ 'widgets.notification.button-view-all' | translate }} |
|||
</mat-slide-toggle> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="tb-form-panel"> |
|||
<div class="tb-form-row no-padding no-border"> |
|||
<mat-slide-toggle class="mat-slide" formControlName="showCounter"> |
|||
{{ 'widgets.notification.counter' | translate }} |
|||
</mat-slide-toggle> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div>{{ 'widgets.notification.counter-value' | translate }}</div> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<tb-font-settings formControlName="counterValueFont" |
|||
clearButton |
|||
[previewText]="unreadNotificationWidgetSettingsForm.get('maxNotificationDisplay').value.toString()" |
|||
[initialPreviewStyle]="widgetConfig.config.titleStyle"> |
|||
</tb-font-settings> |
|||
<tb-color-input asBoxInput |
|||
formControlName="counterValueColor"> |
|||
</tb-color-input> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div>{{ 'widgets.notification.counter-color' | translate }}</div> |
|||
<tb-color-input asBoxInput |
|||
formControlName="counterColor"> |
|||
</tb-color-input> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="tb-form-row space-between"> |
|||
<div>{{ 'widgets.background.background' | translate }}</div> |
|||
<tb-background-settings formControlName="background"> |
|||
</tb-background-settings> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div>{{ 'widget-config.card-padding' | translate }}</div> |
|||
<mat-form-field appearance="outline" subscriptSizing="dynamic"> |
|||
<input matInput formControlName="padding" placeholder="{{ 'widget-config.set' | translate }}"> |
|||
</mat-form-field> |
|||
</div> |
|||
</ng-container> |
|||
@ -0,0 +1,81 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 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 } from '@angular/core'; |
|||
import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models'; |
|||
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { unreadNotificationDefaultSettings } from '@home/components/widget/lib/cards/unread-notification-widget.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-unread-notification-widget-settings', |
|||
templateUrl: './unread-notification-widget-settings.component.html', |
|||
styleUrls: ['./../widget-settings.scss'] |
|||
}) |
|||
export class UnreadNotificationWidgetSettingsComponent extends WidgetSettingsComponent { |
|||
|
|||
unreadNotificationWidgetSettingsForm: UntypedFormGroup; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
private fb: UntypedFormBuilder) { |
|||
super(store); |
|||
} |
|||
|
|||
protected settingsForm(): UntypedFormGroup { |
|||
return this.unreadNotificationWidgetSettingsForm; |
|||
} |
|||
|
|||
protected defaultSettings(): WidgetSettings { |
|||
return {...unreadNotificationDefaultSettings}; |
|||
} |
|||
|
|||
protected onSettingsSet(settings: WidgetSettings) { |
|||
this.unreadNotificationWidgetSettingsForm = this.fb.group({ |
|||
maxNotificationDisplay: [settings?.maxNotificationDisplay, [Validators.required, Validators.min(1)]], |
|||
showCounter: [settings?.showCounter, []], |
|||
counterValueFont: [settings?.counterValueFont, []], |
|||
counterValueColor: [settings?.counterValueColor, []], |
|||
counterColor: [settings?.counterColor, []], |
|||
|
|||
enableViewAll: [settings?.enableViewAll, []], |
|||
enableFilter: [settings?.enableFilter, []], |
|||
enableMarkAsRead: [settings?.enableMarkAsRead, []], |
|||
|
|||
background: [settings?.background, []], |
|||
padding: [settings.padding, []] |
|||
}); |
|||
} |
|||
|
|||
protected validatorTriggers(): string[] { |
|||
return ['showCounter']; |
|||
} |
|||
|
|||
protected updateValidators(emitEvent: boolean) { |
|||
const showCounter: boolean = this.unreadNotificationWidgetSettingsForm.get('showCounter').value; |
|||
|
|||
if (showCounter) { |
|||
this.unreadNotificationWidgetSettingsForm.get('counterValueFont').enable({emitEvent}); |
|||
this.unreadNotificationWidgetSettingsForm.get('counterValueColor').enable({emitEvent}); |
|||
this.unreadNotificationWidgetSettingsForm.get('counterColor').enable({emitEvent}); |
|||
} else { |
|||
this.unreadNotificationWidgetSettingsForm.get('counterValueFont').disable({emitEvent}); |
|||
this.unreadNotificationWidgetSettingsForm.get('counterValueColor').disable({emitEvent}); |
|||
this.unreadNotificationWidgetSettingsForm.get('counterColor').disable({emitEvent}); |
|||
} |
|||
} |
|||
|
|||
} |
|||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 3.0 KiB |
Loading…
Reference in new issue