Merge pull request #10611 from thingsboard/feature/status-widget
Status widget
This commit is contained in:
commit
e79751d3d2
@ -2,15 +2,15 @@
|
||||
"widgetsBundle": {
|
||||
"alias": "status_indicators",
|
||||
"title": "Status indicators",
|
||||
"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAMAAAB+IdObAAAAjVBMVEUAAADu7u7u7u7g4OD///9c35Dw8PCt78fz/ffW9+PHx8dw457r+/Hj4+Pl+u6QkJDZ+OWsrKzM9tzf+er4/vuE56xYWFi6urp0dHTV1dXS9uCZ67qCgoKF56tm4Zeenp7C89VmZmZKSkq48c7A89Wj7cFLS0s9PT08PDyF56zi+euP6bN65aWZ67lZWVkXV4nvAAAABHRSTlMA799f7FlksgAABdJJREFUeNrt3I12mjAYBuBt/YAkJCJgCVQEFLX+7v4vbwHGQEnbeQwOXF7XVjjuvDxiCN04fHv5bvaaHy/fRB7Q8gN6znfheESLCT3HFI4HtGiIhlTREA0ZJSQi5ZJ6iKSlV8i7u4qAAYTiUfwEUzVE3qIeQkKWOku+4zvixEuydZcsVA2Rt6iGxCR1VhCIx9YJiANhylVD5C2qIUu+5StYsZ9sSVxRQXagGiJvUQvhbgSMc+CMRSYnplgKVUPkLb0ffmOmGiJv0fOIhmjIVTREQ54FMquiGiJv6REy8aocFEPkLdPeILM373dQj5CZVweph8wmvojXZCoWD0ghpGl5u2qhCiEIT5Ak04lSCJ3SWTfIPyiDzKazzJKE+kghZPZG90Y3J+9AVUHoJDNkOc6mCiET+iptSdBUFWSCLGnFAt4UQnxkyIM1REM0RENuhXjShvn4ICCbEHM0QgjCksD4IDSRNeTjgzzNYNcQDflHEJNzgDRIwYwD3h+EcAAeugBOyPuBLAMHyNYMiBuznQnAUrMHSBiEwHaRaFlGZYtr3grJv4CA64AbgpOGHLYMII7cVD2kqOCixQldCCIAJ0rTGyHobHezOHQgTkggEJDIiVk/ELeGkKLFYTdCQJ5rSMxDt9wjwGLH7W+PxBUEmHh2G4Tu7W5O/gXEXDkrRt7jFUC5151eIOYqfWdkm+5AJCVuqGKMHBsIY+KLmNW3MsRUCmlaXAIQ1cOcMz2PaIiGaMgzQ6gta7DGBwFEu0Ej/J0drHk3Gzo+SCaf2ccHeZrBriEaoiGfQuizQCZY2jBHU4WQA32VQ6gyCOCZJ/1vhSlVCEFT6YlQktOJMgiaTii6Dp1gtVc+THw0k2SCVV5UQyeSzEApBJA/lYTqC88uoyEaoiHtlu/Qc34IxyNaXn6Yveb7i3A8okVHR+fT6DsM/GX0HQZuylPN7BqiIWU05P+CmBGDCJowAvK4d0DUtsghP93A5GAWq8QXA+KUz0hafC/Xln9CCMw7IEpb5JClG5kBC+LQWa4ix4mLimWcxgHZ8jhO3SAgcfwe7cjWCW6E9N9yoRbvVcBTFjrEIakTFBVxGBGxaO6cpSsSQP24BdJfixyyIksSkNAtK1Z8VVYUa7gDK0JckSV5hy27A6K4RQ5hnAABnsYRi5hYMKPq6jvOouInEyHuFqKIALkR0n+L2VlVXwsnDQ9dFYffr1v0PNJEQzREQzREQzSkjI9F6PghuVEGK4Egf51v5kU21hqjh0JeK4h1N8TPj7ZxmWRv4ZFB8CYx5EmO2WggyGoUUsucjgFC58bX2eOhQySMxLbtpEuhg4ZYSVuw2KwP9fYi7FmLC8+cDhaC7ZZig5HsINB6iZ0NFJK3LprD8FEO56TZKUgtBPlY5E4I2v/ZGRaCz0Iz+89OoUohdjX87oJQW/o2y4OsP2isEIKNKndBascJX5+jFNfLW5aH0YX7XEvQsCC+UWVzMbL3RjunuQdNst9DJRsWBFUfefzFSUoyx9cfRjwsCFgXQxfvO4buQRcVH6/z0MYIeFaO6ve6YUgptPk7GAYHaZInl9ttn2z7krJBUGe4kPbuOG3WtF6NrUV7pwwe4tvNvEg/ngmTTBUEW0WoaghNPj+Raii5Ggj9ve9VQ46SsxQ5xVYBaTa+H8iJwsehxzFAMkPk3NodyMsty8q9Ns0qG4YNgWxxyqEOtezmQDXH7RdZMHBIK/6+cy9FaDIaCNoY3czp6CDUlp+e+CODtB2JnbQW/HFBvKt/g8B5LdvcD0G5JZI9BLKu58XOTGjdD5kbZbxHQFCx1QvaHf42vRfSbONjBvvmnHcnmfOGwsggH2a8ELS2MvoEkPIXFNsbPaSeUPyxQ7J6+hg75GxUWYwdkhtVjmOHHIwq2dghYFVDZPRHLYDDcXH0nmAe6WFmt1qfVvTabC9Kyuf2xYuGDAFvLYKhE7ougtovGv9FNc9zmZOGaIiG3Bl9h4EB3mHgF3NeY+W3xB1xAAAAAElFTkSuQmCC",
|
||||
"image": "tb-image:c3RhdHVzX2luZGljYXRvcnNfc3lzdGVtX2J1bmRsZV9pbWFnZS5wbmc=:IlN0YXR1cyBpbmRpY2F0b3JzIiBzeXN0ZW0gYnVuZGxlIGltYWdl;data:image/png;base64,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",
|
||||
"description": "Contains widgets displaying battery level and signal strength.",
|
||||
"order": 9000,
|
||||
"externalId": null,
|
||||
"name": "Status indicators"
|
||||
},
|
||||
"widgetTypeFqns": [
|
||||
"battery_level",
|
||||
"signal_strength",
|
||||
"progress_bar"
|
||||
"progress_bar",
|
||||
"status_widget"
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@ -114,6 +114,9 @@ import { ComparisonKeyRowComponent } from '@home/components/widget/config/basic/
|
||||
import {
|
||||
ComparisonKeysTableComponent
|
||||
} from '@home/components/widget/config/basic/chart/comparison-keys-table.component';
|
||||
import {
|
||||
StatusWidgetBasicConfigComponent
|
||||
} from '@home/components/widget/config/basic/indicator/status-widget-basic-config.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -151,7 +154,8 @@ import {
|
||||
ToggleButtonBasicConfigComponent,
|
||||
TimeSeriesChartBasicConfigComponent,
|
||||
ComparisonKeyRowComponent,
|
||||
ComparisonKeysTableComponent
|
||||
ComparisonKeysTableComponent,
|
||||
StatusWidgetBasicConfigComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@ -191,7 +195,8 @@ import {
|
||||
PowerButtonBasicConfigComponent,
|
||||
SliderBasicConfigComponent,
|
||||
ToggleButtonBasicConfigComponent,
|
||||
TimeSeriesChartBasicConfigComponent
|
||||
TimeSeriesChartBasicConfigComponent,
|
||||
StatusWidgetBasicConfigComponent
|
||||
]
|
||||
})
|
||||
export class BasicWidgetConfigModule {
|
||||
@ -225,5 +230,6 @@ export const basicWidgetConfigComponentsMap: {[key: string]: Type<IBasicWidgetCo
|
||||
'tb-power-button-basic-config': PowerButtonBasicConfigComponent,
|
||||
'tb-slider-basic-config': SliderBasicConfigComponent,
|
||||
'tb-toggle-button-basic-config': ToggleButtonBasicConfigComponent,
|
||||
'tb-time-series-chart-basic-config': TimeSeriesChartBasicConfigComponent
|
||||
'tb-time-series-chart-basic-config': TimeSeriesChartBasicConfigComponent,
|
||||
'tb-status-widget-basic-config': StatusWidgetBasicConfigComponent
|
||||
};
|
||||
|
||||
@ -0,0 +1,101 @@
|
||||
<!--
|
||||
|
||||
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]="statusWidgetConfigForm">
|
||||
<tb-target-device formControlName="targetDevice"></tb-target-device>
|
||||
<div class="tb-form-panel">
|
||||
<div class="tb-form-panel-title" translate>widgets.status-widget.behavior</div>
|
||||
<div class="tb-form-row">
|
||||
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.rpc-state.initial-state-hint' | translate}}" translate>widgets.rpc-state.initial-state</div>
|
||||
<tb-get-value-action-settings fxFlex
|
||||
panelTitle="widgets.rpc-state.initial-state"
|
||||
[valueType]="valueType.BOOLEAN"
|
||||
trueLabel="widgets.rpc-state.on"
|
||||
falseLabel="widgets.rpc-state.off"
|
||||
stateLabel="widgets.rpc-state.on"
|
||||
[aliasController]="aliasController"
|
||||
[targetDevice]="targetDevice"
|
||||
[widgetType]="widgetType"
|
||||
formControlName="initialState"></tb-get-value-action-settings>
|
||||
</div>
|
||||
<div class="tb-form-row">
|
||||
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.rpc-state.disabled-state-hint' | translate}}" translate>widgets.rpc-state.disabled-state</div>
|
||||
<tb-get-value-action-settings fxFlex
|
||||
panelTitle="widgets.rpc-state.disabled-state"
|
||||
[valueType]="valueType.BOOLEAN"
|
||||
stateLabel="widgets.rpc-state.disabled"
|
||||
[aliasController]="aliasController"
|
||||
[targetDevice]="targetDevice"
|
||||
[widgetType]="widgetType"
|
||||
formControlName="disabledState"></tb-get-value-action-settings>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tb-form-panel">
|
||||
<div class="tb-form-panel-title" translate>widget-config.appearance</div>
|
||||
<tb-image-cards-select rowHeight="1:1"
|
||||
[cols]="{columns: 3,
|
||||
breakpoints: {
|
||||
'lt-sm': 1,
|
||||
'lt-md': 2
|
||||
}}"
|
||||
label="{{ 'widgets.status-widget.layout' | translate }}" formControlName="layout">
|
||||
<tb-image-cards-select-option *ngFor="let layout of statusWidgetLayouts"
|
||||
[value]="layout"
|
||||
[image]="statusWidgetLayoutImageMap.get(layout)">
|
||||
{{ statusWidgetLayoutTranslationMap.get(layout) | translate }}
|
||||
</tb-image-cards-select-option>
|
||||
</tb-image-cards-select>
|
||||
</div>
|
||||
<div class="tb-form-panel">
|
||||
<div fxLayout="row" fxLayoutAlign="space-between center">
|
||||
<div class="tb-form-panel-title" translate>widget-config.card-style</div>
|
||||
<tb-toggle-select [(ngModel)]="cardStyleMode"
|
||||
[ngModelOptions]="{ standalone: true }">
|
||||
<tb-toggle-option value="on">{{ 'widgets.status-widget.on' | translate }}</tb-toggle-option>
|
||||
<tb-toggle-option value="off">{{ 'widgets.status-widget.off' | translate }}</tb-toggle-option>
|
||||
</tb-toggle-select>
|
||||
</div>
|
||||
<tb-status-widget-state-settings
|
||||
*ngIf="cardStyleMode === 'on'"
|
||||
[layout]="statusWidgetConfigForm.get('layout').value"
|
||||
formControlName="onState">
|
||||
</tb-status-widget-state-settings>
|
||||
<tb-status-widget-state-settings
|
||||
*ngIf="cardStyleMode === 'off'"
|
||||
[layout]="statusWidgetConfigForm.get('layout').value"
|
||||
formControlName="offState">
|
||||
</tb-status-widget-state-settings>
|
||||
</div>
|
||||
<div class="tb-form-panel">
|
||||
<div class="tb-form-panel-title" translate>widget-config.card-appearance</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="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>
|
||||
<tb-widget-actions-panel
|
||||
formControlName="actions">
|
||||
</tb-widget-actions-panel>
|
||||
</ng-container>
|
||||
@ -0,0 +1,119 @@
|
||||
///
|
||||
/// 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 } 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 { TargetDevice, WidgetConfig, } from '@shared/models/widget.models';
|
||||
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
|
||||
import { isUndefined } from '@core/utils';
|
||||
import { ValueType } from '@shared/models/constants';
|
||||
import {
|
||||
statusWidgetDefaultSettings,
|
||||
statusWidgetLayoutImages,
|
||||
statusWidgetLayouts,
|
||||
statusWidgetLayoutTranslations,
|
||||
StatusWidgetSettings
|
||||
} from '@home/components/widget/lib/indicator/status-widget.models';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-status-widget-basic-config',
|
||||
templateUrl: './status-widget-basic-config.component.html',
|
||||
styleUrls: ['../basic-config.scss']
|
||||
})
|
||||
export class StatusWidgetBasicConfigComponent extends BasicWidgetConfigComponent {
|
||||
|
||||
get targetDevice(): TargetDevice {
|
||||
return this.statusWidgetConfigForm.get('targetDevice').value;
|
||||
}
|
||||
|
||||
statusWidgetLayouts = statusWidgetLayouts;
|
||||
|
||||
statusWidgetLayoutTranslationMap = statusWidgetLayoutTranslations;
|
||||
statusWidgetLayoutImageMap = statusWidgetLayoutImages;
|
||||
|
||||
valueType = ValueType;
|
||||
|
||||
statusWidgetConfigForm: UntypedFormGroup;
|
||||
|
||||
cardStyleMode = 'on';
|
||||
|
||||
constructor(protected store: Store<AppState>,
|
||||
protected widgetConfigComponent: WidgetConfigComponent,
|
||||
private fb: UntypedFormBuilder) {
|
||||
super(store, widgetConfigComponent);
|
||||
}
|
||||
|
||||
protected configForm(): UntypedFormGroup {
|
||||
return this.statusWidgetConfigForm;
|
||||
}
|
||||
|
||||
protected onConfigSet(configData: WidgetConfigComponentData) {
|
||||
const settings: StatusWidgetSettings = {...statusWidgetDefaultSettings, ...(configData.config.settings || {})};
|
||||
this.statusWidgetConfigForm = this.fb.group({
|
||||
targetDevice: [configData.config.targetDevice, []],
|
||||
|
||||
initialState: [settings.initialState, []],
|
||||
disabledState: [settings.disabledState, []],
|
||||
|
||||
layout: [settings.layout, []],
|
||||
|
||||
onState: [settings.onState, []],
|
||||
offState: [settings.offState, []],
|
||||
|
||||
cardButtons: [this.getCardButtons(configData.config), []],
|
||||
borderRadius: [configData.config.borderRadius, []],
|
||||
|
||||
actions: [configData.config.actions || {}, []]
|
||||
});
|
||||
}
|
||||
|
||||
protected prepareOutputConfig(config: any): WidgetConfigComponentData {
|
||||
this.widgetConfig.config.targetDevice = config.targetDevice;
|
||||
|
||||
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
|
||||
|
||||
this.widgetConfig.config.settings.initialState = config.initialState;
|
||||
this.widgetConfig.config.settings.disabledState = config.disabledState;
|
||||
|
||||
this.widgetConfig.config.settings.layout = config.layout;
|
||||
|
||||
this.widgetConfig.config.settings.onState = config.onState;
|
||||
this.widgetConfig.config.settings.offState = config.offState;
|
||||
|
||||
this.setCardButtons(config.cardButtons, this.widgetConfig.config);
|
||||
this.widgetConfig.config.borderRadius = config.borderRadius;
|
||||
|
||||
this.widgetConfig.config.actions = config.actions;
|
||||
return this.widgetConfig;
|
||||
}
|
||||
|
||||
private getCardButtons(config: WidgetConfig): string[] {
|
||||
const buttons: string[] = [];
|
||||
if (isUndefined(config.enableFullscreen) || config.enableFullscreen) {
|
||||
buttons.push('fullscreen');
|
||||
}
|
||||
return buttons;
|
||||
}
|
||||
|
||||
private setCardButtons(buttons: string[], config: WidgetConfig) {
|
||||
config.enableFullscreen = buttons.includes('fullscreen');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
<!--
|
||||
|
||||
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 #statusWidgetPanel class="tb-status-widget-panel" [style]="backgroundStyle$ | async">
|
||||
<div class="tb-status-widget-overlay" [style]="overlayStyle" [style.inset]="overlayInset"></div>
|
||||
<div class="tb-status-widget-title-panel">
|
||||
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
|
||||
</div>
|
||||
<div #statusWidgetContent class="tb-status-widget-content" [class]="this.layout">
|
||||
<div class="tb-status-widget-icon-container">
|
||||
<tb-icon [style]="iconStyle">{{ icon }}</tb-icon>
|
||||
</div>
|
||||
<div class="tb-status-widget-labels-container">
|
||||
<div *ngIf="showLabel" class="tb-status-widget-label" [style]="labelStyle">{{ label$ | async }}</div>
|
||||
<div *ngIf="showStatus" class="tb-status-widget-status" [style]="statusStyle">{{ status$ | async }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<mat-progress-bar class="tb-action-widget-progress" style="height: 4px;" color="accent" mode="indeterminate" *ngIf="loading$ | async"></mat-progress-bar>
|
||||
</div>
|
||||
@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
.tb-status-widget-panel {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
> div:not(.tb-status-widget-overlay), > tb-icon {
|
||||
z-index: 1;
|
||||
}
|
||||
.tb-status-widget-overlay {
|
||||
position: absolute;
|
||||
inset: 12px;
|
||||
}
|
||||
> div.tb-status-widget-title-panel {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
right: 12px;
|
||||
z-index: 2;
|
||||
}
|
||||
.tb-status-widget-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 16px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.tb-status-widget-icon-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
.tb-status-widget-labels-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
.tb-status-widget-label {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.tb-status-widget-status {
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
&.default {
|
||||
place-content: flex-start space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
&.center {
|
||||
place-content: center flex-start;
|
||||
align-items: center;
|
||||
.tb-status-widget-icon-container {
|
||||
flex: 1;
|
||||
place-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.tb-status-widget-labels-container {
|
||||
flex-direction: column-reverse;
|
||||
place-content: center flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
&.icon {
|
||||
place-content: center;
|
||||
align-items: center;
|
||||
.tb-status-widget-icon-container {
|
||||
flex: 1;
|
||||
place-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.tb-status-widget-labels-container {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,242 @@
|
||||
///
|
||||
/// 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 {
|
||||
AfterViewInit,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Renderer2, ViewChild,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import { BasicActionWidgetComponent } from '@home/components/widget/lib/action/action-widget.models';
|
||||
import {
|
||||
statusWidgetDefaultSettings,
|
||||
StatusWidgetLayout,
|
||||
StatusWidgetSettings, StatusWidgetStateSettings
|
||||
} from '@home/components/widget/lib/indicator/status-widget.models';
|
||||
import { Observable } from 'rxjs';
|
||||
import {
|
||||
backgroundStyle,
|
||||
ComponentStyle,
|
||||
iconStyle,
|
||||
overlayStyle,
|
||||
textStyle
|
||||
} from '@shared/models/widget-settings.models';
|
||||
import { ResizeObserver } from '@juggle/resize-observer';
|
||||
import { ImagePipe } from '@shared/pipe/image.pipe';
|
||||
import { DomSanitizer } from '@angular/platform-browser';
|
||||
import { UtilsService } from '@core/services/utils.service';
|
||||
import { ValueType } from '@shared/models/constants';
|
||||
|
||||
const initialStatusWidgetSize = 147;
|
||||
|
||||
@Component({
|
||||
selector: 'tb-status-widget',
|
||||
templateUrl: './status-widget.component.html',
|
||||
styleUrls: ['../action/action-widget.scss', './status-widget.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class StatusWidgetComponent extends
|
||||
BasicActionWidgetComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
@ViewChild('statusWidgetPanel', {static: false})
|
||||
statusWidgetPanel: ElementRef<HTMLElement>;
|
||||
|
||||
@ViewChild('statusWidgetContent', {static: false})
|
||||
statusWidgetContent: ElementRef<HTMLElement>;
|
||||
|
||||
settings: StatusWidgetSettings;
|
||||
|
||||
backgroundStyle$: Observable<ComponentStyle>;
|
||||
overlayStyle: ComponentStyle = {};
|
||||
|
||||
overlayInset = '12px';
|
||||
borderRadius = '';
|
||||
|
||||
layout: StatusWidgetLayout;
|
||||
|
||||
showLabel = true;
|
||||
label$: Observable<string>;
|
||||
labelStyle: ComponentStyle = {};
|
||||
|
||||
showStatus = true;
|
||||
status$: Observable<string>;
|
||||
statusStyle: ComponentStyle = {};
|
||||
|
||||
icon = '';
|
||||
iconStyle: ComponentStyle = {};
|
||||
|
||||
private panelResize$: ResizeObserver;
|
||||
|
||||
private onLabel$: Observable<string>;
|
||||
private onStatus$: Observable<string>;
|
||||
private onBackground$: Observable<ComponentStyle>;
|
||||
private onBackgroundDisabled$: Observable<ComponentStyle>;
|
||||
|
||||
private offLabel$: Observable<string>;
|
||||
private offStatus$: Observable<string>;
|
||||
private offBackground$: Observable<ComponentStyle>;
|
||||
private offBackgroundDisabled$: Observable<ComponentStyle>;
|
||||
|
||||
private state = false;
|
||||
private disabled = false;
|
||||
private disabledState = false;
|
||||
|
||||
constructor(protected imagePipe: ImagePipe,
|
||||
protected sanitizer: DomSanitizer,
|
||||
private renderer: Renderer2,
|
||||
private utils: UtilsService,
|
||||
protected cd: ChangeDetectorRef,
|
||||
private elementRef: ElementRef) {
|
||||
super(cd);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit();
|
||||
this.settings = {...statusWidgetDefaultSettings, ...this.ctx.settings};
|
||||
this.layout = this.settings.layout;
|
||||
|
||||
this.onLabel$ = this.ctx.registerLabelPattern(this.settings.onState.label, this.onLabel$);
|
||||
this.onStatus$ = this.ctx.registerLabelPattern(this.settings.onState.status, this.onStatus$);
|
||||
this.onBackground$ = backgroundStyle(this.settings.onState.background, this.imagePipe, this.sanitizer);
|
||||
this.onBackgroundDisabled$ = backgroundStyle(this.settings.onState.backgroundDisabled, this.imagePipe, this.sanitizer);
|
||||
|
||||
this.offLabel$ = this.ctx.registerLabelPattern(this.settings.offState.label, this.offLabel$);
|
||||
this.offStatus$ = this.ctx.registerLabelPattern(this.settings.offState.status, this.offStatus$);
|
||||
this.offBackground$ = backgroundStyle(this.settings.offState.background, this.imagePipe, this.sanitizer);
|
||||
this.offBackgroundDisabled$ = backgroundStyle(this.settings.offState.backgroundDisabled, this.imagePipe, this.sanitizer);
|
||||
|
||||
const getInitialStateSettings =
|
||||
{...this.settings.initialState, actionLabel: this.ctx.translate.instant('widgets.rpc-state.initial-state')};
|
||||
this.createValueGetter(getInitialStateSettings, ValueType.BOOLEAN, {
|
||||
next: (value) => this.onState(value)
|
||||
});
|
||||
|
||||
const disabledStateSettings =
|
||||
{...this.settings.disabledState, actionLabel: this.ctx.translate.instant('widgets.rpc-state.disabled-state')};
|
||||
this.createValueGetter(disabledStateSettings, ValueType.BOOLEAN, {
|
||||
next: (value) => this.onDisabled(value)
|
||||
});
|
||||
|
||||
this.loading$.subscribe((loading) => {
|
||||
this.updateDisabledState(loading || this.disabled);
|
||||
});
|
||||
|
||||
this.updateStyle(this.state, this.disabled);
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.renderer.setStyle(this.statusWidgetContent.nativeElement, 'overflow', 'visible');
|
||||
this.renderer.setStyle(this.statusWidgetContent.nativeElement, 'position', 'absolute');
|
||||
this.panelResize$ = new ResizeObserver(() => {
|
||||
this.onResize();
|
||||
});
|
||||
this.panelResize$.observe(this.statusWidgetPanel.nativeElement);
|
||||
if (this.showLabel) {
|
||||
this.panelResize$.observe(this.statusWidgetPanel.nativeElement);
|
||||
}
|
||||
this.onResize();
|
||||
super.ngAfterViewInit();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.panelResize$) {
|
||||
this.panelResize$.disconnect();
|
||||
}
|
||||
super.ngOnDestroy();
|
||||
}
|
||||
|
||||
public onInit() {
|
||||
super.onInit();
|
||||
this.borderRadius = this.ctx.$widgetElement.css('borderRadius');
|
||||
this.overlayStyle = {...this.overlayStyle, ...{borderRadius: this.borderRadius}};
|
||||
this.cd.detectChanges();
|
||||
}
|
||||
|
||||
private onState(value: boolean): void {
|
||||
const newState = !!value;
|
||||
if (this.state !== newState) {
|
||||
this.state = newState;
|
||||
this.updateStyle(this.state, this.disabled || this.disabledState);
|
||||
}
|
||||
}
|
||||
|
||||
private onDisabled(value: boolean): void {
|
||||
const newDisabled = !!value;
|
||||
if (this.disabled !== newDisabled) {
|
||||
this.disabled = newDisabled;
|
||||
this.updateDisabledState(this.disabled);
|
||||
}
|
||||
}
|
||||
|
||||
private updateDisabledState(disabled: boolean) {
|
||||
this.disabledState = disabled;
|
||||
this.updateStyle(this.state, this.disabledState);
|
||||
}
|
||||
|
||||
private onResize() {
|
||||
const panelWidth = this.statusWidgetPanel.nativeElement.getBoundingClientRect().width;
|
||||
const panelHeight = this.statusWidgetPanel.nativeElement.getBoundingClientRect().height;
|
||||
const targetSize = Math.min(panelWidth, panelHeight);
|
||||
const scale = targetSize / initialStatusWidgetSize;
|
||||
const width = initialStatusWidgetSize;
|
||||
const height = initialStatusWidgetSize;
|
||||
this.renderer.setStyle(this.statusWidgetContent.nativeElement, 'width', width + 'px');
|
||||
this.renderer.setStyle(this.statusWidgetContent.nativeElement, 'height', height + 'px');
|
||||
this.renderer.setStyle(this.statusWidgetContent.nativeElement, 'transform', `scale(${scale})`);
|
||||
this.overlayInset = (Math.floor(12 * scale * 100) / 100) + 'px';
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
|
||||
private updateStyle(state: boolean, disabled: boolean) {
|
||||
let stateSettings: StatusWidgetStateSettings;
|
||||
if (state) {
|
||||
this.label$ = this.onLabel$;
|
||||
this.status$ = this.onStatus$;
|
||||
this.backgroundStyle$ = disabled ? this.onBackgroundDisabled$ : this.onBackground$;
|
||||
stateSettings = this.settings.onState;
|
||||
} else {
|
||||
this.label$ = this.offLabel$;
|
||||
this.status$ = this.offStatus$;
|
||||
this.backgroundStyle$ = disabled ? this.offBackgroundDisabled$ : this.offBackground$;
|
||||
stateSettings = this.settings.offState;
|
||||
}
|
||||
this.showLabel = stateSettings.showLabel && this.layout !== StatusWidgetLayout.icon;
|
||||
this.showStatus = stateSettings.showStatus && this.layout !== StatusWidgetLayout.icon;
|
||||
this.icon = stateSettings.icon;
|
||||
|
||||
const primaryColor = disabled ? stateSettings.primaryColorDisabled : stateSettings.primaryColor;
|
||||
const secondaryColor = disabled ? stateSettings.secondaryColorDisabled : stateSettings.secondaryColor;
|
||||
|
||||
this.labelStyle = textStyle(stateSettings.labelFont);
|
||||
this.labelStyle.color = primaryColor;
|
||||
|
||||
this.statusStyle = textStyle(stateSettings.statusFont);
|
||||
this.statusStyle.color = secondaryColor;
|
||||
|
||||
this.iconStyle = iconStyle(stateSettings.iconSize, stateSettings.iconSizeUnit);
|
||||
this.iconStyle.color = primaryColor;
|
||||
|
||||
this.overlayStyle = overlayStyle(disabled ? stateSettings.backgroundDisabled.overlay : stateSettings.background.overlay);
|
||||
if (this.borderRadius) {
|
||||
this.overlayStyle = {...this.overlayStyle, ...{borderRadius: this.borderRadius}};
|
||||
}
|
||||
this.cd.detectChanges();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,204 @@
|
||||
///
|
||||
/// 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 { DataToValueType, GetValueAction, GetValueSettings } from '@shared/models/action-widget-settings.models';
|
||||
import { BackgroundSettings, BackgroundType, cssUnit, Font } from '@shared/models/widget-settings.models';
|
||||
|
||||
export enum StatusWidgetLayout {
|
||||
default = 'default',
|
||||
center = 'center',
|
||||
icon = 'icon'
|
||||
}
|
||||
|
||||
export const statusWidgetLayouts = Object.keys(StatusWidgetLayout) as StatusWidgetLayout[];
|
||||
|
||||
export const statusWidgetLayoutTranslations = new Map<StatusWidgetLayout, string>(
|
||||
[
|
||||
[StatusWidgetLayout.default, 'widgets.status-widget.layout-default'],
|
||||
[StatusWidgetLayout.center, 'widgets.status-widget.layout-center'],
|
||||
[StatusWidgetLayout.icon, 'widgets.status-widget.layout-icon']
|
||||
]
|
||||
);
|
||||
|
||||
export const statusWidgetLayoutImages = new Map<StatusWidgetLayout, string>(
|
||||
[
|
||||
[StatusWidgetLayout.default, 'assets/widget/status-widget/default-layout.svg'],
|
||||
[StatusWidgetLayout.center, 'assets/widget/status-widget/center-layout.svg'],
|
||||
[StatusWidgetLayout.icon, 'assets/widget/status-widget/icon-layout.svg']
|
||||
]
|
||||
);
|
||||
|
||||
export interface StatusWidgetStateSettings {
|
||||
showLabel: boolean;
|
||||
label: string;
|
||||
labelFont: Font;
|
||||
showStatus: boolean;
|
||||
status: string;
|
||||
statusFont: Font;
|
||||
icon: string;
|
||||
iconSize: number;
|
||||
iconSizeUnit: cssUnit;
|
||||
primaryColor: string;
|
||||
secondaryColor: string;
|
||||
background: BackgroundSettings;
|
||||
primaryColorDisabled: string;
|
||||
secondaryColorDisabled: string;
|
||||
backgroundDisabled: BackgroundSettings;
|
||||
}
|
||||
|
||||
export interface StatusWidgetSettings {
|
||||
initialState: GetValueSettings<boolean>;
|
||||
disabledState: GetValueSettings<boolean>;
|
||||
layout: StatusWidgetLayout;
|
||||
onState: StatusWidgetStateSettings;
|
||||
offState: StatusWidgetStateSettings;
|
||||
}
|
||||
|
||||
export const statusWidgetDefaultSettings: StatusWidgetSettings = {
|
||||
initialState: {
|
||||
action: GetValueAction.EXECUTE_RPC,
|
||||
defaultValue: false,
|
||||
executeRpc: {
|
||||
method: 'getState',
|
||||
requestTimeout: 5000,
|
||||
requestPersistent: false,
|
||||
persistentPollingInterval: 1000
|
||||
},
|
||||
getAttribute: {
|
||||
key: 'state',
|
||||
scope: null
|
||||
},
|
||||
getTimeSeries: {
|
||||
key: 'state'
|
||||
},
|
||||
dataToValue: {
|
||||
type: DataToValueType.NONE,
|
||||
compareToValue: true,
|
||||
dataToValueFunction: '/* Should return boolean value */\nreturn data;'
|
||||
}
|
||||
},
|
||||
disabledState: {
|
||||
action: GetValueAction.DO_NOTHING,
|
||||
defaultValue: false,
|
||||
getAttribute: {
|
||||
key: 'state',
|
||||
scope: null
|
||||
},
|
||||
getTimeSeries: {
|
||||
key: 'state'
|
||||
},
|
||||
dataToValue: {
|
||||
type: DataToValueType.NONE,
|
||||
compareToValue: true,
|
||||
dataToValueFunction: '/* Should return boolean value */\nreturn data;'
|
||||
}
|
||||
},
|
||||
layout: StatusWidgetLayout.default,
|
||||
onState: {
|
||||
showLabel: true,
|
||||
label: 'Window left corner',
|
||||
labelFont: {
|
||||
family: 'Roboto',
|
||||
size: 12,
|
||||
sizeUnit: 'px',
|
||||
style: 'normal',
|
||||
weight: '500',
|
||||
lineHeight: '16px'
|
||||
},
|
||||
showStatus: true,
|
||||
status: 'Opened',
|
||||
statusFont: {
|
||||
family: 'Roboto',
|
||||
size: 10,
|
||||
sizeUnit: 'px',
|
||||
style: 'normal',
|
||||
weight: '500',
|
||||
lineHeight: '20px'
|
||||
},
|
||||
icon: 'mdi:curtains',
|
||||
iconSize: 32,
|
||||
iconSizeUnit: 'px',
|
||||
primaryColor: '#fff',
|
||||
secondaryColor: 'rgba(255, 255, 255, 0.80)',
|
||||
background: {
|
||||
type: BackgroundType.color,
|
||||
color: '#3F52DD',
|
||||
overlay: {
|
||||
enabled: false,
|
||||
color: 'rgba(255,255,255,0.72)',
|
||||
blur: 3
|
||||
}
|
||||
},
|
||||
primaryColorDisabled: 'rgba(0, 0, 0, 0.38)',
|
||||
secondaryColorDisabled: 'rgba(0, 0, 0, 0.38)',
|
||||
backgroundDisabled: {
|
||||
type: BackgroundType.color,
|
||||
color: '#CACACA',
|
||||
overlay: {
|
||||
enabled: false,
|
||||
color: 'rgba(255,255,255,0.72)',
|
||||
blur: 3
|
||||
}
|
||||
}
|
||||
},
|
||||
offState: {
|
||||
showLabel: true,
|
||||
label: 'Window left corner',
|
||||
labelFont: {
|
||||
family: 'Roboto',
|
||||
size: 12,
|
||||
sizeUnit: 'px',
|
||||
style: 'normal',
|
||||
weight: '500',
|
||||
lineHeight: '16px'
|
||||
},
|
||||
showStatus: true,
|
||||
status: 'Closed',
|
||||
statusFont: {
|
||||
family: 'Roboto',
|
||||
size: 10,
|
||||
sizeUnit: 'px',
|
||||
style: 'normal',
|
||||
weight: '500',
|
||||
lineHeight: '20px'
|
||||
},
|
||||
icon: 'mdi:curtains-closed',
|
||||
iconSize: 32,
|
||||
iconSizeUnit: 'px',
|
||||
primaryColor: 'rgba(0, 0, 0, 0.87)',
|
||||
secondaryColor: 'rgba(0, 0, 0, 0.54)',
|
||||
background: {
|
||||
type: BackgroundType.color,
|
||||
color: '#FFF',
|
||||
overlay: {
|
||||
enabled: false,
|
||||
color: 'rgba(255,255,255,0.72)',
|
||||
blur: 3
|
||||
}
|
||||
},
|
||||
primaryColorDisabled: 'rgba(0, 0, 0, 0.38)',
|
||||
secondaryColorDisabled: 'rgba(0, 0, 0, 0.38)',
|
||||
backgroundDisabled: {
|
||||
type: BackgroundType.color,
|
||||
color: '#CACACA',
|
||||
overlay: {
|
||||
enabled: false,
|
||||
color: 'rgba(255,255,255,0.72)',
|
||||
blur: 3
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,111 @@
|
||||
<!--
|
||||
|
||||
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]="stateSettingsFormGroup">
|
||||
<div *ngIf="layout !== StatusWidgetLayout.icon" class="tb-form-row column-xs">
|
||||
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showLabel">
|
||||
{{ 'widgets.status-widget.label' | 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="label" placeholder="{{ 'widget-config.set' | translate }}">
|
||||
</mat-form-field>
|
||||
<tb-font-settings formControlName="labelFont"
|
||||
[previewText]="stateSettingsFormGroup.get('label').value">
|
||||
</tb-font-settings>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="layout !== StatusWidgetLayout.icon" class="tb-form-row column-xs">
|
||||
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showStatus">
|
||||
{{ 'widgets.status-widget.status' | 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="status" placeholder="{{ 'widget-config.set' | translate }}">
|
||||
</mat-form-field>
|
||||
<tb-font-settings formControlName="statusFont"
|
||||
[previewText]="stateSettingsFormGroup.get('status').value">
|
||||
</tb-font-settings>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tb-form-row">
|
||||
<div class="fixed-title-width">
|
||||
{{ 'widgets.status-widget.icon' | translate }}
|
||||
</div>
|
||||
<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
|
||||
formControlName="icon">
|
||||
</tb-material-icon-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tb-form-row space-between" [class]="{'column-xs': layout === StatusWidgetLayout.icon, 'column-lt-md': layout !== StatusWidgetLayout.icon}">
|
||||
<div>{{ 'widgets.status-widget.color-palette' | translate }}</div>
|
||||
<div fxLayout="row wrap" fxLayoutAlign="start center" fxLayoutAlign.lt-sm="space-between center"
|
||||
[fxLayoutAlign.lt-md]="layout !== StatusWidgetLayout.icon ? 'space-between center': 'start center'"
|
||||
style="gap: 12px;">
|
||||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
|
||||
<div tb-hint-tooltip-icon="{{'widgets.status-widget.primary-color-hint' | translate}}" translate>widgets.status-widget.primary</div>
|
||||
<tb-color-input asBoxInput
|
||||
formControlName="primaryColor">
|
||||
</tb-color-input>
|
||||
</div>
|
||||
<mat-divider *ngIf="layout !== StatusWidgetLayout.icon" vertical fxHide.lt-md></mat-divider>
|
||||
<div *ngIf="layout !== StatusWidgetLayout.icon" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
|
||||
<div tb-hint-tooltip-icon="{{'widgets.status-widget.secondary-color-hint' | translate}}" translate>widgets.status-widget.secondary</div>
|
||||
<tb-color-input asBoxInput
|
||||
formControlName="secondaryColor">
|
||||
</tb-color-input>
|
||||
</div>
|
||||
<mat-divider vertical fxHide.lt-sm [fxHide.lt-md]="layout !== StatusWidgetLayout.icon"></mat-divider>
|
||||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
|
||||
<div translate>widgets.status-widget.background</div>
|
||||
<tb-background-settings formControlName="background">
|
||||
</tb-background-settings>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tb-form-row space-between" [class]="{'column-xs': layout === StatusWidgetLayout.icon, 'column-lt-md': layout !== StatusWidgetLayout.icon}">
|
||||
<div>{{ 'widgets.status-widget.disabled-color-palette' | translate }}</div>
|
||||
<div fxLayout="row wrap" fxLayoutAlign="start center" fxLayoutAlign.lt-sm="space-between center"
|
||||
[fxLayoutAlign.lt-md]="layout !== StatusWidgetLayout.icon ? 'space-between center': 'start center'"
|
||||
style="gap: 12px;">
|
||||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
|
||||
<div tb-hint-tooltip-icon="{{'widgets.status-widget.primary-color-hint' | translate}}" translate>widgets.status-widget.primary</div>
|
||||
<tb-color-input asBoxInput
|
||||
formControlName="primaryColorDisabled">
|
||||
</tb-color-input>
|
||||
</div>
|
||||
<mat-divider *ngIf="layout !== StatusWidgetLayout.icon" vertical fxHide.lt-md></mat-divider>
|
||||
<div *ngIf="layout !== StatusWidgetLayout.icon" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
|
||||
<div tb-hint-tooltip-icon="{{'widgets.status-widget.secondary-color-hint' | translate}}" translate>widgets.status-widget.secondary</div>
|
||||
<tb-color-input asBoxInput
|
||||
formControlName="secondaryColorDisabled">
|
||||
</tb-color-input>
|
||||
</div>
|
||||
<mat-divider vertical fxHide.lt-sm [fxHide.lt-md]="layout !== StatusWidgetLayout.icon"></mat-divider>
|
||||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
|
||||
<div translate>widgets.status-widget.background</div>
|
||||
<tb-background-settings formControlName="backgroundDisabled">
|
||||
</tb-background-settings>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
@ -0,0 +1,158 @@
|
||||
///
|
||||
/// 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, forwardRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||
import { merge } from 'rxjs';
|
||||
import {
|
||||
StatusWidgetLayout,
|
||||
StatusWidgetStateSettings
|
||||
} from '@home/components/widget/lib/indicator/status-widget.models';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-status-widget-state-settings',
|
||||
templateUrl: './status-widget-state-settings.component.html',
|
||||
styleUrls: ['./../../widget-settings.scss'],
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => StatusWidgetStateSettingsComponent),
|
||||
multi: true
|
||||
}
|
||||
]
|
||||
})
|
||||
export class StatusWidgetStateSettingsComponent implements OnInit, OnChanges, ControlValueAccessor {
|
||||
|
||||
StatusWidgetLayout = StatusWidgetLayout;
|
||||
|
||||
@Input()
|
||||
disabled: boolean;
|
||||
|
||||
@Input()
|
||||
layout: StatusWidgetLayout;
|
||||
|
||||
private modelValue: StatusWidgetStateSettings;
|
||||
|
||||
private propagateChange = null;
|
||||
|
||||
public stateSettingsFormGroup: UntypedFormGroup;
|
||||
|
||||
constructor(private fb: UntypedFormBuilder) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.stateSettingsFormGroup = this.fb.group({
|
||||
showLabel: [null, []],
|
||||
label: [null, []],
|
||||
labelFont: [null, []],
|
||||
showStatus: [null, []],
|
||||
status: [null, []],
|
||||
statusFont: [null, []],
|
||||
icon: [null, []],
|
||||
iconSize: [null, []],
|
||||
iconSizeUnit: [null, []],
|
||||
primaryColor: [null, []],
|
||||
secondaryColor: [null, []],
|
||||
background: [null, []],
|
||||
primaryColorDisabled: [null, []],
|
||||
secondaryColorDisabled: [null, []],
|
||||
backgroundDisabled: [null, []]
|
||||
});
|
||||
this.stateSettingsFormGroup.valueChanges.subscribe(() => {
|
||||
this.updateModel();
|
||||
});
|
||||
merge(this.stateSettingsFormGroup.get('showLabel').valueChanges,
|
||||
this.stateSettingsFormGroup.get('showStatus').valueChanges)
|
||||
.subscribe(() => {
|
||||
this.updateValidators();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
for (const propName of Object.keys(changes)) {
|
||||
const change = changes[propName];
|
||||
if (!change.firstChange && change.currentValue !== change.previousValue) {
|
||||
if (['layout'].includes(propName)) {
|
||||
this.updateValidators();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
this.propagateChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(_fn: any): void {
|
||||
}
|
||||
|
||||
setDisabledState(isDisabled: boolean): void {
|
||||
this.disabled = isDisabled;
|
||||
if (isDisabled) {
|
||||
this.stateSettingsFormGroup.disable({emitEvent: false});
|
||||
} else {
|
||||
this.stateSettingsFormGroup.enable({emitEvent: false});
|
||||
this.updateValidators();
|
||||
}
|
||||
}
|
||||
|
||||
writeValue(value: StatusWidgetStateSettings): void {
|
||||
this.modelValue = value;
|
||||
this.stateSettingsFormGroup.patchValue(
|
||||
value, {emitEvent: false}
|
||||
);
|
||||
this.updateValidators();
|
||||
}
|
||||
|
||||
private updateValidators() {
|
||||
if (this.layout === StatusWidgetLayout.icon) {
|
||||
this.stateSettingsFormGroup.get('showLabel').disable({emitEvent: false});
|
||||
this.stateSettingsFormGroup.get('label').disable({emitEvent: false});
|
||||
this.stateSettingsFormGroup.get('labelFont').disable({emitEvent: false});
|
||||
this.stateSettingsFormGroup.get('showStatus').disable({emitEvent: false});
|
||||
this.stateSettingsFormGroup.get('status').disable({emitEvent: false});
|
||||
this.stateSettingsFormGroup.get('statusFont').disable({emitEvent: false});
|
||||
this.stateSettingsFormGroup.get('secondaryColor').disable({emitEvent: false});
|
||||
this.stateSettingsFormGroup.get('secondaryColorDisabled').disable({emitEvent: false});
|
||||
} else {
|
||||
this.stateSettingsFormGroup.get('showLabel').enable({emitEvent: false});
|
||||
this.stateSettingsFormGroup.get('showStatus').enable({emitEvent: false});
|
||||
this.stateSettingsFormGroup.get('secondaryColor').enable({emitEvent: false});
|
||||
this.stateSettingsFormGroup.get('secondaryColorDisabled').enable({emitEvent: false});
|
||||
const showLabel: boolean = this.stateSettingsFormGroup.get('showLabel').value;
|
||||
const showStatus: boolean = this.stateSettingsFormGroup.get('showStatus').value;
|
||||
if (showLabel) {
|
||||
this.stateSettingsFormGroup.get('label').enable({emitEvent: false});
|
||||
this.stateSettingsFormGroup.get('labelFont').enable({emitEvent: false});
|
||||
} else {
|
||||
this.stateSettingsFormGroup.get('label').disable({emitEvent: false});
|
||||
this.stateSettingsFormGroup.get('labelFont').disable({emitEvent: false});
|
||||
}
|
||||
if (showStatus) {
|
||||
this.stateSettingsFormGroup.get('status').enable({emitEvent: false});
|
||||
this.stateSettingsFormGroup.get('statusFont').enable({emitEvent: false});
|
||||
} else {
|
||||
this.stateSettingsFormGroup.get('status').disable({emitEvent: false});
|
||||
this.stateSettingsFormGroup.get('statusFont').disable({emitEvent: false});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateModel() {
|
||||
this.modelValue = this.stateSettingsFormGroup.getRawValue();
|
||||
this.propagateChange(this.modelValue);
|
||||
}
|
||||
}
|
||||
@ -145,6 +145,9 @@ import {
|
||||
import {
|
||||
TimeSeriesChartGridSettingsComponent
|
||||
} from '@home/components/widget/lib/settings/common/chart/time-series-chart-grid-settings.component';
|
||||
import {
|
||||
StatusWidgetStateSettingsComponent
|
||||
} from '@home/components/widget/lib/settings/common/indicator/status-widget-state-settings.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -198,6 +201,7 @@ import {
|
||||
TimeSeriesChartStatesPanelComponent,
|
||||
TimeSeriesChartStateRowComponent,
|
||||
TimeSeriesChartGridSettingsComponent,
|
||||
StatusWidgetStateSettingsComponent,
|
||||
DataKeyInputComponent,
|
||||
EntityAliasInputComponent
|
||||
],
|
||||
@ -257,6 +261,7 @@ import {
|
||||
TimeSeriesChartStatesPanelComponent,
|
||||
TimeSeriesChartStateRowComponent,
|
||||
TimeSeriesChartGridSettingsComponent,
|
||||
StatusWidgetStateSettingsComponent,
|
||||
DataKeyInputComponent,
|
||||
EntityAliasInputComponent
|
||||
],
|
||||
|
||||
@ -0,0 +1,82 @@
|
||||
<!--
|
||||
|
||||
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]="statusWidgetSettingsForm">
|
||||
<div class="tb-form-panel">
|
||||
<div class="tb-form-panel-title" translate>widgets.status-widget.behavior</div>
|
||||
<div class="tb-form-row">
|
||||
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.rpc-state.initial-state-hint' | translate}}" translate>widgets.rpc-state.initial-state</div>
|
||||
<tb-get-value-action-settings fxFlex
|
||||
panelTitle="widgets.rpc-state.initial-state"
|
||||
[valueType]="valueType.BOOLEAN"
|
||||
trueLabel="widgets.rpc-state.on"
|
||||
falseLabel="widgets.rpc-state.off"
|
||||
stateLabel="widgets.rpc-state.on"
|
||||
[aliasController]="aliasController"
|
||||
[targetDevice]="targetDevice"
|
||||
[widgetType]="widgetType"
|
||||
formControlName="initialState"></tb-get-value-action-settings>
|
||||
</div>
|
||||
<div class="tb-form-row">
|
||||
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.rpc-state.disabled-state-hint' | translate}}" translate>widgets.rpc-state.disabled-state</div>
|
||||
<tb-get-value-action-settings fxFlex
|
||||
panelTitle="widgets.rpc-state.disabled-state"
|
||||
[valueType]="valueType.BOOLEAN"
|
||||
stateLabel="widgets.rpc-state.disabled"
|
||||
[aliasController]="aliasController"
|
||||
[targetDevice]="targetDevice"
|
||||
[widgetType]="widgetType"
|
||||
formControlName="disabledState"></tb-get-value-action-settings>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tb-form-panel">
|
||||
<div class="tb-form-panel-title" translate>widget-config.appearance</div>
|
||||
<tb-image-cards-select rowHeight="1:1"
|
||||
[cols]="{columns: 3,
|
||||
breakpoints: {
|
||||
'lt-sm': 1,
|
||||
'lt-md': 2
|
||||
}}"
|
||||
label="{{ 'widgets.status-widget.layout' | translate }}" formControlName="layout">
|
||||
<tb-image-cards-select-option *ngFor="let layout of statusWidgetLayouts"
|
||||
[value]="layout"
|
||||
[image]="statusWidgetLayoutImageMap.get(layout)">
|
||||
{{ statusWidgetLayoutTranslationMap.get(layout) | translate }}
|
||||
</tb-image-cards-select-option>
|
||||
</tb-image-cards-select>
|
||||
</div>
|
||||
<div class="tb-form-panel">
|
||||
<div fxLayout="row" fxLayoutAlign="space-between center">
|
||||
<div class="tb-form-panel-title" translate>widget-config.card-style</div>
|
||||
<tb-toggle-select [(ngModel)]="cardStyleMode"
|
||||
[ngModelOptions]="{ standalone: true }">
|
||||
<tb-toggle-option value="on">{{ 'widgets.status-widget.on' | translate }}</tb-toggle-option>
|
||||
<tb-toggle-option value="off">{{ 'widgets.status-widget.off' | translate }}</tb-toggle-option>
|
||||
</tb-toggle-select>
|
||||
</div>
|
||||
<tb-status-widget-state-settings
|
||||
*ngIf="cardStyleMode === 'on'"
|
||||
[layout]="statusWidgetSettingsForm.get('layout').value"
|
||||
formControlName="onState">
|
||||
</tb-status-widget-state-settings>
|
||||
<tb-status-widget-state-settings
|
||||
*ngIf="cardStyleMode === 'off'"
|
||||
[layout]="statusWidgetSettingsForm.get('layout').value"
|
||||
formControlName="offState">
|
||||
</tb-status-widget-state-settings>
|
||||
</div>
|
||||
</ng-container>
|
||||
@ -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.
|
||||
///
|
||||
|
||||
import { Component, Injector } from '@angular/core';
|
||||
import { TargetDevice, WidgetSettings, WidgetSettingsComponent, widgetType } from '@shared/models/widget.models';
|
||||
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState } from '@core/core.state';
|
||||
import {
|
||||
statusWidgetDefaultSettings,
|
||||
statusWidgetLayoutImages,
|
||||
statusWidgetLayouts,
|
||||
statusWidgetLayoutTranslations
|
||||
} from '@home/components/widget/lib/indicator/status-widget.models';
|
||||
import { ValueType } from '@shared/models/constants';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-status-widget-settings',
|
||||
templateUrl: './status-widget-settings.component.html',
|
||||
styleUrls: ['./../widget-settings.scss'],
|
||||
})
|
||||
export class StatusWidgetSettingsComponent extends WidgetSettingsComponent {
|
||||
|
||||
get targetDevice(): TargetDevice {
|
||||
return this.widgetConfig?.config?.targetDevice;
|
||||
}
|
||||
|
||||
get widgetType(): widgetType {
|
||||
return this.widgetConfig?.widgetType;
|
||||
}
|
||||
|
||||
statusWidgetLayouts = statusWidgetLayouts;
|
||||
|
||||
statusWidgetLayoutTranslationMap = statusWidgetLayoutTranslations;
|
||||
statusWidgetLayoutImageMap = statusWidgetLayoutImages;
|
||||
|
||||
valueType = ValueType;
|
||||
|
||||
statusWidgetSettingsForm: UntypedFormGroup;
|
||||
|
||||
cardStyleMode = 'on';
|
||||
|
||||
constructor(protected store: Store<AppState>,
|
||||
private $injector: Injector,
|
||||
private fb: UntypedFormBuilder) {
|
||||
super(store);
|
||||
}
|
||||
|
||||
protected settingsForm(): UntypedFormGroup {
|
||||
return this.statusWidgetSettingsForm;
|
||||
}
|
||||
|
||||
protected defaultSettings(): WidgetSettings {
|
||||
return {...statusWidgetDefaultSettings};
|
||||
}
|
||||
|
||||
protected onSettingsSet(settings: WidgetSettings) {
|
||||
this.statusWidgetSettingsForm = this.fb.group({
|
||||
initialState: [settings.initialState, []],
|
||||
disabledState: [settings.disabledState, []],
|
||||
layout: [settings.layout, []],
|
||||
onState: [settings.onState, []],
|
||||
offState: [settings.offState, []]
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -342,6 +342,9 @@ import {
|
||||
import {
|
||||
TimeSeriesChartWidgetSettingsComponent
|
||||
} from '@home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component';
|
||||
import {
|
||||
StatusWidgetSettingsComponent
|
||||
} from '@home/components/widget/lib/settings/indicator/status-widget-settings.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -464,7 +467,8 @@ import {
|
||||
TimeSeriesChartKeySettingsComponent,
|
||||
TimeSeriesChartLineSettingsComponent,
|
||||
TimeSeriesChartBarSettingsComponent,
|
||||
TimeSeriesChartWidgetSettingsComponent
|
||||
TimeSeriesChartWidgetSettingsComponent,
|
||||
StatusWidgetSettingsComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@ -592,7 +596,8 @@ import {
|
||||
TimeSeriesChartKeySettingsComponent,
|
||||
TimeSeriesChartLineSettingsComponent,
|
||||
TimeSeriesChartBarSettingsComponent,
|
||||
TimeSeriesChartWidgetSettingsComponent
|
||||
TimeSeriesChartWidgetSettingsComponent,
|
||||
StatusWidgetSettingsComponent
|
||||
]
|
||||
})
|
||||
export class WidgetSettingsModule {
|
||||
@ -685,5 +690,6 @@ export const widgetSettingsComponentsMap: {[key: string]: Type<IWidgetSettingsCo
|
||||
'tb-slider-widget-settings': SliderWidgetSettingsComponent,
|
||||
'tb-toggle-button-widget-settings': ToggleButtonWidgetSettingsComponent,
|
||||
'tb-time-series-chart-key-settings': TimeSeriesChartKeySettingsComponent,
|
||||
'tb-time-series-chart-widget-settings': TimeSeriesChartWidgetSettingsComponent
|
||||
'tb-time-series-chart-widget-settings': TimeSeriesChartWidgetSettingsComponent,
|
||||
'tb-status-widget-settings': StatusWidgetSettingsComponent
|
||||
};
|
||||
|
||||
@ -86,6 +86,7 @@ import { PowerButtonWidgetComponent } from '@home/components/widget/lib/rpc/powe
|
||||
import { SliderWidgetComponent } from '@home/components/widget/lib/rpc/slider-widget.component';
|
||||
import { ToggleButtonWidgetComponent } from '@home/components/widget/lib/button/toggle-button-widget.component';
|
||||
import { TimeSeriesChartWidgetComponent } from '@home/components/widget/lib/chart/time-series-chart-widget.component';
|
||||
import { StatusWidgetComponent } from '@home/components/widget/lib/indicator/status-widget.component';
|
||||
|
||||
@NgModule({
|
||||
declarations:
|
||||
@ -138,7 +139,8 @@ import { TimeSeriesChartWidgetComponent } from '@home/components/widget/lib/char
|
||||
PowerButtonWidgetComponent,
|
||||
SliderWidgetComponent,
|
||||
ToggleButtonWidgetComponent,
|
||||
TimeSeriesChartWidgetComponent
|
||||
TimeSeriesChartWidgetComponent,
|
||||
StatusWidgetComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@ -195,7 +197,8 @@ import { TimeSeriesChartWidgetComponent } from '@home/components/widget/lib/char
|
||||
PowerButtonWidgetComponent,
|
||||
SliderWidgetComponent,
|
||||
ToggleButtonWidgetComponent,
|
||||
TimeSeriesChartWidgetComponent
|
||||
TimeSeriesChartWidgetComponent,
|
||||
StatusWidgetComponent
|
||||
],
|
||||
providers: [
|
||||
{provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule }
|
||||
|
||||
@ -5461,6 +5461,25 @@
|
||||
"signal-strength-card-style": "Signal strength card style",
|
||||
"no-signal-rssi-value": "\"No signal\" rssi value"
|
||||
},
|
||||
"status-widget": {
|
||||
"behavior": "Behavior",
|
||||
"layout": "Layout",
|
||||
"layout-default": "Default",
|
||||
"layout-center": "Center",
|
||||
"layout-icon": "Icon",
|
||||
"on": "On",
|
||||
"off": "Off",
|
||||
"label": "Label",
|
||||
"status": "Status",
|
||||
"icon": "Icon",
|
||||
"color-palette": "Color palette",
|
||||
"disabled-color-palette": "Disabled color palette",
|
||||
"primary": "Primary",
|
||||
"primary-color-hint": "Color of icon and label",
|
||||
"secondary": "Secondary",
|
||||
"secondary-color-hint": "Color of status",
|
||||
"background": "Background"
|
||||
},
|
||||
"chart": {
|
||||
"common-settings": "Common settings",
|
||||
"enable-stacking-mode": "Enable stacking mode",
|
||||
|
||||
20
ui-ngx/src/assets/widget/status-widget/center-layout.svg
Normal file
20
ui-ngx/src/assets/widget/status-widget/center-layout.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 19 KiB |
20
ui-ngx/src/assets/widget/status-widget/default-layout.svg
Normal file
20
ui-ngx/src/assets/widget/status-widget/default-layout.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 19 KiB |
18
ui-ngx/src/assets/widget/status-widget/icon-layout.svg
Normal file
18
ui-ngx/src/assets/widget/status-widget/icon-layout.svg
Normal file
@ -0,0 +1,18 @@
|
||||
<svg width="141" height="140" viewBox="0 0 141 140" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d_5952_143941)">
|
||||
<rect x="8.66675" y="4" width="124" height="124" rx="4" fill="#3F52DD"/>
|
||||
<path d="M83.0387 55.8776H58.2949V53.6282H83.0387V55.8776ZM59.4196 77.2472H63.9185C63.9185 73.8731 61.6691 71.6236 61.6691 71.6236C68.4174 67.1248 69.5421 57.0023 69.5421 57.0023H59.4196V77.2472ZM81.914 57.0023H71.7915C71.7915 57.0023 72.9162 67.1248 79.6645 71.6236C79.6645 71.6236 77.4151 73.8731 77.4151 77.2472H81.914V57.0023Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_5952_143941" x="0.666748" y="0" width="140" height="140" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="4"/>
|
||||
<feGaussianBlur stdDeviation="4"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5952_143941"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_5952_143941" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
Loading…
x
Reference in New Issue
Block a user