committed by
GitHub
24 changed files with 2155 additions and 10 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,197 @@ |
|||
<!-- |
|||
|
|||
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]="powerButtonWidgetConfigForm"> |
|||
<tb-target-device formControlName="targetDevice"></tb-target-device> |
|||
<div class="tb-form-panel"> |
|||
<div class="tb-form-panel-title" translate>widgets.power-button.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 space-between"> |
|||
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.power-button.power-on-hint' | translate}}" translate>widgets.power-button.power-on</div> |
|||
<tb-set-value-action-settings fxFlex |
|||
panelTitle="widgets.power-button.power-on " |
|||
[aliasController]="aliasController" |
|||
[targetDevice]="targetDevice" |
|||
[widgetType]="widgetType" |
|||
formControlName="onUpdateState"></tb-set-value-action-settings> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.power-button.power-off-hint' | translate}}" translate>widgets.power-button.power-off</div> |
|||
<tb-set-value-action-settings fxFlex |
|||
panelTitle="widgets.power-button.power-off" |
|||
[aliasController]="aliasController" |
|||
[targetDevice]="targetDevice" |
|||
[widgetType]="widgetType" |
|||
formControlName="offUpdateState"></tb-set-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.power-button.layout' | translate }}" formControlName="layout"> |
|||
<tb-image-cards-select-option *ngFor="let layout of powerButtonLayouts" |
|||
[value]="layout" |
|||
[image]="powerButtonLayoutImageMap.get(layout)"> |
|||
{{ powerButtonLayoutTranslationMap.get(layout) | translate }} |
|||
</tb-image-cards-select-option> |
|||
</tb-image-cards-select> |
|||
<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]="powerButtonWidgetConfigForm.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"> |
|||
{{ 'widget-config.card-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]="powerButtonWidgetConfigForm.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.power-button.power-on-colors' | translate }}</div> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="12px"> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<div translate>widgets.power-button.main</div> |
|||
<tb-color-input asBoxInput |
|||
formControlName="mainColorOn"> |
|||
</tb-color-input> |
|||
</div> |
|||
<mat-divider vertical></mat-divider> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<div translate>widgets.power-button.background</div> |
|||
<tb-color-input asBoxInput |
|||
formControlName="backgroundColorOn"> |
|||
</tb-color-input> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div>{{ 'widgets.power-button.power-off-colors' | translate }}</div> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="12px"> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<div translate>widgets.power-button.main</div> |
|||
<tb-color-input asBoxInput |
|||
formControlName="mainColorOff"> |
|||
</tb-color-input> |
|||
</div> |
|||
<mat-divider vertical></mat-divider> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<div translate>widgets.power-button.background</div> |
|||
<tb-color-input asBoxInput |
|||
formControlName="backgroundColorOff"> |
|||
</tb-color-input> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div>{{ 'widgets.power-button.disabled-colors' | translate }}</div> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="12px"> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<div translate>widgets.power-button.main</div> |
|||
<tb-color-input asBoxInput |
|||
formControlName="mainColorDisabled"> |
|||
</tb-color-input> |
|||
</div> |
|||
<mat-divider vertical></mat-divider> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<div translate>widgets.power-button.background</div> |
|||
<tb-color-input asBoxInput |
|||
formControlName="backgroundColorDisabled"> |
|||
</tb-color-input> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</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"> |
|||
<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="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,195 @@ |
|||
///
|
|||
/// 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 { 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 { |
|||
powerButtonDefaultSettings, |
|||
powerButtonLayoutImages, |
|||
powerButtonLayouts, |
|||
powerButtonLayoutTranslations, |
|||
PowerButtonWidgetSettings |
|||
} from '@home/components/widget/lib/rpc/power-button-widget.models'; |
|||
import { cssSizeToStrSize, resolveCssSize } from '@shared/models/widget-settings.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-power-button-basic-config', |
|||
templateUrl: './power-button-basic-config.component.html', |
|||
styleUrls: ['../basic-config.scss'] |
|||
}) |
|||
export class PowerButtonBasicConfigComponent extends BasicWidgetConfigComponent { |
|||
|
|||
get targetDevice(): TargetDevice { |
|||
return this.powerButtonWidgetConfigForm.get('targetDevice').value; |
|||
} |
|||
|
|||
powerButtonLayouts = powerButtonLayouts; |
|||
|
|||
powerButtonLayoutTranslationMap = powerButtonLayoutTranslations; |
|||
powerButtonLayoutImageMap = powerButtonLayoutImages; |
|||
|
|||
valueType = ValueType; |
|||
|
|||
powerButtonWidgetConfigForm: UntypedFormGroup; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
protected widgetConfigComponent: WidgetConfigComponent, |
|||
private fb: UntypedFormBuilder) { |
|||
super(store, widgetConfigComponent); |
|||
} |
|||
|
|||
protected configForm(): UntypedFormGroup { |
|||
return this.powerButtonWidgetConfigForm; |
|||
} |
|||
|
|||
protected onConfigSet(configData: WidgetConfigComponentData) { |
|||
const settings: PowerButtonWidgetSettings = {...powerButtonDefaultSettings, ...(configData.config.settings || {})}; |
|||
const iconSize = resolveCssSize(configData.config.iconSize); |
|||
this.powerButtonWidgetConfigForm = this.fb.group({ |
|||
targetDevice: [configData.config.targetDevice, []], |
|||
|
|||
initialState: [settings.initialState, []], |
|||
onUpdateState: [settings.onUpdateState, []], |
|||
offUpdateState: [settings.offUpdateState, []], |
|||
disabledState: [settings.disabledState, []], |
|||
|
|||
layout: [settings.layout, []], |
|||
|
|||
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, []], |
|||
|
|||
mainColorOn: [settings.mainColorOn, []], |
|||
backgroundColorOn: [settings.backgroundColorOn, []], |
|||
|
|||
mainColorOff: [settings.mainColorOff, []], |
|||
backgroundColorOff: [settings.backgroundColorOff, []], |
|||
|
|||
mainColorDisabled: [settings.mainColorDisabled, []], |
|||
backgroundColorDisabled: [settings.backgroundColorDisabled, []], |
|||
|
|||
background: [settings.background, []], |
|||
|
|||
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.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.initialState = config.initialState; |
|||
this.widgetConfig.config.settings.onUpdateState = config.onUpdateState; |
|||
this.widgetConfig.config.settings.offUpdateState = config.offUpdateState; |
|||
this.widgetConfig.config.settings.disabledState = config.disabledState; |
|||
|
|||
this.widgetConfig.config.settings.layout = config.layout; |
|||
|
|||
this.widgetConfig.config.settings.mainColorOn = config.mainColorOn; |
|||
this.widgetConfig.config.settings.backgroundColorOn = config.backgroundColorOn; |
|||
|
|||
this.widgetConfig.config.settings.mainColorOff = config.mainColorOff; |
|||
this.widgetConfig.config.settings.backgroundColorOff = config.backgroundColorOff; |
|||
|
|||
this.widgetConfig.config.settings.mainColorDisabled = config.mainColorDisabled; |
|||
this.widgetConfig.config.settings.backgroundColorDisabled = config.backgroundColorDisabled; |
|||
|
|||
this.widgetConfig.config.settings.background = config.background; |
|||
|
|||
this.setCardButtons(config.cardButtons, this.widgetConfig.config); |
|||
this.widgetConfig.config.borderRadius = config.borderRadius; |
|||
|
|||
this.widgetConfig.config.actions = config.actions; |
|||
return this.widgetConfig; |
|||
} |
|||
|
|||
protected validatorTriggers(): string[] { |
|||
return ['showTitle', 'showIcon']; |
|||
} |
|||
|
|||
protected updateValidators(emitEvent: boolean, trigger?: string) { |
|||
const showTitle: boolean = this.powerButtonWidgetConfigForm.get('showTitle').value; |
|||
const showIcon: boolean = this.powerButtonWidgetConfigForm.get('showIcon').value; |
|||
if (showTitle) { |
|||
this.powerButtonWidgetConfigForm.get('title').enable(); |
|||
this.powerButtonWidgetConfigForm.get('titleFont').enable(); |
|||
this.powerButtonWidgetConfigForm.get('titleColor').enable(); |
|||
this.powerButtonWidgetConfigForm.get('showIcon').enable({emitEvent: false}); |
|||
if (showIcon) { |
|||
this.powerButtonWidgetConfigForm.get('iconSize').enable(); |
|||
this.powerButtonWidgetConfigForm.get('iconSizeUnit').enable(); |
|||
this.powerButtonWidgetConfigForm.get('icon').enable(); |
|||
this.powerButtonWidgetConfigForm.get('iconColor').enable(); |
|||
} else { |
|||
this.powerButtonWidgetConfigForm.get('iconSize').disable(); |
|||
this.powerButtonWidgetConfigForm.get('iconSizeUnit').disable(); |
|||
this.powerButtonWidgetConfigForm.get('icon').disable(); |
|||
this.powerButtonWidgetConfigForm.get('iconColor').disable(); |
|||
} |
|||
} else { |
|||
this.powerButtonWidgetConfigForm.get('title').disable(); |
|||
this.powerButtonWidgetConfigForm.get('titleFont').disable(); |
|||
this.powerButtonWidgetConfigForm.get('titleColor').disable(); |
|||
this.powerButtonWidgetConfigForm.get('showIcon').disable({emitEvent: false}); |
|||
this.powerButtonWidgetConfigForm.get('iconSize').disable(); |
|||
this.powerButtonWidgetConfigForm.get('iconSizeUnit').disable(); |
|||
this.powerButtonWidgetConfigForm.get('icon').disable(); |
|||
this.powerButtonWidgetConfigForm.get('iconColor').disable(); |
|||
} |
|||
} |
|||
|
|||
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,32 @@ |
|||
<!-- |
|||
|
|||
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-power-button-panel" [style]="backgroundStyle$ | async"> |
|||
<div class="tb-power-button-overlay" [style]="overlayStyle"></div> |
|||
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container> |
|||
<div class="tb-power-button-content"> |
|||
<div #powerButtonShape class="tb-power-button-shape" [class.tb-power-button-pointer]="!disabled && (loading$ | async) === false"> |
|||
</div> |
|||
</div> |
|||
<mat-progress-bar class="tb-action-widget-progress" style="height: 4px;" color="accent" mode="indeterminate" *ngIf="loading$ | async"></mat-progress-bar> |
|||
<div *ngIf="error" class="tb-action-widget-error-container"> |
|||
<div class="tb-action-widget-error-panel"> |
|||
<div class="tb-action-widget-error-text" [innerHTML]="error | safe: 'html'"></div> |
|||
<button class="tb-action-widget-error-clear tb-mat-20" mat-icon-button (click)="clearError()"><mat-icon>close</mat-icon></button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,64 @@ |
|||
/** |
|||
* 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-power-button-panel { |
|||
width: 100%; |
|||
height: 100%; |
|||
position: relative; |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 16px; |
|||
padding: 20px 24px 24px 24px; |
|||
> div:not(.tb-power-button-overlay) { |
|||
z-index: 1; |
|||
} |
|||
.tb-power-button-overlay { |
|||
position: absolute; |
|||
top: 12px; |
|||
left: 12px; |
|||
bottom: 12px; |
|||
right: 12px; |
|||
} |
|||
div.tb-widget-title { |
|||
padding: 0; |
|||
} |
|||
.tb-power-button-content { |
|||
flex: 1; |
|||
min-width: 0; |
|||
min-height: 0; |
|||
.tb-power-button-shape { |
|||
width: 100%; |
|||
height: 100%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
svg { |
|||
.tb-small-shadow { |
|||
filter: drop-shadow(0px 0px 4px rgba(0, 0, 0, 0.2)); |
|||
} |
|||
.tb-shadow { |
|||
filter: drop-shadow(0px 0px 10px rgba(0, 0, 0, 0.15)); |
|||
} |
|||
} |
|||
&.tb-power-button-pointer { |
|||
svg { |
|||
.tb-hover-circle { |
|||
cursor: pointer; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,203 @@ |
|||
///
|
|||
/// 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, ValueSetter } from '@home/components/widget/lib/action/action-widget.models'; |
|||
import { backgroundStyle, ComponentStyle, overlayStyle } from '@shared/models/widget-settings.models'; |
|||
import { Observable } from 'rxjs'; |
|||
import { ResizeObserver } from '@juggle/resize-observer'; |
|||
import { ImagePipe } from '@shared/pipe/image.pipe'; |
|||
import { DomSanitizer } from '@angular/platform-browser'; |
|||
import { ValueType } from '@shared/models/constants'; |
|||
import { |
|||
powerButtonDefaultSettings, |
|||
PowerButtonShape, |
|||
powerButtonShapeSize, |
|||
PowerButtonWidgetSettings |
|||
} from '@home/components/widget/lib/rpc/power-button-widget.models'; |
|||
import { SVG, Svg } from '@svgdotjs/svg.js'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-power-button-widget', |
|||
templateUrl: './power-button-widget.component.html', |
|||
styleUrls: ['../action/action-widget.scss', './power-button-widget.component.scss'], |
|||
encapsulation: ViewEncapsulation.None |
|||
}) |
|||
export class PowerButtonWidgetComponent extends |
|||
BasicActionWidgetComponent implements OnInit, AfterViewInit, OnDestroy { |
|||
|
|||
@ViewChild('powerButtonShape', {static: false}) |
|||
powerButtonShape: ElementRef<HTMLElement>; |
|||
|
|||
settings: PowerButtonWidgetSettings; |
|||
|
|||
backgroundStyle$: Observable<ComponentStyle>; |
|||
overlayStyle: ComponentStyle = {}; |
|||
|
|||
value = false; |
|||
disabled = false; |
|||
|
|||
private shapeResize$: ResizeObserver; |
|||
private drawSvgShapePending = false; |
|||
private svgShape: Svg; |
|||
private powerButtonSvgShape: PowerButtonShape; |
|||
private disabledState = false; |
|||
|
|||
private onValueSetter: ValueSetter<boolean>; |
|||
private offValueSetter: ValueSetter<boolean>; |
|||
|
|||
constructor(protected imagePipe: ImagePipe, |
|||
protected sanitizer: DomSanitizer, |
|||
private renderer: Renderer2, |
|||
protected cd: ChangeDetectorRef) { |
|||
super(cd); |
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
super.ngOnInit(); |
|||
this.settings = {...powerButtonDefaultSettings, ...this.ctx.settings}; |
|||
|
|||
this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); |
|||
this.overlayStyle = overlayStyle(this.settings.background.overlay); |
|||
|
|||
const getInitialStateSettings = |
|||
{...this.settings.initialState, actionLabel: this.ctx.translate.instant('widgets.rpc-state.initial-state')}; |
|||
this.createValueGetter(getInitialStateSettings, ValueType.BOOLEAN, { |
|||
next: (value) => this.onValue(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) |
|||
}); |
|||
|
|||
const onUpdateStateSettings = {...this.settings.onUpdateState, |
|||
actionLabel: this.ctx.translate.instant('widgets.power-button.power-on')}; |
|||
this.onValueSetter = this.createValueSetter(onUpdateStateSettings); |
|||
|
|||
const offUpdateStateSettings = {...this.settings.offUpdateState, |
|||
actionLabel: this.ctx.translate.instant('widgets.power-button.power-off')}; |
|||
this.offValueSetter = this.createValueSetter(offUpdateStateSettings); |
|||
|
|||
this.loading$.subscribe((loading) => { |
|||
this.updateDisabledState(loading || this.disabled); |
|||
this.cd.markForCheck(); |
|||
}); |
|||
} |
|||
|
|||
ngAfterViewInit(): void { |
|||
if (this.drawSvgShapePending) { |
|||
this.drawSvg(); |
|||
} |
|||
super.ngAfterViewInit(); |
|||
} |
|||
|
|||
ngOnDestroy() { |
|||
if (this.shapeResize$) { |
|||
this.shapeResize$.disconnect(); |
|||
} |
|||
super.ngOnDestroy(); |
|||
} |
|||
|
|||
public onInit() { |
|||
super.onInit(); |
|||
const borderRadius = this.ctx.$widgetElement.css('borderRadius'); |
|||
this.overlayStyle = {...this.overlayStyle, ...{borderRadius}}; |
|||
if (this.powerButtonShape) { |
|||
this.drawSvg(); |
|||
} else { |
|||
this.drawSvgShapePending = true; |
|||
} |
|||
this.cd.detectChanges(); |
|||
} |
|||
|
|||
private onValue(value: boolean): void { |
|||
const newValue = !!value; |
|||
if (this.value !== newValue) { |
|||
this.value = newValue; |
|||
this.powerButtonSvgShape?.setValue(this.value); |
|||
this.cd.markForCheck(); |
|||
} |
|||
} |
|||
|
|||
private onDisabled(value: boolean): void { |
|||
const newDisabled = !!value; |
|||
if (this.disabled !== newDisabled) { |
|||
this.disabled = newDisabled; |
|||
this.updateDisabledState(this.disabled); |
|||
this.cd.markForCheck(); |
|||
} |
|||
} |
|||
|
|||
private onClick() { |
|||
if (!this.ctx.isEdit && !this.ctx.isPreview && !this.disabledState) { |
|||
this.onValue(!this.value); |
|||
const targetValue = this.value; |
|||
const targetSetter = targetValue ? this.onValueSetter : this.offValueSetter; |
|||
this.powerButtonSvgShape?.setPressed(true); |
|||
this.updateValue(targetSetter, targetValue, { |
|||
next: () => { |
|||
this.powerButtonSvgShape?.setPressed(false); |
|||
this.onValue(targetValue); |
|||
}, |
|||
error: () => { |
|||
this.powerButtonSvgShape?.setPressed(false); |
|||
this.onValue(!targetValue); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
|
|||
private drawSvg() { |
|||
this.svgShape = SVG().addTo(this.powerButtonShape.nativeElement).size(powerButtonShapeSize, powerButtonShapeSize); |
|||
this.renderer.setStyle(this.svgShape.node, 'overflow', 'visible'); |
|||
this.renderer.setStyle(this.svgShape.node, 'user-select', 'none'); |
|||
|
|||
this.powerButtonSvgShape = PowerButtonShape.fromSettings(this.ctx, this.svgShape, |
|||
this.settings, this.value, this.disabledState, () => this.onClick()); |
|||
|
|||
this.shapeResize$ = new ResizeObserver(() => { |
|||
this.onResize(); |
|||
}); |
|||
this.shapeResize$.observe(this.powerButtonShape.nativeElement); |
|||
this.onResize(); |
|||
} |
|||
|
|||
private updateDisabledState(disabled: boolean) { |
|||
this.disabledState = disabled; |
|||
this.powerButtonSvgShape?.setDisabled(this.disabledState); |
|||
} |
|||
|
|||
private onResize() { |
|||
const shapeWidth = this.powerButtonShape.nativeElement.getBoundingClientRect().width; |
|||
const shapeHeight = this.powerButtonShape.nativeElement.getBoundingClientRect().height; |
|||
const size = Math.min(shapeWidth, shapeHeight); |
|||
const scale = size / powerButtonShapeSize; |
|||
this.renderer.setStyle(this.svgShape.node, 'transform', `scale(${scale})`); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,953 @@ |
|||
///
|
|||
/// 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 } from '@shared/models/widget-settings.models'; |
|||
import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; |
|||
import { |
|||
DataToValueType, |
|||
GetValueAction, |
|||
GetValueSettings, |
|||
SetValueAction, |
|||
SetValueSettings, |
|||
ValueToDataType |
|||
} from '@shared/models/action-widget-settings.models'; |
|||
import { Circle, Effect, Element, G, Gradient, Runner, Svg, Text, Timeline } from '@svgdotjs/svg.js'; |
|||
import '@svgdotjs/svg.filter.js'; |
|||
import tinycolor from 'tinycolor2'; |
|||
import { WidgetContext } from '@home/models/widget-component.models'; |
|||
|
|||
export enum PowerButtonLayout { |
|||
default = 'default', |
|||
simplified = 'simplified', |
|||
outlined = 'outlined', |
|||
default_volume = 'default_volume', |
|||
simplified_volume = 'simplified_volume', |
|||
outlined_volume = 'outlined_volume' |
|||
} |
|||
|
|||
export const powerButtonLayouts = Object.keys(PowerButtonLayout) as PowerButtonLayout[]; |
|||
|
|||
export const powerButtonLayoutTranslations = new Map<PowerButtonLayout, string>( |
|||
[ |
|||
[PowerButtonLayout.default, 'widgets.power-button.layout-default'], |
|||
[PowerButtonLayout.simplified, 'widgets.power-button.layout-simplified'], |
|||
[PowerButtonLayout.outlined, 'widgets.power-button.layout-outlined'], |
|||
[PowerButtonLayout.default_volume, 'widgets.power-button.layout-default-volume'], |
|||
[PowerButtonLayout.simplified_volume, 'widgets.power-button.layout-simplified-volume'], |
|||
[PowerButtonLayout.outlined_volume, 'widgets.power-button.layout-outlined-volume'] |
|||
] |
|||
); |
|||
|
|||
export const powerButtonLayoutImages = new Map<PowerButtonLayout, string>( |
|||
[ |
|||
[PowerButtonLayout.default, 'assets/widget/power-button/default-layout.svg'], |
|||
[PowerButtonLayout.simplified, 'assets/widget/power-button/simplified-layout.svg'], |
|||
[PowerButtonLayout.outlined, 'assets/widget/power-button/outlined-layout.svg'], |
|||
[PowerButtonLayout.default_volume, 'assets/widget/power-button/default-volume-layout.svg'], |
|||
[PowerButtonLayout.simplified_volume, 'assets/widget/power-button/simplified-volume-layout.svg'], |
|||
[PowerButtonLayout.outlined_volume, 'assets/widget/power-button/outlined-volume-layout.svg'] |
|||
] |
|||
); |
|||
|
|||
export interface PowerButtonWidgetSettings { |
|||
initialState: GetValueSettings<boolean>; |
|||
disabledState: GetValueSettings<boolean>; |
|||
onUpdateState: SetValueSettings; |
|||
offUpdateState: SetValueSettings; |
|||
layout: PowerButtonLayout; |
|||
mainColorOn: string; |
|||
backgroundColorOn: string; |
|||
mainColorOff: string; |
|||
backgroundColorOff: string; |
|||
mainColorDisabled: string; |
|||
backgroundColorDisabled: string; |
|||
background: BackgroundSettings; |
|||
} |
|||
|
|||
export const powerButtonDefaultSettings: PowerButtonWidgetSettings = { |
|||
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;' |
|||
} |
|||
}, |
|||
onUpdateState: { |
|||
action: SetValueAction.EXECUTE_RPC, |
|||
executeRpc: { |
|||
method: 'setState', |
|||
requestTimeout: 5000, |
|||
requestPersistent: false, |
|||
persistentPollingInterval: 1000 |
|||
}, |
|||
setAttribute: { |
|||
key: 'state', |
|||
scope: AttributeScope.SHARED_SCOPE |
|||
}, |
|||
putTimeSeries: { |
|||
key: 'state' |
|||
}, |
|||
valueToData: { |
|||
type: ValueToDataType.CONSTANT, |
|||
constantValue: true, |
|||
valueToDataFunction: '/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;' |
|||
} |
|||
}, |
|||
offUpdateState: { |
|||
action: SetValueAction.EXECUTE_RPC, |
|||
executeRpc: { |
|||
method: 'setState', |
|||
requestTimeout: 5000, |
|||
requestPersistent: false, |
|||
persistentPollingInterval: 1000 |
|||
}, |
|||
setAttribute: { |
|||
key: 'state', |
|||
scope: AttributeScope.SHARED_SCOPE |
|||
}, |
|||
putTimeSeries: { |
|||
key: 'state' |
|||
}, |
|||
valueToData: { |
|||
type: ValueToDataType.CONSTANT, |
|||
constantValue: false, |
|||
valueToDataFunction: '/* Convert input boolean value to RPC parameters or attribute/time-series value */ \n return value;' |
|||
} |
|||
}, |
|||
layout: PowerButtonLayout.default, |
|||
mainColorOn: '#3F52DD', |
|||
backgroundColorOn: '#FFFFFF', |
|||
mainColorOff: '#A2A2A2', |
|||
backgroundColorOff: '#FFFFFF', |
|||
mainColorDisabled: 'rgba(0,0,0,0.12)', |
|||
backgroundColorDisabled: '#FFFFFF', |
|||
background: { |
|||
type: BackgroundType.color, |
|||
color: '#fff', |
|||
overlay: { |
|||
enabled: false, |
|||
color: 'rgba(255,255,255,0.72)', |
|||
blur: 3 |
|||
} |
|||
} |
|||
}; |
|||
|
|||
interface PowerButtonColor { |
|||
hex: string; |
|||
opacity: number; |
|||
} |
|||
|
|||
type PowerButtonState = 'on' | 'off' | 'disabled'; |
|||
|
|||
interface PowerButtonColorState { |
|||
mainColor: PowerButtonColor; |
|||
backgroundColor: PowerButtonColor; |
|||
} |
|||
|
|||
type PowerButtonShapeColors = Record<PowerButtonState, PowerButtonColorState>; |
|||
|
|||
const createPowerButtonShapeColors = (settings: PowerButtonWidgetSettings): PowerButtonShapeColors => { |
|||
const mainColorOn = tinycolor(settings.mainColorOn); |
|||
const backgroundColorOn = tinycolor(settings.backgroundColorOn); |
|||
const mainColorOff = tinycolor(settings.mainColorOff); |
|||
const backgroundColorOff = tinycolor(settings.backgroundColorOff); |
|||
const mainColorDisabled = tinycolor(settings.mainColorDisabled); |
|||
const backgroundColorDisabled = tinycolor(settings.backgroundColorDisabled); |
|||
return { |
|||
on: { |
|||
mainColor: {hex: mainColorOn.toHexString(), opacity: mainColorOn.getAlpha()}, |
|||
backgroundColor: {hex: backgroundColorOn.toHexString(), opacity: backgroundColorOn.getAlpha()}, |
|||
}, |
|||
off: { |
|||
mainColor: {hex: mainColorOff.toHexString(), opacity: mainColorOff.getAlpha()}, |
|||
backgroundColor: {hex: backgroundColorOff.toHexString(), opacity: backgroundColorOff.getAlpha()}, |
|||
}, |
|||
disabled: { |
|||
mainColor: {hex: mainColorDisabled.toHexString(), opacity: mainColorDisabled.getAlpha()}, |
|||
backgroundColor: {hex: backgroundColorDisabled.toHexString(), opacity: backgroundColorDisabled.getAlpha()}, |
|||
} |
|||
}; |
|||
}; |
|||
|
|||
export const powerButtonShapeSize = 110; |
|||
const cx = powerButtonShapeSize / 2; |
|||
const cy = powerButtonShapeSize / 2; |
|||
|
|||
const powerButtonAnimation = (element: Element): Runner => element.animate(200, 0, 'now'); |
|||
|
|||
export abstract class PowerButtonShape { |
|||
|
|||
static fromSettings(ctx: WidgetContext, |
|||
svgShape: Svg, |
|||
settings: PowerButtonWidgetSettings, |
|||
value: boolean, |
|||
disabled: boolean, |
|||
onClick: () => void): PowerButtonShape { |
|||
switch (settings.layout) { |
|||
case PowerButtonLayout.default: |
|||
return new DefaultPowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); |
|||
case PowerButtonLayout.simplified: |
|||
return new SimplifiedPowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); |
|||
case PowerButtonLayout.outlined: |
|||
return new OutlinedPowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); |
|||
case PowerButtonLayout.default_volume: |
|||
return new DefaultVolumePowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); |
|||
case PowerButtonLayout.simplified_volume: |
|||
return new SimplifiedVolumePowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); |
|||
case PowerButtonLayout.outlined_volume: |
|||
return new OutlinedVolumePowerButtonShape(ctx, svgShape, settings, value, disabled, onClick); |
|||
} |
|||
} |
|||
|
|||
protected readonly colors: PowerButtonShapeColors; |
|||
protected readonly onLabel: string; |
|||
protected readonly offLabel: string; |
|||
|
|||
protected backgroundShape: Circle; |
|||
protected hoverShape: Circle; |
|||
protected hovered = false; |
|||
protected pressed = false; |
|||
protected forcePressed = false; |
|||
|
|||
protected constructor(protected widgetContext: WidgetContext, |
|||
protected svgShape: Svg, |
|||
protected settings: PowerButtonWidgetSettings, |
|||
protected value: boolean, |
|||
protected disabled: boolean, |
|||
protected onClick: () => void) { |
|||
this.colors = createPowerButtonShapeColors(this.settings); |
|||
this.onLabel = this.widgetContext.translate.instant('widgets.power-button.on-label').toUpperCase(); |
|||
this.offLabel = this.widgetContext.translate.instant('widgets.power-button.off-label').toUpperCase(); |
|||
this._drawShape(); |
|||
} |
|||
|
|||
public setValue(value: boolean) { |
|||
if (this.value !== value) { |
|||
this.value = value; |
|||
this._drawState(); |
|||
} |
|||
} |
|||
|
|||
public setDisabled(disabled: boolean) { |
|||
if (this.disabled !== disabled) { |
|||
this.disabled = disabled; |
|||
this._drawState(); |
|||
} |
|||
} |
|||
|
|||
public setPressed(pressed: boolean) { |
|||
if (this.forcePressed !== pressed) { |
|||
this.forcePressed = pressed; |
|||
if (this.forcePressed && !this.pressed) { |
|||
this.onPressStart(); |
|||
} else if (!this.forcePressed && !this.pressed) { |
|||
this.onPressEnd(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private _drawShape() { |
|||
|
|||
this.backgroundShape = this.svgShape.circle(powerButtonShapeSize).center(cx, cy) |
|||
.fill({opacity: 0}).stroke({width: 0}); |
|||
|
|||
this.drawShape(); |
|||
|
|||
this.hoverShape = this.svgShape.circle(powerButtonShapeSize).center(cx, cy).addClass('tb-hover-circle') |
|||
.fill({color: '#000000', opacity: 0}); |
|||
this.hoverShape.on('mouseover', () => { |
|||
this.hovered = true; |
|||
if (!this.disabled) { |
|||
this.hoverShape.timeline().finish(); |
|||
this.hoverShape.animate(200).attr({'fill-opacity': 0.06}); |
|||
} |
|||
}); |
|||
this.hoverShape.on('mouseout', () => { |
|||
this.hovered = false; |
|||
this.hoverShape.timeline().finish(); |
|||
this.hoverShape.animate(200).attr({'fill-opacity': 0}); |
|||
this._cancelPressed(); |
|||
}); |
|||
this.hoverShape.on('touchmove', (event: TouchEvent) => { |
|||
const touch = event.touches[0]; |
|||
const element = document.elementFromPoint(touch.pageX,touch.pageY); |
|||
if (this.hoverShape.node !== element) { |
|||
this._cancelPressed(); |
|||
} |
|||
}); |
|||
this.hoverShape.on('touchcancel', () => { |
|||
this._cancelPressed(); |
|||
}); |
|||
this.hoverShape.on('mousedown touchstart', (event: Event) => { |
|||
if (event.type === 'mousedown') { |
|||
if ((event as MouseEvent).button !== 0) { |
|||
return; |
|||
} |
|||
} |
|||
if (!this.disabled && !this.pressed) { |
|||
this.pressed = true; |
|||
if (!this.forcePressed) { |
|||
this.onPressStart(); |
|||
} |
|||
} |
|||
}); |
|||
this.hoverShape.on('mouseup touchend touchcancel', () => { |
|||
if (this.pressed && !this.disabled) { |
|||
this.onClick(); |
|||
} |
|||
this._cancelPressed(); |
|||
}); |
|||
this._drawState(); |
|||
} |
|||
|
|||
private _cancelPressed() { |
|||
if (this.pressed) { |
|||
this.pressed = false; |
|||
if (!this.forcePressed) { |
|||
this.onPressEnd(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private _drawState() { |
|||
let colorState: PowerButtonColorState; |
|||
if (this.disabled) { |
|||
colorState = this.colors.disabled; |
|||
} else { |
|||
colorState = this.value ? this.colors.on : this.colors.off; |
|||
} |
|||
this.drawBackgroundState(colorState.backgroundColor); |
|||
this.drawColorState(colorState.mainColor); |
|||
if (this.value) { |
|||
this.drawOn(); |
|||
} else { |
|||
this.drawOff(); |
|||
} |
|||
if (this.disabled) { |
|||
this.hoverShape.timeline().finish(); |
|||
this.hoverShape.attr({'fill-opacity': 0}); |
|||
} else if (this.hovered) { |
|||
this.hoverShape.timeline().finish(); |
|||
this.hoverShape.animate(200).attr({'fill-opacity': 0.06}); |
|||
} |
|||
} |
|||
|
|||
private drawBackgroundState(backgroundColor: PowerButtonColor) { |
|||
this.backgroundShape.attr({ fill: backgroundColor.hex, 'fill-opacity': backgroundColor.opacity}); |
|||
} |
|||
|
|||
protected drawShape() {} |
|||
|
|||
protected drawColorState(_mainColor: PowerButtonColor) {} |
|||
|
|||
protected drawOff() {} |
|||
|
|||
protected drawOn() {} |
|||
|
|||
protected onPressStart() {} |
|||
|
|||
protected onPressEnd() {} |
|||
|
|||
protected createMask(shape: Element, maskElements: Element[]) { |
|||
const mask = |
|||
this.svgShape.mask().add(this.svgShape.rect().width('100%').height('100%').fill('#fff')); |
|||
maskElements.forEach(e => { |
|||
mask.add(e.fill('#000').attr({'fill-opacity': 1})); |
|||
}); |
|||
shape.maskWith(mask); |
|||
} |
|||
|
|||
protected createOnLabel(fontWeight = '500'): Text { |
|||
return this.createLabel(this.onLabel, fontWeight); |
|||
} |
|||
|
|||
protected createOffLabel(fontWeight = '500'): Text { |
|||
return this.createLabel(this.offLabel, fontWeight); |
|||
} |
|||
|
|||
private createLabel(text: string, fontWeight = '500'): Text { |
|||
return this.svgShape.text(text).font({ |
|||
family: 'Roboto', |
|||
weight: fontWeight, |
|||
style: 'normal', |
|||
size: '22px' |
|||
}).attr({x: '50%', y: '50%', 'text-anchor': 'middle', 'dominant-baseline': 'middle'}); |
|||
} |
|||
|
|||
} |
|||
|
|||
class InnerShadowCircle { |
|||
|
|||
private shadowCircle: Circle; |
|||
private blurEffect: Effect; |
|||
private offsetEffect: Effect; |
|||
private floodEffect: Effect; |
|||
|
|||
constructor(private svgShape: Svg, |
|||
private diameter: number, |
|||
private centerX: number, |
|||
private centerY: number, |
|||
private blur = 6, |
|||
private shadowOpacity = 0.6, |
|||
private dx = 0, |
|||
private dy = 0, |
|||
private shadowColor = '#000') { |
|||
|
|||
this.shadowCircle = this.svgShape.circle(this.diameter).center(this.centerX, this.centerY) |
|||
.fill({color: '#fff', opacity: 1}).stroke({width: 0}); |
|||
|
|||
this.shadowCircle.filterWith(add => { |
|||
add.x('-50%').y('-50%').width('200%').height('200%'); |
|||
let effect: Effect = add.componentTransfer(components => { |
|||
components.funcA({ type: 'table', tableValues: '1 0' }); |
|||
}).in(add.$fill); |
|||
effect = effect.gaussianBlur(this.blur, this.blur).attr({stdDeviation: this.blur}); |
|||
this.blurEffect = effect; |
|||
effect = effect.offset(this.dx, this.dy); |
|||
this.offsetEffect = effect; |
|||
effect = effect.flood(this.shadowColor, this.shadowOpacity); |
|||
this.floodEffect = effect; |
|||
effect = effect.composite(this.offsetEffect, 'in'); |
|||
effect.composite(add.$sourceAlpha, 'in'); |
|||
add.merge(m => { |
|||
m.mergeNode(add.$fill); |
|||
m.mergeNode(); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
public timeline(tl: Timeline): void { |
|||
this.blurEffect.timeline(tl); |
|||
this.offsetEffect.timeline(tl); |
|||
this.floodEffect.timeline(tl); |
|||
} |
|||
|
|||
public animate(blur: number, opacity: number, dx = 0, dy = 0): Runner { |
|||
powerButtonAnimation(this.blurEffect).attr({stdDeviation: blur}); |
|||
powerButtonAnimation(this.offsetEffect).attr({dx, dy}); |
|||
return powerButtonAnimation(this.floodEffect).attr({'flood-opacity': opacity}); |
|||
} |
|||
|
|||
public animateRestore(): Runner { |
|||
return this.animate(this.blur, this.shadowOpacity, this.dx, this.dy); |
|||
} |
|||
|
|||
public show(): void { |
|||
this.shadowCircle.show(); |
|||
} |
|||
|
|||
public hide(): void { |
|||
this.shadowCircle.hide(); |
|||
} |
|||
|
|||
} |
|||
|
|||
class DefaultPowerButtonShape extends PowerButtonShape { |
|||
|
|||
private outerBorder: Circle; |
|||
private outerBorderMask: Circle; |
|||
private offLabelShape: Text; |
|||
private onCircleShape: Circle; |
|||
private onLabelShape: Text; |
|||
private pressedShadow: InnerShadowCircle; |
|||
private pressedTimeline: Timeline; |
|||
private centerGroup: G; |
|||
|
|||
protected drawShape() { |
|||
this.outerBorder = this.svgShape.circle(powerButtonShapeSize).center(cx, cy) |
|||
.fill({opacity: 0}).stroke({width: 0}); |
|||
this.outerBorderMask = this.svgShape.circle(powerButtonShapeSize - 20).center(cx, cy); |
|||
this.createMask(this.outerBorder, [this.outerBorderMask]); |
|||
this.centerGroup = this.svgShape.group(); |
|||
this.offLabelShape = this.createOffLabel().addTo(this.centerGroup); |
|||
this.onCircleShape = this.svgShape.circle(powerButtonShapeSize - 20) |
|||
.center(cx, cy); |
|||
this.onLabelShape = this.createOnLabel(); |
|||
this.createMask(this.onCircleShape, [this.onLabelShape]); |
|||
this.pressedShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 20, cx, cy, 0, 0); |
|||
|
|||
this.pressedTimeline = new Timeline(); |
|||
this.centerGroup.timeline(this.pressedTimeline); |
|||
this.onLabelShape.timeline(this.pressedTimeline); |
|||
this.pressedShadow.timeline(this.pressedTimeline); |
|||
} |
|||
|
|||
protected drawColorState(mainColor: PowerButtonColor) { |
|||
this.outerBorder.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); |
|||
this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); |
|||
this.onCircleShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); |
|||
} |
|||
|
|||
protected drawOff() { |
|||
this.outerBorderMask.radius((powerButtonShapeSize - 20)/2); |
|||
this.onCircleShape.hide(); |
|||
this.centerGroup.show(); |
|||
} |
|||
|
|||
protected drawOn() { |
|||
this.outerBorderMask.radius((powerButtonShapeSize - 2)/2); |
|||
this.centerGroup.hide(); |
|||
this.onCircleShape.show(); |
|||
} |
|||
|
|||
protected onPressStart() { |
|||
this.pressedTimeline.finish(); |
|||
const pressedScale = 0.75; |
|||
powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); |
|||
powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale}); |
|||
this.pressedShadow.animate(6, 0.6); |
|||
} |
|||
|
|||
protected onPressEnd() { |
|||
this.pressedTimeline.finish(); |
|||
powerButtonAnimation(this.centerGroup).transform({scale: 1}); |
|||
powerButtonAnimation(this.onLabelShape).transform({scale: 1}); |
|||
this.pressedShadow.animateRestore(); |
|||
} |
|||
|
|||
} |
|||
|
|||
class SimplifiedPowerButtonShape extends PowerButtonShape { |
|||
|
|||
private outerBorder: Circle; |
|||
private outerBorderMask: Circle; |
|||
private onCircleShape: Circle; |
|||
private offLabelShape: Text; |
|||
private onLabelShape: Text; |
|||
private pressedShadow: InnerShadowCircle; |
|||
private pressedTimeline: Timeline; |
|||
private centerGroup: G; |
|||
|
|||
protected drawShape() { |
|||
this.outerBorder = this.svgShape.circle(powerButtonShapeSize).center(cx, cy) |
|||
.fill({opacity: 0}).stroke({width: 0}); |
|||
this.outerBorderMask = this.svgShape.circle(powerButtonShapeSize - 4).center(cx, cy); |
|||
this.createMask(this.outerBorder, [this.outerBorderMask]); |
|||
this.centerGroup = this.svgShape.group(); |
|||
this.offLabelShape = this.createOffLabel().addTo(this.centerGroup); |
|||
this.onCircleShape = this.svgShape.circle(powerButtonShapeSize).center(cx, cy); |
|||
this.onLabelShape = this.createOnLabel(); |
|||
this.createMask(this.onCircleShape, [this.onLabelShape]); |
|||
this.pressedShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 4, cx, cy, 0, 0); |
|||
|
|||
this.pressedTimeline = new Timeline(); |
|||
this.centerGroup.timeline(this.pressedTimeline); |
|||
this.onLabelShape.timeline(this.pressedTimeline); |
|||
this.pressedShadow.timeline(this.pressedTimeline); |
|||
} |
|||
|
|||
protected drawColorState(mainColor: PowerButtonColor) { |
|||
this.outerBorder.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); |
|||
this.onCircleShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); |
|||
this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); |
|||
} |
|||
|
|||
protected drawOff() { |
|||
this.onCircleShape.hide(); |
|||
this.outerBorder.show(); |
|||
this.centerGroup.show(); |
|||
} |
|||
|
|||
protected drawOn() { |
|||
this.centerGroup.hide(); |
|||
this.outerBorder.hide(); |
|||
this.onCircleShape.show(); |
|||
} |
|||
|
|||
protected onPressStart() { |
|||
this.pressedTimeline.finish(); |
|||
const pressedScale = 0.75; |
|||
powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); |
|||
powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale}); |
|||
this.pressedShadow.animate(6, 0.6); |
|||
} |
|||
|
|||
protected onPressEnd() { |
|||
this.pressedTimeline.finish(); |
|||
powerButtonAnimation(this.centerGroup).transform({scale: 1}); |
|||
powerButtonAnimation(this.onLabelShape).transform({scale: 1}); |
|||
this.pressedShadow.animateRestore(); |
|||
} |
|||
} |
|||
|
|||
class OutlinedPowerButtonShape extends PowerButtonShape { |
|||
private outerBorder: Circle; |
|||
private outerBorderMask: Circle; |
|||
private innerBorder: Circle; |
|||
private innerBorderMask: Circle; |
|||
private offLabelShape: Text; |
|||
private onCircleShape: Circle; |
|||
private onLabelShape: Text; |
|||
private pressedShadow: InnerShadowCircle; |
|||
private pressedTimeline: Timeline; |
|||
private centerGroup: G; |
|||
private onCenterGroup: G; |
|||
|
|||
protected drawShape() { |
|||
this.outerBorder = this.svgShape.circle(powerButtonShapeSize).center(cx, cy) |
|||
.fill({opacity: 0}).stroke({width: 0}); |
|||
this.outerBorderMask = this.svgShape.circle(powerButtonShapeSize - 2).center(cx, cy); |
|||
this.createMask(this.outerBorder, [this.outerBorderMask]); |
|||
this.innerBorder = this.svgShape.circle(powerButtonShapeSize - 20).center(cx, cy) |
|||
.fill({opacity: 0}).stroke({width: 0}); |
|||
this.innerBorderMask = this.svgShape.circle(powerButtonShapeSize - 24).center(cx, cy); |
|||
this.createMask(this.innerBorder, [this.innerBorderMask]); |
|||
this.centerGroup = this.svgShape.group(); |
|||
this.offLabelShape = this.createOffLabel().addTo(this.centerGroup); |
|||
this.onCenterGroup = this.svgShape.group(); |
|||
this.onCircleShape = this.svgShape.circle(powerButtonShapeSize - 28).center(cx, cy) |
|||
.addTo(this.onCenterGroup); |
|||
this.onLabelShape = this.createOnLabel(); |
|||
this.createMask(this.onCircleShape, [this.onLabelShape]); |
|||
this.pressedShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 24, cx, cy, 0, 0); |
|||
|
|||
this.pressedTimeline = new Timeline(); |
|||
this.centerGroup.timeline(this.pressedTimeline); |
|||
this.onCenterGroup.timeline(this.pressedTimeline); |
|||
this.onLabelShape.timeline(this.pressedTimeline); |
|||
this.pressedShadow.timeline(this.pressedTimeline); |
|||
} |
|||
|
|||
protected drawColorState(mainColor: PowerButtonColor) { |
|||
this.outerBorder.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); |
|||
this.innerBorder.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); |
|||
this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); |
|||
this.onCircleShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); |
|||
} |
|||
|
|||
protected drawOff() { |
|||
this.onCenterGroup.hide(); |
|||
this.centerGroup.show(); |
|||
} |
|||
|
|||
protected drawOn() { |
|||
this.centerGroup.hide(); |
|||
this.onCenterGroup.show(); |
|||
} |
|||
|
|||
protected onPressStart() { |
|||
this.pressedTimeline.finish(); |
|||
const pressedScale = 0.75; |
|||
powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); |
|||
powerButtonAnimation(this.onCenterGroup).transform({scale: 0.98}); |
|||
powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale / 0.98}); |
|||
this.pressedShadow.animate(6, 0.6); |
|||
} |
|||
|
|||
protected onPressEnd() { |
|||
this.pressedTimeline.finish(); |
|||
powerButtonAnimation(this.centerGroup).transform({scale: 1}); |
|||
powerButtonAnimation(this.onCenterGroup).transform({scale: 1}); |
|||
powerButtonAnimation(this.onLabelShape).transform({scale: 1}); |
|||
this.pressedShadow.animateRestore(); |
|||
} |
|||
} |
|||
|
|||
class DefaultVolumePowerButtonShape extends PowerButtonShape { |
|||
private outerBorder: Circle; |
|||
private outerBorderMask: Circle; |
|||
private outerBorderGradient: Gradient; |
|||
private innerBorder: Circle; |
|||
private innerBorderMask: Circle; |
|||
private innerBorderGradient: Gradient; |
|||
private innerShadow: InnerShadowCircle; |
|||
//private innerShadowGradient: Gradient;
|
|||
//private innerShadowGradientStop: Stop;
|
|||
private offLabelShape: Text; |
|||
private onCircleShape: Circle; |
|||
private onLabelShape: Text; |
|||
private pressedTimeline: Timeline; |
|||
private centerGroup: G; |
|||
|
|||
protected drawShape() { |
|||
this.outerBorder = this.svgShape.circle(powerButtonShapeSize).center(cx, cy) |
|||
.fill({opacity: 0}).stroke({width: 0}); |
|||
this.outerBorderMask = this.svgShape.circle(powerButtonShapeSize - 20).center(cx, cy); |
|||
this.createMask(this.outerBorder, [this.outerBorderMask]); |
|||
this.outerBorderGradient = this.svgShape.gradient('linear', (add) => { |
|||
add.stop(0, '#CCCCCC', 1); |
|||
add.stop(1, '#FFFFFF', 1); |
|||
}).from(0.268, 0.92).to(0.832, 0.1188); |
|||
this.innerBorder = this.svgShape.circle(powerButtonShapeSize - 20).center(cx, cy) |
|||
.fill({opacity: 0}).stroke({width: 0}); |
|||
this.innerBorderMask = this.svgShape.circle(powerButtonShapeSize - 24).center(cx, cy); |
|||
this.createMask(this.innerBorder, [this.innerBorderMask]); |
|||
this.innerBorderGradient = this.svgShape.gradient('linear', (add) => { |
|||
add.stop(0, '#CCCCCC', 1); |
|||
add.stop(1, '#FFFFFF', 1); |
|||
}).from(0.832, 0.1188).to(0.268, 0.92); |
|||
this.centerGroup = this.svgShape.group(); |
|||
this.offLabelShape = this.createOffLabel('400').addTo(this.centerGroup); |
|||
this.onCircleShape = this.svgShape.circle(powerButtonShapeSize - 24).center(cx, cy); |
|||
this.onLabelShape = this.createOnLabel('400'); |
|||
this.createMask(this.onCircleShape, [this.onLabelShape]); |
|||
this.innerShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 24, cx, cy, 3, 0.3); |
|||
|
|||
this.pressedTimeline = new Timeline(); |
|||
this.centerGroup.timeline(this.pressedTimeline); |
|||
this.onLabelShape.timeline(this.pressedTimeline); |
|||
this.innerShadow.timeline(this.pressedTimeline); |
|||
} |
|||
|
|||
protected drawColorState(mainColor: PowerButtonColor){ |
|||
if (this.disabled) { |
|||
this.backgroundShape.removeClass('tb-small-shadow'); |
|||
if (!this.forcePressed) { |
|||
this.innerShadow.hide(); |
|||
} |
|||
this.outerBorder.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); |
|||
this.innerBorder.attr({fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); |
|||
} else { |
|||
this.backgroundShape.addClass('tb-small-shadow'); |
|||
this.innerShadow.show(); |
|||
this.outerBorder.fill(this.outerBorderGradient); |
|||
this.outerBorder.attr({ 'fill-opacity': 1 }); |
|||
this.innerBorder.fill(this.innerBorderGradient); |
|||
this.innerBorder.attr({ 'fill-opacity': 1 }); |
|||
} |
|||
this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); |
|||
this.onCircleShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); |
|||
} |
|||
|
|||
protected drawOff() { |
|||
this.onCircleShape.hide(); |
|||
this.centerGroup.show(); |
|||
this.innerBorder.show(); |
|||
} |
|||
|
|||
protected drawOn() { |
|||
if (this.disabled) { |
|||
this.innerBorder.hide(); |
|||
} else { |
|||
this.innerBorder.show(); |
|||
} |
|||
this.centerGroup.hide(); |
|||
this.onCircleShape.show(); |
|||
} |
|||
|
|||
protected onPressStart() { |
|||
this.pressedTimeline.finish(); |
|||
this.innerShadow.show(); |
|||
const pressedScale = 0.75; |
|||
powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); |
|||
powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale}); |
|||
this.innerShadow.animate(6, 0.6); |
|||
} |
|||
|
|||
protected onPressEnd() { |
|||
this.pressedTimeline.finish(); |
|||
powerButtonAnimation(this.centerGroup).transform({scale: 1}); |
|||
powerButtonAnimation(this.onLabelShape).transform({scale: 1}); |
|||
this.innerShadow.animateRestore().after(() => { |
|||
if (this.disabled) { |
|||
this.innerShadow.hide(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
} |
|||
|
|||
class SimplifiedVolumePowerButtonShape extends PowerButtonShape { |
|||
|
|||
private outerBorder: Circle; |
|||
private outerBorderMask: Circle; |
|||
private offLabelShape: Text; |
|||
private onLabelShape: Text; |
|||
private innerShadow: InnerShadowCircle; |
|||
private pressedShadow: InnerShadowCircle; |
|||
private pressedTimeline: Timeline; |
|||
private centerGroup: G; |
|||
private onCenterGroup: G; |
|||
|
|||
|
|||
protected drawShape() { |
|||
this.outerBorder = this.svgShape.circle(powerButtonShapeSize).center(cx, cy) |
|||
.fill({color: '#FAFAFA', opacity: 1}).stroke({width: 0}); |
|||
this.outerBorderMask = this.svgShape.circle(powerButtonShapeSize - 4).center(cx, cy); |
|||
this.createMask(this.outerBorder, [this.outerBorderMask]); |
|||
this.centerGroup = this.svgShape.group(); |
|||
this.offLabelShape = this.createOffLabel().addTo(this.centerGroup); |
|||
this.onCenterGroup = this.svgShape.group(); |
|||
this.onLabelShape = this.createOnLabel().addTo(this.onCenterGroup); |
|||
this.innerShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 4, cx, cy, 3, 0.3); |
|||
this.pressedShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 4, cx, cy, 0, 0); |
|||
this.pressedTimeline = new Timeline(); |
|||
this.centerGroup.timeline(this.pressedTimeline); |
|||
this.onCenterGroup.timeline(this.pressedTimeline); |
|||
this.pressedShadow.timeline(this.pressedTimeline); |
|||
} |
|||
|
|||
protected drawColorState(mainColor: PowerButtonColor){ |
|||
this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); |
|||
this.onLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); |
|||
} |
|||
|
|||
protected drawOff() { |
|||
if (!this.pressed) { |
|||
this.backgroundShape.addClass('tb-shadow'); |
|||
} |
|||
this.innerShadow.hide(); |
|||
this.onCenterGroup.hide(); |
|||
this.centerGroup.show(); |
|||
} |
|||
|
|||
protected drawOn() { |
|||
this.backgroundShape.removeClass('tb-shadow'); |
|||
this.centerGroup.hide(); |
|||
this.onCenterGroup.show(); |
|||
this.innerShadow.show(); |
|||
} |
|||
|
|||
protected onPressStart() { |
|||
this.pressedTimeline.finish(); |
|||
const pressedScale = 0.75; |
|||
if (!this.value) { |
|||
this.backgroundShape.removeClass('tb-shadow'); |
|||
} |
|||
powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); |
|||
powerButtonAnimation(this.onCenterGroup).transform({scale: pressedScale}); |
|||
this.pressedShadow.animate(8, 0.4); |
|||
} |
|||
|
|||
protected onPressEnd() { |
|||
this.pressedTimeline.finish(); |
|||
powerButtonAnimation(this.centerGroup).transform({scale: 1}); |
|||
powerButtonAnimation(this.onCenterGroup).transform({scale: 1}); |
|||
this.pressedShadow.animateRestore().after(() => { |
|||
if (!this.value) { |
|||
this.backgroundShape.addClass('tb-shadow'); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
|
|||
class OutlinedVolumePowerButtonShape extends PowerButtonShape { |
|||
private outerBorder: Circle; |
|||
private outerBorderMask: Circle; |
|||
private outerBorderGradient: Gradient; |
|||
private innerBorder: Circle; |
|||
private innerBorderMask: Circle; |
|||
private offLabelShape: Text; |
|||
private onCircleShape: Circle; |
|||
private onLabelShape: Text; |
|||
private pressedShadow: InnerShadowCircle; |
|||
private pressedTimeline: Timeline; |
|||
private centerGroup: G; |
|||
private onCenterGroup: G; |
|||
|
|||
protected drawShape() { |
|||
this.outerBorder = this.svgShape.circle(powerButtonShapeSize).center(cx, cy) |
|||
.fill({opacity: 0}).stroke({width: 0}); |
|||
this.outerBorderMask = this.svgShape.circle(powerButtonShapeSize - 20).center(cx, cy); |
|||
this.createMask(this.outerBorder, [this.outerBorderMask]); |
|||
this.outerBorderGradient = this.svgShape.gradient('linear', (add) => { |
|||
add.stop(0, '#CCCCCC', 1); |
|||
add.stop(1, '#FFFFFF', 1); |
|||
}).from(0.268, 0.92).to(0.832, 0.1188); |
|||
this.innerBorder = this.svgShape.circle(powerButtonShapeSize - 20).center(cx, cy) |
|||
.fill({opacity: 0}).stroke({width: 0}); |
|||
this.innerBorderMask = this.svgShape.circle(powerButtonShapeSize - 30).center(cx, cy); |
|||
this.createMask(this.innerBorder, [this.innerBorderMask]); |
|||
this.centerGroup = this.svgShape.group(); |
|||
this.offLabelShape = this.createOffLabel('800').addTo(this.centerGroup); |
|||
this.onCenterGroup = this.svgShape.group(); |
|||
this.onCircleShape = this.svgShape.circle(powerButtonShapeSize - 30).center(cx, cy) |
|||
.addTo(this.onCenterGroup); |
|||
this.onLabelShape = this.createOnLabel('800'); |
|||
this.createMask(this.onCircleShape, [this.onLabelShape]); |
|||
this.pressedShadow = new InnerShadowCircle(this.svgShape, powerButtonShapeSize - 30, cx, cy, 0, 0); |
|||
this.backgroundShape.addClass('tb-small-shadow'); |
|||
|
|||
this.pressedTimeline = new Timeline(); |
|||
this.centerGroup.timeline(this.pressedTimeline); |
|||
this.onCenterGroup.timeline(this.pressedTimeline); |
|||
this.onLabelShape.timeline(this.pressedTimeline); |
|||
this.pressedShadow.timeline(this.pressedTimeline); |
|||
} |
|||
|
|||
protected drawColorState(mainColor: PowerButtonColor){ |
|||
if (this.disabled) { |
|||
this.outerBorder.attr({ fill: '#000000', 'fill-opacity': 0.03}); |
|||
} else { |
|||
this.outerBorder.fill(this.outerBorderGradient); |
|||
this.outerBorder.attr({ 'fill-opacity': 1 }); |
|||
} |
|||
this.innerBorder.attr({fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); |
|||
this.offLabelShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); |
|||
this.onCircleShape.attr({ fill: mainColor.hex, 'fill-opacity': mainColor.opacity}); |
|||
} |
|||
|
|||
protected drawOff() { |
|||
this.onCenterGroup.hide(); |
|||
this.centerGroup.show(); |
|||
this.innerBorder.show(); |
|||
} |
|||
|
|||
protected drawOn() { |
|||
this.innerBorder.hide(); |
|||
this.centerGroup.hide(); |
|||
this.onCenterGroup.show(); |
|||
} |
|||
|
|||
protected onPressStart() { |
|||
this.pressedTimeline.finish(); |
|||
const pressedScale = 0.75; |
|||
powerButtonAnimation(this.centerGroup).transform({scale: pressedScale}); |
|||
powerButtonAnimation(this.onCenterGroup).transform({scale: 0.98}); |
|||
powerButtonAnimation(this.onLabelShape).transform({scale: pressedScale / 0.98}); |
|||
this.pressedShadow.animate(6, 0.6); |
|||
} |
|||
|
|||
protected onPressEnd() { |
|||
this.pressedTimeline.finish(); |
|||
powerButtonAnimation(this.centerGroup).transform({scale: 1}); |
|||
powerButtonAnimation(this.onCenterGroup).transform({scale: 1}); |
|||
powerButtonAnimation(this.onLabelShape).transform({scale: 1}); |
|||
this.pressedShadow.animateRestore(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,142 @@ |
|||
<!-- |
|||
|
|||
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]="powerButtonWidgetSettingsForm"> |
|||
<div class="tb-form-panel"> |
|||
<div class="tb-form-panel-title" translate>widgets.power-button.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 space-between"> |
|||
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.power-button.power-on-hint' | translate}}" translate>widgets.power-button.power-on</div> |
|||
<tb-set-value-action-settings fxFlex |
|||
panelTitle="widgets.power-button.power-on " |
|||
[aliasController]="aliasController" |
|||
[targetDevice]="targetDevice" |
|||
[widgetType]="widgetType" |
|||
formControlName="onUpdateState"></tb-set-value-action-settings> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.power-button.power-off-hint' | translate}}" translate>widgets.power-button.power-off</div> |
|||
<tb-set-value-action-settings fxFlex |
|||
panelTitle="widgets.power-button.power-off" |
|||
[aliasController]="aliasController" |
|||
[targetDevice]="targetDevice" |
|||
[widgetType]="widgetType" |
|||
formControlName="offUpdateState"></tb-set-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.card-style</div> |
|||
<tb-image-cards-select rowHeight="1:1" |
|||
[cols]="{columns: 3, |
|||
breakpoints: { |
|||
'lt-sm': 1, |
|||
'lt-md': 2 |
|||
}}" |
|||
label="{{ 'widgets.power-button.layout' | translate }}" formControlName="layout"> |
|||
<tb-image-cards-select-option *ngFor="let layout of powerButtonLayouts" |
|||
[value]="layout" |
|||
[image]="powerButtonLayoutImageMap.get(layout)"> |
|||
{{ powerButtonLayoutTranslationMap.get(layout) | translate }} |
|||
</tb-image-cards-select-option> |
|||
</tb-image-cards-select> |
|||
<div class="tb-form-row space-between"> |
|||
<div>{{ 'widgets.background.background' | translate }}</div> |
|||
<tb-background-settings formControlName="background"> |
|||
</tb-background-settings> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-panel"> |
|||
<div class="tb-form-panel-title" translate>widgets.power-button.button</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div>{{ 'widgets.power-button.power-on-colors' | translate }}</div> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="12px"> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<div translate>widgets.power-button.main</div> |
|||
<tb-color-input asBoxInput |
|||
formControlName="mainColorOn"> |
|||
</tb-color-input> |
|||
</div> |
|||
<mat-divider vertical></mat-divider> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<div translate>widgets.power-button.background</div> |
|||
<tb-color-input asBoxInput |
|||
formControlName="backgroundColorOn"> |
|||
</tb-color-input> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div>{{ 'widgets.power-button.power-off-colors' | translate }}</div> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="12px"> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<div translate>widgets.power-button.main</div> |
|||
<tb-color-input asBoxInput |
|||
formControlName="mainColorOff"> |
|||
</tb-color-input> |
|||
</div> |
|||
<mat-divider vertical></mat-divider> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<div translate>widgets.power-button.background</div> |
|||
<tb-color-input asBoxInput |
|||
formControlName="backgroundColorOff"> |
|||
</tb-color-input> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div>{{ 'widgets.power-button.disabled-colors' | translate }}</div> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="12px"> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<div translate>widgets.power-button.main</div> |
|||
<tb-color-input asBoxInput |
|||
formControlName="mainColorDisabled"> |
|||
</tb-color-input> |
|||
</div> |
|||
<mat-divider vertical></mat-divider> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<div translate>widgets.power-button.background</div> |
|||
<tb-color-input asBoxInput |
|||
formControlName="backgroundColorDisabled"> |
|||
</tb-color-input> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</ng-container> |
|||
@ -0,0 +1,88 @@ |
|||
///
|
|||
/// 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 { 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 { ValueType } from '@shared/models/constants'; |
|||
import { |
|||
powerButtonDefaultSettings, |
|||
powerButtonLayoutImages, |
|||
powerButtonLayouts, |
|||
powerButtonLayoutTranslations |
|||
} from '@home/components/widget/lib/rpc/power-button-widget.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-power-button-widget-settings', |
|||
templateUrl: './power-button-widget-settings.component.html', |
|||
styleUrls: ['./../widget-settings.scss'] |
|||
}) |
|||
export class PowerButtonWidgetSettingsComponent extends WidgetSettingsComponent { |
|||
|
|||
get targetDevice(): TargetDevice { |
|||
return this.widgetConfig?.config?.targetDevice; |
|||
} |
|||
|
|||
get widgetType(): widgetType { |
|||
return this.widgetConfig?.widgetType; |
|||
} |
|||
|
|||
powerButtonLayouts = powerButtonLayouts; |
|||
|
|||
powerButtonLayoutTranslationMap = powerButtonLayoutTranslations; |
|||
powerButtonLayoutImageMap = powerButtonLayoutImages; |
|||
|
|||
valueType = ValueType; |
|||
|
|||
powerButtonWidgetSettingsForm: UntypedFormGroup; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
private fb: UntypedFormBuilder) { |
|||
super(store); |
|||
} |
|||
|
|||
protected settingsForm(): UntypedFormGroup { |
|||
return this.powerButtonWidgetSettingsForm; |
|||
} |
|||
|
|||
protected defaultSettings(): WidgetSettings { |
|||
return {...powerButtonDefaultSettings}; |
|||
} |
|||
|
|||
protected onSettingsSet(settings: WidgetSettings) { |
|||
this.powerButtonWidgetSettingsForm = this.fb.group({ |
|||
initialState: [settings.initialState, []], |
|||
onUpdateState: [settings.onUpdateState, []], |
|||
offUpdateState: [settings.offUpdateState, []], |
|||
disabledState: [settings.disabledState, []], |
|||
|
|||
layout: [settings.layout, []], |
|||
|
|||
mainColorOn: [settings.mainColorOn, []], |
|||
backgroundColorOn: [settings.backgroundColorOn, []], |
|||
|
|||
mainColorOff: [settings.mainColorOff, []], |
|||
backgroundColorOff: [settings.backgroundColorOff, []], |
|||
|
|||
mainColorDisabled: [settings.mainColorDisabled, []], |
|||
backgroundColorDisabled: [settings.backgroundColorDisabled, []], |
|||
|
|||
background: [settings.background, []] |
|||
}); |
|||
} |
|||
} |
|||
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
Loading…
Reference in new issue