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