committed by
GitHub
99 changed files with 3059 additions and 283 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,77 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2024 The Thingsboard Authors |
|||
|
|||
Licensed under the Apache License, Version 2.0 (the "License"); |
|||
you may not use this file except in compliance with the License. |
|||
You may obtain a copy of the License at |
|||
|
|||
http://www.apache.org/licenses/LICENSE-2.0 |
|||
|
|||
Unless required by applicable law or agreed to in writing, software |
|||
distributed under the License is distributed on an "AS IS" BASIS, |
|||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
See the License for the specific language governing permissions and |
|||
limitations under the License. |
|||
|
|||
--> |
|||
<ng-container [formGroup]="actionButtonWidgetConfigForm"> |
|||
<tb-datasources |
|||
*ngIf="!widgetEditMode" |
|||
[configMode]="basicMode" |
|||
hideDatasourceLabel |
|||
hideDataKeys |
|||
forceSingleDatasource |
|||
formControlName="datasources"> |
|||
</tb-datasources> |
|||
<div class="tb-form-panel"> |
|||
<div class="tb-form-panel-title" translate>widgets.action-button.behavior</div> |
|||
<div class="tb-form-row"> |
|||
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.action-button.on-click-hint' | translate}}" translate>widgets.action-button.on-click</div> |
|||
<tb-widget-action-settings fxFlex |
|||
panelTitle="widgets.action-button.on-click" |
|||
[callbacks]="callbacks" |
|||
[widgetType]="widgetType" |
|||
formControlName="onClickAction"> |
|||
</tb-widget-action-settings> |
|||
</div> |
|||
<div class="tb-form-row"> |
|||
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.button-state.activated-state-hint' | translate}}" translate>widgets.button-state.activated-state</div> |
|||
<tb-get-value-action-settings fxFlex |
|||
panelTitle="widgets.button-state.activated-state" |
|||
[valueType]="valueType.BOOLEAN" |
|||
stateLabel="widgets.button-state.activated" |
|||
[aliasController]="aliasController" |
|||
[targetDevice]="targetDevice" |
|||
[widgetType]="widgetType" |
|||
formControlName="activatedState"></tb-get-value-action-settings> |
|||
</div> |
|||
<div class="tb-form-row"> |
|||
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.button-state.disabled-state-hint' | translate}}" translate>widgets.button-state.disabled-state</div> |
|||
<tb-get-value-action-settings fxFlex |
|||
panelTitle="widgets.button-state.disabled-state" |
|||
[valueType]="valueType.BOOLEAN" |
|||
stateLabel="widgets.button-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-widget-button-appearance |
|||
[borderRadius]="actionButtonWidgetConfigForm.get('borderRadius').value" |
|||
formControlName="appearance"> |
|||
</tb-widget-button-appearance> |
|||
</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>{{ '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> |
|||
</ng-container> |
|||
@ -0,0 +1,139 @@ |
|||
///
|
|||
/// 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 { |
|||
actionDescriptorToAction, |
|||
Datasource, |
|||
defaultWidgetAction, |
|||
TargetDevice, |
|||
WidgetAction, |
|||
WidgetConfig, |
|||
} from '@shared/models/widget.models'; |
|||
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; |
|||
import { guid } from '@core/utils'; |
|||
import { ValueType } from '@shared/models/constants'; |
|||
import { getTargetDeviceFromDatasources } from '@shared/models/widget-settings.models'; |
|||
import { |
|||
actionButtonDefaultSettings, |
|||
ActionButtonWidgetSettings |
|||
} from '@home/components/widget/lib/button/action-button-widget.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-action-button-basic-config', |
|||
templateUrl: './action-button-basic-config.component.html', |
|||
styleUrls: ['../basic-config.scss'] |
|||
}) |
|||
export class ActionButtonBasicConfigComponent extends BasicWidgetConfigComponent { |
|||
|
|||
get targetDevice(): TargetDevice { |
|||
const datasources: Datasource[] = this.actionButtonWidgetConfigForm.get('datasources').value; |
|||
return getTargetDeviceFromDatasources(datasources); |
|||
} |
|||
|
|||
valueType = ValueType; |
|||
|
|||
actionButtonWidgetConfigForm: UntypedFormGroup; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
protected widgetConfigComponent: WidgetConfigComponent, |
|||
private fb: UntypedFormBuilder) { |
|||
super(store, widgetConfigComponent); |
|||
} |
|||
|
|||
protected configForm(): UntypedFormGroup { |
|||
return this.actionButtonWidgetConfigForm; |
|||
} |
|||
|
|||
protected onConfigSet(configData: WidgetConfigComponentData) { |
|||
const settings: ActionButtonWidgetSettings = {...actionButtonDefaultSettings, ...(configData.config.settings || {})}; |
|||
const onClickAction = this.getOnClickAction(configData.config); |
|||
this.actionButtonWidgetConfigForm = this.fb.group({ |
|||
datasources: [configData.config.datasources, []], |
|||
|
|||
onClickAction: [onClickAction, []], |
|||
activatedState: [settings.activatedState, []], |
|||
disabledState: [settings.disabledState, []], |
|||
|
|||
appearance: [settings.appearance, []], |
|||
|
|||
borderRadius: [configData.config.borderRadius, []] |
|||
}); |
|||
} |
|||
|
|||
protected prepareOutputConfig(config: any): WidgetConfigComponentData { |
|||
|
|||
this.widgetConfig.config.datasources = config.datasources; |
|||
this.setOnClickAction(this.widgetConfig.config, config.onClickAction); |
|||
|
|||
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {}; |
|||
|
|||
this.widgetConfig.config.settings.activatedState = config.activatedState; |
|||
this.widgetConfig.config.settings.disabledState = config.disabledState; |
|||
|
|||
this.widgetConfig.config.settings.appearance = config.appearance; |
|||
|
|||
this.widgetConfig.config.borderRadius = config.borderRadius; |
|||
|
|||
return this.widgetConfig; |
|||
} |
|||
|
|||
private getOnClickAction(config: WidgetConfig): WidgetAction { |
|||
let clickAction: WidgetAction; |
|||
const actions = config.actions; |
|||
if (actions && actions.click) { |
|||
const descriptors = actions.click; |
|||
if (descriptors?.length) { |
|||
const descriptor = descriptors[0]; |
|||
clickAction = actionDescriptorToAction(descriptor); |
|||
} |
|||
} |
|||
if (!clickAction) { |
|||
clickAction = defaultWidgetAction(); |
|||
} |
|||
return clickAction; |
|||
} |
|||
|
|||
private setOnClickAction(config: WidgetConfig, clickAction: WidgetAction): void { |
|||
let actions = config.actions; |
|||
if (!actions) { |
|||
actions = {}; |
|||
config.actions = actions; |
|||
} |
|||
let descriptors = actions.click; |
|||
if (!descriptors) { |
|||
descriptors = []; |
|||
actions.click = descriptors; |
|||
} |
|||
let descriptor = descriptors[0]; |
|||
if (!descriptor) { |
|||
descriptor = { |
|||
id: guid(), |
|||
name: 'onClick', |
|||
icon: 'more_horiz', |
|||
...clickAction |
|||
}; |
|||
descriptors[0] = descriptor; |
|||
} else { |
|||
descriptors[0] = {...descriptor, ...clickAction}; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
/** |
|||
* 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-action-widget-error-container { |
|||
position: absolute; |
|||
bottom: 0; |
|||
left: 0; |
|||
right: 0; |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
align-items: center; |
|||
z-index: 1; |
|||
.tb-action-widget-error-panel { |
|||
display: flex; |
|||
padding: 4px 4px 4px 12px; |
|||
justify-content: center; |
|||
align-items: center; |
|||
gap: 4px; |
|||
border-radius: 4px; |
|||
background-color: #fff2f3; |
|||
box-shadow: -2px 2px 4px 0px rgba(0,0,0,0.2); |
|||
.tb-action-widget-error-text { |
|||
font-size: 12px; |
|||
font-style: normal; |
|||
font-weight: 400; |
|||
line-height: 16px; |
|||
color: rgba(209, 39, 48, 1); |
|||
} |
|||
.tb-action-widget-error-clear { |
|||
color: rgba(209, 39, 48, 1); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
<!-- |
|||
|
|||
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-action-button-widget" [style.pointer-events]="ctx.isEdit ? 'none' : 'all'"> |
|||
<div class="tb-action-button-widget-title-panel"> |
|||
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container> |
|||
</div> |
|||
<tb-widget-button |
|||
[appearance]="appearance" |
|||
[borderRadius]="borderRadius" |
|||
[disabled]="disabled" |
|||
[activated]="activated" |
|||
(clicked)="onClick($event)"> |
|||
</tb-widget-button> |
|||
<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,28 @@ |
|||
/** |
|||
* 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-action-button-widget { |
|||
width: 100%; |
|||
height: 100%; |
|||
position: relative; |
|||
|
|||
> div.tb-action-button-widget-title-panel { |
|||
position: absolute; |
|||
top: 12px; |
|||
left: 12px; |
|||
right: 12px; |
|||
z-index: 2; |
|||
} |
|||
} |
|||
@ -0,0 +1,100 @@ |
|||
///
|
|||
/// 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, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; |
|||
import { BasicActionWidgetComponent } from '@home/components/widget/lib/action/action-widget.models'; |
|||
import { ImagePipe } from '@shared/pipe/image.pipe'; |
|||
import { DomSanitizer } from '@angular/platform-browser'; |
|||
import { ValueType } from '@shared/models/constants'; |
|||
import { |
|||
actionButtonDefaultSettings, |
|||
ActionButtonWidgetSettings |
|||
} from '@home/components/widget/lib/button/action-button-widget.models'; |
|||
import { WidgetButtonAppearance } from '@shared/components/button/widget-button.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-action-button-widget', |
|||
templateUrl: './action-button-widget.component.html', |
|||
styleUrls: ['../action/action-widget.scss', './action-button-widget.component.scss'], |
|||
encapsulation: ViewEncapsulation.None |
|||
}) |
|||
export class ActionButtonWidgetComponent extends |
|||
BasicActionWidgetComponent implements OnInit, AfterViewInit, OnDestroy { |
|||
|
|||
settings: ActionButtonWidgetSettings; |
|||
|
|||
disabled = false; |
|||
activated = false; |
|||
|
|||
appearance: WidgetButtonAppearance; |
|||
borderRadius = '4px'; |
|||
|
|||
constructor(protected imagePipe: ImagePipe, |
|||
protected sanitizer: DomSanitizer, |
|||
protected cd: ChangeDetectorRef) { |
|||
super(cd); |
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
super.ngOnInit(); |
|||
this.settings = {...actionButtonDefaultSettings, ...this.ctx.settings}; |
|||
|
|||
this.appearance = this.settings.appearance; |
|||
|
|||
const activatedStateSettings = |
|||
{...this.settings.activatedState, actionLabel: this.ctx.translate.instant('widgets.button-state.activated-state')}; |
|||
this.createValueGetter(activatedStateSettings, ValueType.BOOLEAN, { |
|||
next: (value) => this.onActivated(value) |
|||
}); |
|||
|
|||
const disabledStateSettings = |
|||
{...this.settings.disabledState, actionLabel: this.ctx.translate.instant('widgets.button-state.disabled-state')}; |
|||
this.createValueGetter(disabledStateSettings, ValueType.BOOLEAN, { |
|||
next: (value) => this.onDisabled(value) |
|||
}); |
|||
} |
|||
|
|||
ngAfterViewInit(): void { |
|||
super.ngAfterViewInit(); |
|||
} |
|||
|
|||
ngOnDestroy() { |
|||
super.ngOnDestroy(); |
|||
} |
|||
|
|||
public onInit() { |
|||
super.onInit(); |
|||
this.borderRadius = this.ctx.$widgetElement.css('borderRadius'); |
|||
this.cd.detectChanges(); |
|||
} |
|||
|
|||
public onClick($event: MouseEvent) { |
|||
if (!this.ctx.isEdit && !this.ctx.isPreview) { |
|||
this.ctx.actionsApi.click($event); |
|||
} |
|||
} |
|||
|
|||
private onActivated(value: boolean): void { |
|||
this.activated = !!value; |
|||
this.cd.markForCheck(); |
|||
} |
|||
|
|||
private onDisabled(value: boolean): void { |
|||
this.disabled = !!value; |
|||
this.cd.markForCheck(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
///
|
|||
/// 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 { |
|||
WidgetButtonAppearance, |
|||
widgetButtonDefaultAppearance |
|||
} from '@shared/components/button/widget-button.models'; |
|||
import { DataToValueType, GetValueAction, GetValueSettings } from '@shared/models/action-widget-settings.models'; |
|||
|
|||
export interface ActionButtonWidgetSettings { |
|||
appearance: WidgetButtonAppearance; |
|||
activatedState: GetValueSettings<boolean>; |
|||
disabledState: GetValueSettings<boolean>; |
|||
} |
|||
|
|||
export const actionButtonDefaultSettings: ActionButtonWidgetSettings = { |
|||
appearance: widgetButtonDefaultAppearance, |
|||
activatedState: { |
|||
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;' |
|||
} |
|||
}, |
|||
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;' |
|||
} |
|||
} |
|||
}; |
|||
@ -0,0 +1,51 @@ |
|||
<!-- |
|||
|
|||
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]="actionButtonWidgetSettingsForm"> |
|||
<div class="tb-form-panel"> |
|||
<div class="tb-form-panel-title" translate>widgets.action-button.behavior</div> |
|||
<div class="tb-form-row"> |
|||
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.button-state.activated-state-hint' | translate}}" translate>widgets.button-state.activated-state</div> |
|||
<tb-get-value-action-settings fxFlex |
|||
panelTitle="widgets.button-state.activated-state" |
|||
[valueType]="valueType.BOOLEAN" |
|||
stateLabel="widgets.button-state.activated" |
|||
[aliasController]="aliasController" |
|||
[targetDevice]="targetDevice" |
|||
[widgetType]="widgetType" |
|||
formControlName="activatedState"></tb-get-value-action-settings> |
|||
</div> |
|||
<div class="tb-form-row"> |
|||
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.button-state.disabled-state-hint' | translate}}" translate>widgets.button-state.disabled-state</div> |
|||
<tb-get-value-action-settings fxFlex |
|||
panelTitle="widgets.button-state.disabled-state" |
|||
[valueType]="valueType.BOOLEAN" |
|||
stateLabel="widgets.button-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-widget-button-appearance |
|||
[borderRadius]="borderRadius" |
|||
formControlName="appearance"> |
|||
</tb-widget-button-appearance> |
|||
</div> |
|||
</ng-container> |
|||
@ -0,0 +1,70 @@ |
|||
///
|
|||
/// 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 { getTargetDeviceFromDatasources } from '@shared/models/widget-settings.models'; |
|||
import { actionButtonDefaultSettings } from '@home/components/widget/lib/button/action-button-widget.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-action-button-widget-settings', |
|||
templateUrl: './action-button-widget-settings.component.html', |
|||
styleUrls: ['./../widget-settings.scss'] |
|||
}) |
|||
export class ActionButtonWidgetSettingsComponent extends WidgetSettingsComponent { |
|||
|
|||
get targetDevice(): TargetDevice { |
|||
const datasources = this.widgetConfig?.config?.datasources; |
|||
return getTargetDeviceFromDatasources(datasources); |
|||
} |
|||
|
|||
get widgetType(): widgetType { |
|||
return this.widgetConfig?.widgetType; |
|||
} |
|||
get borderRadius(): string { |
|||
return this.widgetConfig?.config?.borderRadius; |
|||
} |
|||
|
|||
valueType = ValueType; |
|||
|
|||
actionButtonWidgetSettingsForm: UntypedFormGroup; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
private fb: UntypedFormBuilder) { |
|||
super(store); |
|||
} |
|||
|
|||
protected settingsForm(): UntypedFormGroup { |
|||
return this.actionButtonWidgetSettingsForm; |
|||
} |
|||
|
|||
protected defaultSettings(): WidgetSettings { |
|||
return {...actionButtonDefaultSettings}; |
|||
} |
|||
|
|||
protected onSettingsSet(settings: WidgetSettings) { |
|||
this.actionButtonWidgetSettingsForm = this.fb.group({ |
|||
activatedState: [settings.activatedState, []], |
|||
disabledState: [settings.disabledState, []], |
|||
|
|||
appearance: [settings.appearance, []] |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
<!-- |
|||
|
|||
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-action-settings-panel" [formGroup]="widgetActionFormGroup"> |
|||
<div class="tb-action-settings-title">{{ panelTitle | translate }}</div> |
|||
<div class="tb-action-settings-panel-content"> |
|||
<tb-widget-action |
|||
formControlName="widgetAction" |
|||
[callbacks]="callbacks" |
|||
[widgetType]="widgetType"> |
|||
</tb-widget-action> |
|||
</div> |
|||
<div class="tb-action-settings-panel-buttons"> |
|||
<button mat-button |
|||
color="primary" |
|||
type="button" |
|||
(click)="cancel()"> |
|||
{{ 'action.cancel' | translate }} |
|||
</button> |
|||
<button mat-raised-button |
|||
color="primary" |
|||
type="button" |
|||
(click)="applyWidgetAction()" |
|||
[disabled]="widgetActionFormGroup.invalid || !widgetActionFormGroup.dirty"> |
|||
{{ 'action.apply' | translate }} |
|||
</button> |
|||
</div> |
|||
</div> |
|||
@ -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, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; |
|||
import { PageComponent } from '@shared/components/page.component'; |
|||
import { TbPopoverComponent } from '@shared/components/popover.component'; |
|||
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { merge } from 'rxjs'; |
|||
import { |
|||
DataToValueType, |
|||
GetValueAction, |
|||
getValueActions, |
|||
getValueActionTranslations, |
|||
GetValueSettings |
|||
} from '@shared/models/action-widget-settings.models'; |
|||
import { ValueType } from '@shared/models/constants'; |
|||
import { TargetDevice, WidgetAction, widgetType } from '@shared/models/widget.models'; |
|||
import { AttributeScope, DataKeyType, telemetryTypeTranslationsShort } from '@shared/models/telemetry/telemetry.models'; |
|||
import { IAliasController } from '@core/api/widget-api.models'; |
|||
import { WidgetService } from '@core/http/widget.service'; |
|||
import { WidgetActionCallbacks } from '@home/components/widget/action/manage-widget-actions.component.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-widget-action-settings-panel', |
|||
templateUrl: './widget-action-settings-panel.component.html', |
|||
providers: [], |
|||
styleUrls: ['./action-settings-panel.component.scss'], |
|||
encapsulation: ViewEncapsulation.None |
|||
}) |
|||
export class WidgetActionSettingsPanelComponent extends PageComponent implements OnInit { |
|||
|
|||
@Input() |
|||
widgetAction: WidgetAction; |
|||
|
|||
@Input() |
|||
panelTitle: string; |
|||
|
|||
@Input() |
|||
widgetType: widgetType; |
|||
|
|||
@Input() |
|||
callbacks: WidgetActionCallbacks; |
|||
|
|||
@Input() |
|||
popover: TbPopoverComponent<WidgetActionSettingsPanelComponent>; |
|||
|
|||
@Output() |
|||
widgetActionApplied = new EventEmitter<WidgetAction>(); |
|||
|
|||
widgetActionFormGroup: UntypedFormGroup; |
|||
|
|||
constructor(private fb: UntypedFormBuilder, |
|||
protected store: Store<AppState>) { |
|||
super(store); |
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
this.widgetActionFormGroup = this.fb.group( |
|||
{ |
|||
widgetAction: [this.widgetAction, []] |
|||
} |
|||
); |
|||
} |
|||
|
|||
cancel() { |
|||
this.popover?.hide(); |
|||
} |
|||
|
|||
applyWidgetAction() { |
|||
const widgetAction: WidgetAction = this.widgetActionFormGroup.get('widgetAction').getRawValue(); |
|||
this.widgetActionApplied.emit(widgetAction); |
|||
} |
|||
} |
|||
@ -0,0 +1,136 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { |
|||
ChangeDetectorRef, |
|||
Component, |
|||
forwardRef, |
|||
HostBinding, |
|||
Input, |
|||
OnInit, |
|||
Renderer2, |
|||
ViewContainerRef, |
|||
ViewEncapsulation |
|||
} from '@angular/core'; |
|||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; |
|||
import { MatButton } from '@angular/material/button'; |
|||
import { TbPopoverService } from '@shared/components/popover.service'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { WidgetAction, widgetActionTypeTranslationMap, widgetType } from '@shared/models/widget.models'; |
|||
import { WidgetActionCallbacks } from '@home/components/widget/action/manage-widget-actions.component.models'; |
|||
import { |
|||
WidgetActionSettingsPanelComponent |
|||
} from '@home/components/widget/lib/settings/common/action/widget-action-settings-panel.component'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-widget-action-settings', |
|||
templateUrl: './action-settings-button.component.html', |
|||
styleUrls: ['./action-settings-button.scss'], |
|||
providers: [ |
|||
{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => WidgetActionSettingsComponent), |
|||
multi: true |
|||
} |
|||
], |
|||
encapsulation: ViewEncapsulation.None |
|||
}) |
|||
export class WidgetActionSettingsComponent implements OnInit, ControlValueAccessor { |
|||
|
|||
@HostBinding('style.overflow') |
|||
overflow = 'hidden'; |
|||
|
|||
@Input() |
|||
panelTitle: string; |
|||
|
|||
@Input() |
|||
widgetType: widgetType; |
|||
|
|||
@Input() |
|||
callbacks: WidgetActionCallbacks; |
|||
|
|||
@Input() |
|||
disabled = false; |
|||
|
|||
modelValue: WidgetAction; |
|||
|
|||
displayValue: string; |
|||
|
|||
private propagateChange = null; |
|||
|
|||
constructor(private translate: TranslateService, |
|||
private popoverService: TbPopoverService, |
|||
private renderer: Renderer2, |
|||
private viewContainerRef: ViewContainerRef, |
|||
private cd: ChangeDetectorRef) {} |
|||
|
|||
ngOnInit(): void { |
|||
} |
|||
|
|||
registerOnChange(fn: any): void { |
|||
this.propagateChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(_fn: any): void { |
|||
} |
|||
|
|||
setDisabledState(isDisabled: boolean): void { |
|||
if (this.disabled !== isDisabled) { |
|||
this.disabled = isDisabled; |
|||
} |
|||
} |
|||
|
|||
writeValue(value: WidgetAction): void { |
|||
this.modelValue = value; |
|||
this.updateDisplayValue(); |
|||
} |
|||
|
|||
openActionSettingsPopup($event: Event, matButton: MatButton) { |
|||
if ($event) { |
|||
$event.stopPropagation(); |
|||
} |
|||
const trigger = matButton._elementRef.nativeElement; |
|||
if (this.popoverService.hasPopover(trigger)) { |
|||
this.popoverService.hidePopover(trigger); |
|||
} else { |
|||
const ctx: any = { |
|||
widgetAction: this.modelValue, |
|||
panelTitle: this.panelTitle, |
|||
widgetType: this.widgetType, |
|||
callbacks: this.callbacks |
|||
}; |
|||
const widgetActionSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer, |
|||
this.viewContainerRef, WidgetActionSettingsPanelComponent, |
|||
['leftTopOnly', 'leftOnly', 'leftBottomOnly'], true, null, |
|||
ctx, |
|||
{}, |
|||
{}, {}, true); |
|||
widgetActionSettingsPanelPopover.tbComponentRef.instance.popover = widgetActionSettingsPanelPopover; |
|||
widgetActionSettingsPanelPopover.tbComponentRef.instance.widgetActionApplied.subscribe((widgetAction) => { |
|||
widgetActionSettingsPanelPopover.hide(); |
|||
this.modelValue = widgetAction; |
|||
this.updateDisplayValue(); |
|||
this.propagateChange(this.modelValue); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
private updateDisplayValue() { |
|||
this.displayValue = this.translate.instant(widgetActionTypeTranslationMap.get(this.modelValue.type)); |
|||
this.cd.markForCheck(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,97 @@ |
|||
<!-- |
|||
|
|||
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-form-panel no-padding no-border" [formGroup]="appearanceFormGroup"> |
|||
<tb-image-cards-select rowHeight="3:1" |
|||
[cols]="{columns: 2, |
|||
breakpoints: { |
|||
'lt-sm': 1 |
|||
}}" |
|||
label="{{ 'widgets.button.layout' | translate }}" formControlName="type"> |
|||
<tb-image-cards-select-option *ngFor="let type of widgetButtonTypes" |
|||
[value]="type" |
|||
[image]="widgetButtonTypeImageMap.get(type)"> |
|||
{{ widgetButtonTypeTranslationMap.get(type) | translate }} |
|||
</tb-image-cards-select-option> |
|||
</tb-image-cards-select> |
|||
<div class="tb-form-row"> |
|||
<mat-slide-toggle class="mat-slide" formControlName="autoScale"> |
|||
{{ 'widgets.button.auto-scale' | translate }} |
|||
</mat-slide-toggle> |
|||
</div> |
|||
<div class="tb-form-row"> |
|||
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showLabel"> |
|||
{{ 'widgets.button.label' | translate }} |
|||
</mat-slide-toggle> |
|||
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic"> |
|||
<input matInput formControlName="label" placeholder="{{ 'widget-config.set' | translate }}"> |
|||
</mat-form-field> |
|||
</div> |
|||
<div class="tb-form-row"> |
|||
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showIcon"> |
|||
{{ 'widgets.button.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 |
|||
formControlName="icon"> |
|||
</tb-material-icon-select> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div>{{ 'widgets.button.color-palette' | translate }}</div> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="12px"> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<div translate>widgets.button.main</div> |
|||
<tb-color-input asBoxInput |
|||
formControlName="mainColor"> |
|||
</tb-color-input> |
|||
</div> |
|||
<mat-divider vertical></mat-divider> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<div translate>widgets.button.background</div> |
|||
<tb-color-input asBoxInput |
|||
formControlName="backgroundColor"> |
|||
</tb-color-input> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-panel stroked" formGroupName="customStyle"> |
|||
<mat-expansion-panel class="tb-settings"> |
|||
<mat-expansion-panel-header fxLayout="row wrap"> |
|||
<mat-panel-title> |
|||
<div class="tb-form-panel-title" translate>widgets.button.custom-styles</div> |
|||
</mat-panel-title> |
|||
</mat-expansion-panel-header> |
|||
<ng-template matExpansionPanelContent> |
|||
<div class="tb-form-row space-between" *ngFor="let state of widgetButtonStates"> |
|||
<div>{{ widgetButtonStateTranslationMap.get(state) | translate }}</div> |
|||
<tb-widget-button-custom-style |
|||
[state]="state" |
|||
[appearance]="this.appearanceFormGroup.value" |
|||
[borderRadius]="borderRadius" |
|||
[formControlName]="state"> |
|||
</tb-widget-button-custom-style> |
|||
</div> |
|||
</ng-template> |
|||
</mat-expansion-panel> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,141 @@ |
|||
///
|
|||
/// 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, OnInit, ViewEncapsulation } from '@angular/core'; |
|||
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; |
|||
import { |
|||
WidgetButtonAppearance, |
|||
widgetButtonStates, widgetButtonStatesTranslations, |
|||
widgetButtonTypeImages, |
|||
widgetButtonTypes, |
|||
widgetButtonTypeTranslations |
|||
} from '@shared/components/button/widget-button.models'; |
|||
import { merge } from 'rxjs'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-widget-button-appearance', |
|||
templateUrl: './widget-button-appearance.component.html', |
|||
styleUrls: [], |
|||
providers: [ |
|||
{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => WidgetButtonAppearanceComponent), |
|||
multi: true |
|||
} |
|||
], |
|||
encapsulation: ViewEncapsulation.None |
|||
}) |
|||
export class WidgetButtonAppearanceComponent implements OnInit, ControlValueAccessor { |
|||
|
|||
@Input() |
|||
disabled = false; |
|||
|
|||
@Input() |
|||
borderRadius: string; |
|||
|
|||
widgetButtonTypes = widgetButtonTypes; |
|||
|
|||
widgetButtonTypeTranslationMap = widgetButtonTypeTranslations; |
|||
widgetButtonTypeImageMap = widgetButtonTypeImages; |
|||
|
|||
widgetButtonStates = widgetButtonStates; |
|||
widgetButtonStateTranslationMap = widgetButtonStatesTranslations; |
|||
|
|||
modelValue: WidgetButtonAppearance; |
|||
|
|||
appearanceFormGroup: UntypedFormGroup; |
|||
|
|||
private propagateChange = (_val: any) => {}; |
|||
|
|||
constructor(private fb: UntypedFormBuilder) {} |
|||
|
|||
ngOnInit(): void { |
|||
this.appearanceFormGroup = this.fb.group({ |
|||
type: [null, []], |
|||
autoScale: [null, []], |
|||
showLabel: [null, []], |
|||
label: [null, []], |
|||
showIcon: [null, []], |
|||
icon: [null, []], |
|||
iconSize: [null, []], |
|||
iconSizeUnit: [null, []], |
|||
mainColor: [null, []], |
|||
backgroundColor: [null, []] |
|||
}); |
|||
const customStyle = this.fb.group({}); |
|||
for (const state of widgetButtonStates) { |
|||
customStyle.addControl(state, this.fb.control(null, [])); |
|||
} |
|||
this.appearanceFormGroup.addControl('customStyle', customStyle); |
|||
this.appearanceFormGroup.valueChanges.subscribe(() => { |
|||
this.updateModel(); |
|||
}); |
|||
merge(this.appearanceFormGroup.get('showLabel').valueChanges, |
|||
this.appearanceFormGroup.get('showIcon').valueChanges) |
|||
.subscribe(() => { |
|||
this.updateValidators(); |
|||
}); |
|||
} |
|||
|
|||
registerOnChange(fn: any): void { |
|||
this.propagateChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: any): void { |
|||
} |
|||
|
|||
setDisabledState(isDisabled: boolean): void { |
|||
this.disabled = isDisabled; |
|||
if (this.disabled) { |
|||
this.appearanceFormGroup.disable({emitEvent: false}); |
|||
} else { |
|||
this.appearanceFormGroup.enable({emitEvent: false}); |
|||
this.updateValidators(); |
|||
} |
|||
} |
|||
|
|||
writeValue(value: WidgetButtonAppearance): void { |
|||
this.modelValue = value; |
|||
this.appearanceFormGroup.patchValue( |
|||
value, {emitEvent: false} |
|||
); |
|||
this.updateValidators(); |
|||
} |
|||
|
|||
private updateModel() { |
|||
this.modelValue = this.appearanceFormGroup.getRawValue(); |
|||
this.propagateChange(this.modelValue); |
|||
} |
|||
|
|||
private updateValidators(): void { |
|||
const showLabel: boolean = this.appearanceFormGroup.get('showLabel').value; |
|||
const showIcon: boolean = this.appearanceFormGroup.get('showIcon').value; |
|||
if (showLabel) { |
|||
this.appearanceFormGroup.get('label').enable(); |
|||
} else { |
|||
this.appearanceFormGroup.get('label').disable(); |
|||
} |
|||
if (showIcon) { |
|||
this.appearanceFormGroup.get('icon').enable(); |
|||
this.appearanceFormGroup.get('iconSize').enable(); |
|||
this.appearanceFormGroup.get('iconSizeUnit').enable(); |
|||
} else { |
|||
this.appearanceFormGroup.get('icon').disable(); |
|||
this.appearanceFormGroup.get('iconSize').disable(); |
|||
this.appearanceFormGroup.get('iconSizeUnit').disable(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,93 @@ |
|||
<!-- |
|||
|
|||
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-widget-button-custom-style-panel" [formGroup]="customStyleFormGroup"> |
|||
<div class="tb-widget-button-custom-style-title">{{ widgetButtonStateTranslationMap.get(state) | translate }}</div> |
|||
<div class="tb-widget-button-custom-style-panel-content"> |
|||
<div class="tb-form-row space-between"> |
|||
<mat-slide-toggle class="mat-slide" formControlName="overrideMainColor"> |
|||
{{ 'widgets.button.main' | translate }} |
|||
</mat-slide-toggle> |
|||
<tb-color-input asBoxInput |
|||
formControlName="mainColor"> |
|||
</tb-color-input> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<mat-slide-toggle class="mat-slide" formControlName="overrideBackgroundColor"> |
|||
{{ 'widgets.button.background' | translate }} |
|||
</mat-slide-toggle> |
|||
<tb-color-input asBoxInput |
|||
formControlName="backgroundColor"> |
|||
</tb-color-input> |
|||
</div> |
|||
<div class="tb-form-row space-between column-xs"> |
|||
<mat-slide-toggle class="mat-slide" formControlName="overrideDropShadow"> |
|||
{{ 'widgets.button.shadow' | translate }} |
|||
</mat-slide-toggle> |
|||
<mat-chip-listbox formControlName="dropShadow"> |
|||
<mat-chip-option [selectable]="!customStyleFormGroup.get('dropShadow').value" [value]="true">{{ 'widgets.button.enabled' | translate }}</mat-chip-option> |
|||
<mat-chip-option [selectable]="customStyleFormGroup.get('dropShadow').value" [value]="false">{{ 'widgets.button.disabled' | translate }}</mat-chip-option> |
|||
</mat-chip-listbox> |
|||
</div> |
|||
<div class="tb-widget-button-custom-style-preview"> |
|||
<div class="tb-widget-button-custom-style-preview-title" translate> |
|||
widgets.button.preview |
|||
</div> |
|||
<tb-widget-button |
|||
[appearance]="previewAppearance" |
|||
[borderRadius]="borderRadius" |
|||
disableEvents |
|||
[hovered]="state === widgetButtonState.hovered" |
|||
[pressed]="state === widgetButtonState.pressed" |
|||
[activated]="state === widgetButtonState.activated" |
|||
[disabled]="state === widgetButtonState.disabled"> |
|||
</tb-widget-button> |
|||
</div> |
|||
</div> |
|||
<div class="tb-widget-button-custom-style-panel-buttons"> |
|||
<button *ngIf="copyFromStates?.length" |
|||
#copyStyleButton |
|||
class="tb-nowrap" |
|||
mat-stroked-button |
|||
color="primary" |
|||
type="button" |
|||
[matMenuTriggerFor]="styleSourcesMenu" [matMenuTriggerData]="{menuWidth: copyStyleButton._elementRef.nativeElement.clientWidth}"> |
|||
{{ 'widgets.button.copy-style-from' | translate }} |
|||
</button> |
|||
<mat-menu #styleSourcesMenu="matMenu"> |
|||
<ng-template matMenuContent let-menuWidth="menuWidth"> |
|||
<div [style.min-width.px]="menuWidth"> |
|||
<button mat-menu-item *ngFor="let state of copyFromStates" (click)="copyStyle(state)">{{ widgetButtonStateTranslationMap.get(state) | translate }}</button> |
|||
</div> |
|||
</ng-template> |
|||
</mat-menu> |
|||
<span fxFlex></span> |
|||
<button mat-button |
|||
color="primary" |
|||
type="button" |
|||
(click)="cancel()"> |
|||
{{ 'action.cancel' | translate }} |
|||
</button> |
|||
<button mat-raised-button |
|||
color="primary" |
|||
type="button" |
|||
(click)="applyCustomStyle()" |
|||
[disabled]="customStyleFormGroup.invalid || !customStyleFormGroup.dirty"> |
|||
{{ 'action.apply' | translate }} |
|||
</button> |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,68 @@ |
|||
/** |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
@import '../../../../../../../../../scss/constants'; |
|||
|
|||
.tb-widget-button-custom-style-panel { |
|||
width: 530px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 16px; |
|||
@media #{$mat-lt-md} { |
|||
width: 90vw; |
|||
} |
|||
.tb-widget-button-custom-style-panel-content { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 16px; |
|||
overflow: auto; |
|||
} |
|||
.tb-widget-button-custom-style-title { |
|||
font-size: 16px; |
|||
font-weight: 500; |
|||
line-height: 24px; |
|||
letter-spacing: 0.25px; |
|||
color: rgba(0, 0, 0, 0.87); |
|||
} |
|||
.tb-widget-button-custom-style-preview { |
|||
flex: 1; |
|||
background: rgba(0, 0, 0, 0.04); |
|||
display: flex; |
|||
flex-direction: column; |
|||
padding: 12px 16px 24px 16px; |
|||
align-items: center; |
|||
gap: 12px; |
|||
.tb-widget-button-custom-style-preview-title { |
|||
align-self: stretch; |
|||
font-size: 16px; |
|||
font-style: normal; |
|||
font-weight: 500; |
|||
line-height: 24px; |
|||
color: rgba(0, 0, 0, 0.38); |
|||
} |
|||
tb-widget-button { |
|||
width: 200px; |
|||
height: 60px; |
|||
} |
|||
} |
|||
.tb-widget-button-custom-style-panel-buttons { |
|||
height: 40px; |
|||
display: flex; |
|||
flex-direction: row; |
|||
gap: 16px; |
|||
justify-content: flex-end; |
|||
align-items: flex-end; |
|||
} |
|||
} |
|||
@ -0,0 +1,171 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; |
|||
import { PageComponent } from '@shared/components/page.component'; |
|||
import { TbPopoverComponent } from '@shared/components/popover.component'; |
|||
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { |
|||
defaultBackgroundColorDisabled, |
|||
defaultMainColorDisabled, |
|||
WidgetButtonAppearance, |
|||
WidgetButtonCustomStyle, |
|||
WidgetButtonState, |
|||
widgetButtonStates, |
|||
widgetButtonStatesTranslations, |
|||
WidgetButtonType |
|||
} from '@shared/components/button/widget-button.models'; |
|||
import { merge } from 'rxjs'; |
|||
import { deepClone } from '@core/utils'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-widget-button-custom-style-panel', |
|||
templateUrl: './widget-button-custom-style-panel.component.html', |
|||
providers: [], |
|||
styleUrls: ['./widget-button-custom-style-panel.component.scss'], |
|||
encapsulation: ViewEncapsulation.None |
|||
}) |
|||
export class WidgetButtonCustomStylePanelComponent extends PageComponent implements OnInit { |
|||
|
|||
@Input() |
|||
appearance: WidgetButtonAppearance; |
|||
|
|||
@Input() |
|||
borderRadius: string; |
|||
|
|||
@Input() |
|||
state: WidgetButtonState; |
|||
|
|||
@Input() |
|||
customStyle: WidgetButtonCustomStyle; |
|||
|
|||
@Input() |
|||
popover: TbPopoverComponent<WidgetButtonCustomStylePanelComponent>; |
|||
|
|||
@Output() |
|||
customStyleApplied = new EventEmitter<WidgetButtonCustomStyle>(); |
|||
|
|||
widgetButtonStateTranslationMap = widgetButtonStatesTranslations; |
|||
|
|||
widgetButtonState = WidgetButtonState; |
|||
|
|||
previewAppearance: WidgetButtonAppearance; |
|||
|
|||
copyFromStates: WidgetButtonState[]; |
|||
|
|||
customStyleFormGroup: UntypedFormGroup; |
|||
|
|||
constructor(private fb: UntypedFormBuilder, |
|||
protected store: Store<AppState>, |
|||
private cd: ChangeDetectorRef) { |
|||
super(store); |
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
this.copyFromStates = widgetButtonStates.filter(state => |
|||
state !== this.state && !!this.appearance.customStyle[state]); |
|||
this.customStyleFormGroup = this.fb.group( |
|||
{ |
|||
overrideMainColor: [false, []], |
|||
mainColor: [null, []], |
|||
overrideBackgroundColor: [false, []], |
|||
backgroundColor: [null, []], |
|||
overrideDropShadow: [false, []], |
|||
dropShadow: [false, []] |
|||
} |
|||
); |
|||
merge(this.customStyleFormGroup.get('overrideMainColor').valueChanges, |
|||
this.customStyleFormGroup.get('overrideBackgroundColor').valueChanges, |
|||
this.customStyleFormGroup.get('overrideDropShadow').valueChanges) |
|||
.subscribe(() => { |
|||
this.updateValidators(); |
|||
}); |
|||
this.customStyleFormGroup.valueChanges.subscribe(() => { |
|||
this.updatePreviewAppearance(); |
|||
}); |
|||
this.setStyle(this.customStyle); |
|||
} |
|||
|
|||
copyStyle(state: WidgetButtonState) { |
|||
this.customStyle = deepClone(this.appearance.customStyle[state]); |
|||
this.setStyle(this.customStyle); |
|||
this.customStyleFormGroup.markAsDirty(); |
|||
} |
|||
|
|||
cancel() { |
|||
this.popover?.hide(); |
|||
} |
|||
|
|||
applyCustomStyle() { |
|||
const customStyle: WidgetButtonCustomStyle = this.customStyleFormGroup.value; |
|||
this.customStyleApplied.emit(customStyle); |
|||
} |
|||
|
|||
private setStyle(customStyle?: WidgetButtonCustomStyle): void { |
|||
let mainColor = this.state === WidgetButtonState.disabled ? defaultMainColorDisabled : this.appearance.mainColor; |
|||
if (customStyle?.overrideMainColor) { |
|||
mainColor = customStyle?.mainColor; |
|||
} |
|||
let backgroundColor = this.state === WidgetButtonState.disabled ? defaultBackgroundColorDisabled : this.appearance.backgroundColor; |
|||
if (customStyle?.overrideBackgroundColor) { |
|||
backgroundColor = customStyle?.backgroundColor; |
|||
} |
|||
let dropShadow = this.appearance.type === WidgetButtonType.basic ? false : true; |
|||
if (customStyle?.overrideDropShadow) { |
|||
dropShadow = customStyle?.dropShadow; |
|||
} |
|||
this.customStyleFormGroup.patchValue({ |
|||
overrideMainColor: customStyle?.overrideMainColor, |
|||
mainColor, |
|||
overrideBackgroundColor: customStyle?.overrideBackgroundColor, |
|||
backgroundColor, |
|||
overrideDropShadow: customStyle?.overrideDropShadow, |
|||
dropShadow |
|||
}, {emitEvent: false}); |
|||
this.updateValidators(); |
|||
this.updatePreviewAppearance(); |
|||
} |
|||
|
|||
private updateValidators() { |
|||
const overrideMainColor: boolean = this.customStyleFormGroup.get('overrideMainColor').value; |
|||
const overrideBackgroundColor: boolean = this.customStyleFormGroup.get('overrideBackgroundColor').value; |
|||
const overrideDropShadow: boolean = this.customStyleFormGroup.get('overrideDropShadow').value; |
|||
|
|||
if (overrideMainColor) { |
|||
this.customStyleFormGroup.get('mainColor').enable({emitEvent: false}); |
|||
} else { |
|||
this.customStyleFormGroup.get('mainColor').disable({emitEvent: false}); |
|||
} |
|||
if (overrideBackgroundColor) { |
|||
this.customStyleFormGroup.get('backgroundColor').enable({emitEvent: false}); |
|||
} else { |
|||
this.customStyleFormGroup.get('backgroundColor').disable({emitEvent: false}); |
|||
} |
|||
if (overrideDropShadow) { |
|||
this.customStyleFormGroup.get('dropShadow').enable({emitEvent: false}); |
|||
} else { |
|||
this.customStyleFormGroup.get('dropShadow').disable({emitEvent: false}); |
|||
} |
|||
} |
|||
|
|||
private updatePreviewAppearance() { |
|||
this.previewAppearance = deepClone(this.appearance); |
|||
this.previewAppearance.customStyle[this.state] = this.customStyleFormGroup.value; |
|||
this.cd.markForCheck(); |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
<!-- |
|||
|
|||
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-widget-button-custom-style"> |
|||
<div class="tb-widget-button-preview-panel tb-primary-fill"> |
|||
<tb-widget-button |
|||
[appearance]="previewAppearance" |
|||
[borderRadius]="borderRadius" |
|||
disableEvents |
|||
[hovered]="state === widgetButtonState.hovered" |
|||
[pressed]="state === widgetButtonState.pressed" |
|||
[activated]="state === widgetButtonState.activated" |
|||
[disabled]="state === widgetButtonState.disabled"> |
|||
</tb-widget-button> |
|||
<button *ngIf="modelValue" |
|||
mat-icon-button |
|||
[matTooltip]="'widgets.button.clear-style' | translate" |
|||
matTooltipPosition="above" |
|||
class="tb-mat-32" |
|||
(click)="clearStyle()"> |
|||
<tb-icon>mdi:broom</tb-icon> |
|||
</button> |
|||
</div> |
|||
<button mat-icon-button |
|||
class="tb-mat-32" |
|||
#matIconButton |
|||
(click)="openButtonCustomStylePopup($event, matIconButton)"> |
|||
<mat-icon>edit</mat-icon> |
|||
</button> |
|||
</div> |
|||
@ -0,0 +1,46 @@ |
|||
/** |
|||
* Copyright © 2016-2024 The Thingsboard Authors |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0 |
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
@import '../../../../../../../../../scss/constants'; |
|||
|
|||
.tb-widget-button-custom-style { |
|||
display: flex; |
|||
flex-direction: row; |
|||
align-items: center; |
|||
gap: 12px; |
|||
button.mat-mdc-icon-button { |
|||
color: rgba(0,0,0,0.56); |
|||
} |
|||
.tb-widget-button-preview-panel { |
|||
width: 148px; |
|||
height: 48px; |
|||
padding: 8px 12px; |
|||
border-radius: 4px; |
|||
display: flex; |
|||
flex-direction: row; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
tb-widget-button { |
|||
width: 84px; |
|||
height: 100%; |
|||
} |
|||
@media #{$mat-gt-xs} { |
|||
width: 168px; |
|||
tb-widget-button { |
|||
width: 104px; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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 { |
|||
ChangeDetectorRef, |
|||
Component, |
|||
forwardRef, |
|||
Input, |
|||
OnChanges, |
|||
OnInit, |
|||
Renderer2, |
|||
SimpleChanges, |
|||
ViewContainerRef, |
|||
ViewEncapsulation |
|||
} from '@angular/core'; |
|||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; |
|||
import { |
|||
WidgetButtonAppearance, |
|||
WidgetButtonCustomStyle, |
|||
WidgetButtonState |
|||
} from '@shared/components/button/widget-button.models'; |
|||
import { TbPopoverService } from '@shared/components/popover.service'; |
|||
import { MatIconButton } from '@angular/material/button'; |
|||
import { |
|||
WidgetButtonCustomStylePanelComponent |
|||
} from '@home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component'; |
|||
import { deepClone } from '@core/utils'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-widget-button-custom-style', |
|||
templateUrl: './widget-button-custom-style.component.html', |
|||
styleUrls: ['./widget-button-custom-style.component.scss'], |
|||
providers: [ |
|||
{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => WidgetButtonCustomStyleComponent), |
|||
multi: true |
|||
} |
|||
], |
|||
encapsulation: ViewEncapsulation.None |
|||
}) |
|||
export class WidgetButtonCustomStyleComponent implements OnInit, OnChanges, ControlValueAccessor { |
|||
|
|||
@Input() |
|||
disabled = false; |
|||
|
|||
@Input() |
|||
appearance: WidgetButtonAppearance; |
|||
|
|||
@Input() |
|||
borderRadius: string; |
|||
|
|||
@Input() |
|||
state: WidgetButtonState; |
|||
|
|||
widgetButtonState = WidgetButtonState; |
|||
|
|||
modelValue: WidgetButtonCustomStyle; |
|||
|
|||
previewAppearance: WidgetButtonAppearance; |
|||
|
|||
private propagateChange = (_val: any) => {}; |
|||
|
|||
constructor(private popoverService: TbPopoverService, |
|||
private renderer: Renderer2, |
|||
private viewContainerRef: ViewContainerRef, |
|||
private cd: ChangeDetectorRef) {} |
|||
|
|||
ngOnInit(): void { |
|||
this.updatePreviewAppearance(); |
|||
} |
|||
|
|||
ngOnChanges(changes: SimpleChanges): void { |
|||
for (const propName of Object.keys(changes)) { |
|||
const change = changes[propName]; |
|||
if (!change.firstChange) { |
|||
if (propName === 'appearance') { |
|||
this.updatePreviewAppearance(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
registerOnChange(fn: any): void { |
|||
this.propagateChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: any): void { |
|||
} |
|||
|
|||
setDisabledState(_isDisabled: boolean): void { |
|||
} |
|||
|
|||
writeValue(value: WidgetButtonCustomStyle): void { |
|||
this.modelValue = value; |
|||
this.updatePreviewAppearance(); |
|||
} |
|||
|
|||
clearStyle() { |
|||
this.updateModel(null); |
|||
} |
|||
|
|||
openButtonCustomStylePopup($event: Event, matButton: MatIconButton) { |
|||
if ($event) { |
|||
$event.stopPropagation(); |
|||
} |
|||
const trigger = matButton._elementRef.nativeElement; |
|||
if (this.popoverService.hasPopover(trigger)) { |
|||
this.popoverService.hidePopover(trigger); |
|||
} else { |
|||
const ctx: any = { |
|||
appearance: this.appearance, |
|||
borderRadius: this.borderRadius, |
|||
state: this.state, |
|||
customStyle: this.modelValue |
|||
}; |
|||
const widgetButtonCustomStylePanelPopover = this.popoverService.displayPopover(trigger, this.renderer, |
|||
this.viewContainerRef, WidgetButtonCustomStylePanelComponent, |
|||
['leftTopOnly', 'leftOnly', 'leftBottomOnly'], true, null, |
|||
ctx, |
|||
{}, |
|||
{}, {}, true); |
|||
widgetButtonCustomStylePanelPopover.tbComponentRef.instance.popover = widgetButtonCustomStylePanelPopover; |
|||
widgetButtonCustomStylePanelPopover.tbComponentRef.instance.customStyleApplied.subscribe((customStyle) => { |
|||
widgetButtonCustomStylePanelPopover.hide(); |
|||
this.updateModel(customStyle); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
private updateModel(value: WidgetButtonCustomStyle): void { |
|||
this.modelValue = value; |
|||
this.updatePreviewAppearance(); |
|||
this.propagateChange(this.modelValue); |
|||
} |
|||
|
|||
private updatePreviewAppearance() { |
|||
this.previewAppearance = deepClone(this.appearance); |
|||
if (this.modelValue) { |
|||
this.previewAppearance.customStyle[this.state] = this.modelValue; |
|||
} |
|||
this.cd.markForCheck(); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
<!-- |
|||
|
|||
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. |
|||
|
|||
--> |
|||
<button #widgetButton |
|||
mat-button |
|||
type="button" |
|||
class="tb-widget-button" |
|||
(click)="clicked.emit($event)" |
|||
(mousedown)="mousePressed = true" |
|||
(mouseup)="mousePressed = false" |
|||
(mouseleave)="mousePressed = false" |
|||
[disabled]="disabled" |
|||
[class]="'tb-'+appearance.type" |
|||
[class.tb-pressed]="mousePressed" |
|||
[class.tb-pressed-state]="pressed" |
|||
[class.tb-hover-state]="hovered" |
|||
[class.tb-active-state]="activated" |
|||
[class.tb-disabled-state]="disabled" |
|||
[style.border-radius]="borderRadius" |
|||
[style.pointer-events]="disableEvents ? 'none' : ''"> |
|||
<div #widgetButtonContent class="tb-widget-button-content" *ngIf="appearance.showIcon || appearance.showLabel"> |
|||
<tb-icon matButtonIcon *ngIf="appearance.showIcon" [style]="iconStyle">{{ appearance.icon }}</tb-icon> |
|||
<span *ngIf="appearance.showLabel" class="tb-widget-button-label">{{ appearance.label }}</span> |
|||
</div> |
|||
</button> |
|||
@ -0,0 +1,189 @@ |
|||
/** |
|||
* 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. |
|||
*/ |
|||
$defaultMainColor: #3F52DD; |
|||
$defaultBackgroundColor: #FFFFFF; |
|||
$defaultBoxShadowColor: rgba(0, 0, 0, 0.08); |
|||
$defaultDisabledBoxShadowColor: rgba(0, 0, 0, 0); |
|||
|
|||
$defaultMainColorDisabled: rgba(0, 0, 0, 0.38); |
|||
$defaultBackgroundColorDisabled: rgba(0, 0, 0, 0.03); |
|||
|
|||
$mainColorEnabled: var(--tb-widget-button-main-color-enabled, $defaultMainColor); |
|||
$backgroundColorEnabled: var(--tb-widget-button-background-color-enabled, $defaultBackgroundColor); |
|||
$boxShadowColorEnabled: var(--tb-widget-button-box-shadow-color-enabled, $defaultBoxShadowColor); |
|||
|
|||
$mainColorHovered: var(--tb-widget-button-main-color-hovered, $defaultMainColor); |
|||
$backgroundColorHovered: var(--tb-widget-button-background-color-hovered, $defaultBackgroundColor); |
|||
$boxShadowColorHovered: var(--tb-widget-button-box-shadow-color-hovered, $defaultBoxShadowColor); |
|||
$mainColorHoveredFilled: var(--tb-widget-button-main-color-hovered-filled, #3347db); // main.darken(6) |
|||
|
|||
$mainColorPressed: var(--tb-widget-button-main-color-pressed, $defaultMainColor); |
|||
$backgroundColorPressed: var(--tb-widget-button-background-color-pressed, $defaultBackgroundColor); |
|||
$boxShadowColorPressed: var(--tb-widget-button-box-shadow-color-pressed, $defaultBoxShadowColor); |
|||
$mainColorPressedFilled: var(--tb-widget-button-main-color-pressed-filled, #273cd9); // main.darken(12) |
|||
$mainColorPressedRipple: var(--tb-widget-button-main-color-pressed-ripple, rgba(63, 82, 221, 0.1)); // Alpha(Main, 0.1) |
|||
$mainColorPressedRippleFilled: var(--tb-widget-button-main-color-pressed-ripple-filled, #2439cd); // main.darken(18) |
|||
|
|||
$mainColorActivated: var(--tb-widget-button-main-color-activated, $defaultMainColor); |
|||
$backgroundColorActivated: var(--tb-widget-button-background-color-activated, $defaultBackgroundColor); |
|||
$boxShadowColorActivated: var(--tb-widget-button-box-shadow-color-activated, $defaultBoxShadowColor); |
|||
$mainColorActivatedFilled: var(--tb-widget-button-main-color-activated-filled, #273cd9); // main.darken(12) |
|||
|
|||
$mainColorDisabled: var(--tb-widget-button-main-color-disabled, $defaultMainColorDisabled); |
|||
$backgroundColorDisabled: var(--tb-widget-button-background-color-disabled, $defaultBackgroundColorDisabled); |
|||
$boxShadowColorDisabled: var(--tb-widget-button-box-shadow-color-disabled, $defaultBoxShadowColor); |
|||
|
|||
|
|||
@mixin _tb-widget-button-styles($main, $background, $boxShadow) { |
|||
color: $main; |
|||
background-color: $background; |
|||
box-shadow: 0 4px 8px 0 $boxShadow; |
|||
&.tb-outlined { |
|||
border: 1px solid $main; |
|||
} |
|||
&.tb-filled { |
|||
color: $background; |
|||
background-color: $main; |
|||
} |
|||
&.tb-underlined { |
|||
border-bottom: 2px solid $main; |
|||
} |
|||
&.tb-basic { |
|||
background-color: transparent; |
|||
} |
|||
} |
|||
|
|||
|
|||
.mat-mdc-button.mat-mdc-button-base.tb-widget-button { |
|||
width: 100%; |
|||
height: 100%; |
|||
padding: 8px 12px; |
|||
.mdc-button__label { |
|||
width: 100%; |
|||
height: 100%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
.tb-widget-button-content { |
|||
width: 100%; |
|||
display: flex; |
|||
flex-direction: row; |
|||
gap: 4px; |
|||
justify-content: center; |
|||
align-items: center; |
|||
.mat-icon { |
|||
margin: 0; |
|||
} |
|||
span.tb-widget-button-label { |
|||
line-height: normal; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
} |
|||
} |
|||
|
|||
.mat-mdc-button-persistent-ripple::before { |
|||
opacity: 0; |
|||
} |
|||
|
|||
@include _tb-widget-button-styles($mainColorEnabled, $backgroundColorEnabled, $boxShadowColorEnabled); |
|||
|
|||
&:not(:disabled):not(.tb-disabled-state) { |
|||
&:hover, &.tb-hover-state { |
|||
&:not(:active):not(.tb-active-state) { |
|||
&:not(.tb-filled) { |
|||
.mat-mdc-button-persistent-ripple::before { |
|||
opacity: 0.04; |
|||
background-color: $mainColorHovered; |
|||
} |
|||
} |
|||
&.tb-filled { |
|||
.mat-mdc-button-persistent-ripple::before { |
|||
opacity: 1; |
|||
background-color: $mainColorHoveredFilled; |
|||
} |
|||
} |
|||
@include _tb-widget-button-styles($mainColorHovered, $backgroundColorHovered, $boxShadowColorHovered); |
|||
} |
|||
} |
|||
&.tb-pressed-state { |
|||
&:not(.tb-filled) { |
|||
.mat-mdc-button-ripple { |
|||
background-color: $mainColorPressedRipple; |
|||
} |
|||
} |
|||
&.tb-filled { |
|||
.mat-mdc-button-ripple { |
|||
background-color: $mainColorPressedRippleFilled; |
|||
} |
|||
} |
|||
} |
|||
&.tb-pressed { |
|||
&:not(.tb-filled) { |
|||
.mat-ripple-element { |
|||
background-color: $mainColorPressedRipple; |
|||
} |
|||
} |
|||
&.tb-filled { |
|||
.mat-ripple-element { |
|||
background-color: $mainColorPressedRippleFilled; |
|||
} |
|||
} |
|||
} |
|||
&.tb-pressed, &.tb-pressed-state { |
|||
&:not(.tb-filled) { |
|||
.mat-mdc-button-persistent-ripple::before { |
|||
opacity: 0.12; |
|||
background-color: $mainColorPressed; |
|||
} |
|||
} |
|||
&.tb-filled { |
|||
.mat-mdc-button-persistent-ripple::before { |
|||
opacity: 1; |
|||
background-color: $mainColorPressedFilled; |
|||
} |
|||
} |
|||
@include _tb-widget-button-styles($mainColorPressed, $backgroundColorPressed, $boxShadowColorPressed); |
|||
} |
|||
&:active, &.tb-active-state { |
|||
&:not(.tb-pressed):not(.tb-pressed-state) { |
|||
&:not(.tb-filled) { |
|||
.mat-mdc-button-persistent-ripple::before { |
|||
opacity: 0.12; |
|||
background-color: $mainColorActivated; |
|||
} |
|||
} |
|||
&.tb-filled { |
|||
.mat-mdc-button-persistent-ripple::before { |
|||
opacity: 1; |
|||
background-color: $mainColorActivatedFilled; |
|||
} |
|||
} |
|||
@include _tb-widget-button-styles($mainColorActivated, $backgroundColorActivated, $boxShadowColorActivated); |
|||
} |
|||
} |
|||
} |
|||
|
|||
&:disabled, &.tb-disabled-state { |
|||
&:not(.tb-filled) { |
|||
@include _tb-widget-button-styles($mainColorDisabled, $backgroundColorDisabled, $boxShadowColorDisabled); |
|||
} |
|||
&.tb-filled { |
|||
@include _tb-widget-button-styles($backgroundColorDisabled, $mainColorDisabled, $boxShadowColorDisabled); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,178 @@ |
|||
///
|
|||
/// 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, |
|||
Component, |
|||
ElementRef, |
|||
EventEmitter, |
|||
Input, |
|||
OnChanges, |
|||
OnDestroy, |
|||
OnInit, |
|||
Output, |
|||
Renderer2, |
|||
SimpleChanges, ViewChild, |
|||
ViewEncapsulation |
|||
} from '@angular/core'; |
|||
import { |
|||
generateWidgetButtonAppearanceCss, |
|||
widgetButtonDefaultAppearance |
|||
} from '@shared/components/button/widget-button.models'; |
|||
import { coerceBoolean } from '@shared/decorators/coercion'; |
|||
import { ComponentStyle, iconStyle } from '@shared/models/widget-settings.models'; |
|||
import { UtilsService } from '@core/services/utils.service'; |
|||
import { ResizeObserver } from '@juggle/resize-observer'; |
|||
|
|||
const initialButtonHeight = 60; |
|||
const horizontalLayoutPadding = 24; |
|||
const verticalLayoutPadding = 16; |
|||
|
|||
@Component({ |
|||
selector: 'tb-widget-button', |
|||
templateUrl: './widget-button.component.html', |
|||
styleUrls: ['./widget-button.component.scss'], |
|||
encapsulation: ViewEncapsulation.None |
|||
}) |
|||
export class WidgetButtonComponent implements OnInit, AfterViewInit, OnDestroy, OnChanges { |
|||
|
|||
@ViewChild('widgetButton', {read: ElementRef}) |
|||
widgetButton: ElementRef<HTMLElement>; |
|||
|
|||
@ViewChild('widgetButtonContent', {static: false}) |
|||
widgetButtonContent: ElementRef<HTMLElement>; |
|||
|
|||
@Input() |
|||
appearance = widgetButtonDefaultAppearance; |
|||
|
|||
@Input() |
|||
borderRadius = '4px'; |
|||
|
|||
@Input() |
|||
@coerceBoolean() |
|||
disabled = false; |
|||
|
|||
@Input() |
|||
@coerceBoolean() |
|||
activated = false; |
|||
|
|||
@Input() |
|||
@coerceBoolean() |
|||
hovered = false; |
|||
|
|||
@Input() |
|||
@coerceBoolean() |
|||
pressed = false; |
|||
|
|||
@Input() |
|||
@coerceBoolean() |
|||
disableEvents = false; |
|||
|
|||
@Output() |
|||
clicked = new EventEmitter<MouseEvent>(); |
|||
|
|||
iconStyle: ComponentStyle = {}; |
|||
|
|||
mousePressed = false; |
|||
|
|||
private buttonResize$: ResizeObserver; |
|||
|
|||
private appearanceCssClass: string; |
|||
|
|||
constructor(private renderer: Renderer2, |
|||
private elementRef: ElementRef, |
|||
private utils: UtilsService) {} |
|||
|
|||
ngOnInit(): void { |
|||
this.updateAppearance(); |
|||
} |
|||
|
|||
ngOnChanges(changes: SimpleChanges): void { |
|||
for (const propName of Object.keys(changes)) { |
|||
const change = changes[propName]; |
|||
if (!change.firstChange) { |
|||
if (propName === 'appearance') { |
|||
this.updateAppearance(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
ngAfterViewInit(): void { |
|||
this.updateAutoScale(); |
|||
} |
|||
|
|||
ngOnDestroy(): void { |
|||
if (this.buttonResize$) { |
|||
this.buttonResize$.disconnect(); |
|||
} |
|||
this.clearAppearanceCss(); |
|||
} |
|||
|
|||
private updateAppearance(): void { |
|||
this.clearAppearanceCss(); |
|||
if (this.appearance.showIcon) { |
|||
this.iconStyle = iconStyle(this.appearance.iconSize, this.appearance.iconSizeUnit); |
|||
} |
|||
const appearanceCss = generateWidgetButtonAppearanceCss(this.appearance); |
|||
this.appearanceCssClass = this.utils.applyCssToElement(this.renderer, this.elementRef.nativeElement, |
|||
'tb-widget-button', appearanceCss); |
|||
this.updateAutoScale(); |
|||
} |
|||
|
|||
private clearAppearanceCss(): void { |
|||
if (this.appearanceCssClass) { |
|||
this.utils.clearCssElement(this.renderer, this.appearanceCssClass, this.elementRef?.nativeElement); |
|||
this.appearanceCssClass = null; |
|||
} |
|||
} |
|||
|
|||
private updateAutoScale() { |
|||
if (this.buttonResize$) { |
|||
this.buttonResize$.disconnect(); |
|||
} |
|||
if (this.widgetButton && this.widgetButtonContent) { |
|||
if (this.appearance.autoScale) { |
|||
this.buttonResize$ = new ResizeObserver(() => { |
|||
this.onResize(); |
|||
}); |
|||
this.buttonResize$.observe(this.widgetButton.nativeElement); |
|||
this.onResize(); |
|||
} else { |
|||
this.renderer.setStyle(this.widgetButtonContent.nativeElement, 'transform', 'none'); |
|||
this.renderer.setStyle(this.widgetButtonContent.nativeElement, 'width', '100%'); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private onResize() { |
|||
const height = this.widgetButton.nativeElement.getBoundingClientRect().height; |
|||
const buttonScale = height / initialButtonHeight; |
|||
const paddingScale = Math.min(buttonScale, 1); |
|||
const buttonWidth = this.widgetButton.nativeElement.getBoundingClientRect().width - (horizontalLayoutPadding * paddingScale); |
|||
const buttonHeight = this.widgetButton.nativeElement.getBoundingClientRect().height - (verticalLayoutPadding * paddingScale); |
|||
this.renderer.setStyle(this.widgetButtonContent.nativeElement, 'transform', `scale(1)`); |
|||
this.renderer.setStyle(this.widgetButtonContent.nativeElement, 'width', 'auto'); |
|||
const contentWidth = this.widgetButtonContent.nativeElement.getBoundingClientRect().width; |
|||
const contentHeight = this.widgetButtonContent.nativeElement.getBoundingClientRect().height; |
|||
const maxScale = Math.max(1, buttonScale); |
|||
const scale = Math.min(Math.min(buttonWidth / contentWidth, buttonHeight / contentHeight), maxScale); |
|||
const targetWidth = buttonWidth / scale; |
|||
this.renderer.setStyle(this.widgetButtonContent.nativeElement, 'width', targetWidth + 'px'); |
|||
this.renderer.setStyle(this.widgetButtonContent.nativeElement, 'transform', `scale(${scale})`); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,271 @@ |
|||
///
|
|||
/// 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 { cssUnit } from '@shared/models/widget-settings.models'; |
|||
import tinycolor from 'tinycolor2'; |
|||
|
|||
const defaultMainColor = '#3F52DD'; |
|||
const defaultBackgroundColor = '#FFFFFF'; |
|||
|
|||
const hoveredFilledDarkenAmount = 6; |
|||
const pressedFilledDarkenAmount = 12; |
|||
const activatedFilledDarkenAmount = 12; |
|||
const pressedRippleFilledDarkenAmount = 18; |
|||
|
|||
export const defaultMainColorDisabled = 'rgba(0, 0, 0, 0.38)'; |
|||
export const defaultBackgroundColorDisabled = 'rgba(0, 0, 0, 0.03)'; |
|||
|
|||
const defaultBoxShadowColor = 'rgba(0, 0, 0, 0.08)'; |
|||
const defaultDisabledBoxShadowColor = 'rgba(0, 0, 0, 0)'; |
|||
|
|||
export enum WidgetButtonType { |
|||
outlined = 'outlined', |
|||
filled = 'filled', |
|||
underlined = 'underlined', |
|||
basic = 'basic' |
|||
} |
|||
|
|||
export const widgetButtonTypes = Object.keys(WidgetButtonType) as WidgetButtonType[]; |
|||
|
|||
export const widgetButtonTypeTranslations = new Map<WidgetButtonType, string>( |
|||
[ |
|||
[WidgetButtonType.outlined, 'widgets.button.outlined'], |
|||
[WidgetButtonType.filled, 'widgets.button.filled'], |
|||
[WidgetButtonType.underlined, 'widgets.button.underlined'], |
|||
[WidgetButtonType.basic, 'widgets.button.basic'] |
|||
] |
|||
); |
|||
|
|||
export const widgetButtonTypeImages = new Map<WidgetButtonType, string>( |
|||
[ |
|||
[WidgetButtonType.outlined, 'assets/widget/button/outlined.svg'], |
|||
[WidgetButtonType.filled, 'assets/widget/button/filled.svg'], |
|||
[WidgetButtonType.underlined, 'assets/widget/button/underlined.svg'], |
|||
[WidgetButtonType.basic, 'assets/widget/button/basic.svg'] |
|||
] |
|||
); |
|||
|
|||
export enum WidgetButtonState { |
|||
enabled = 'enabled', |
|||
hovered = 'hovered', |
|||
pressed = 'pressed', |
|||
activated = 'activated', |
|||
disabled = 'disabled' |
|||
} |
|||
|
|||
export const widgetButtonStates = Object.keys(WidgetButtonState) as WidgetButtonState[]; |
|||
|
|||
export const widgetButtonStatesTranslations = new Map<WidgetButtonState, string>( |
|||
[ |
|||
[WidgetButtonState.enabled, 'widgets.button-state.enabled'], |
|||
[WidgetButtonState.hovered, 'widgets.button-state.hovered'], |
|||
[WidgetButtonState.pressed, 'widgets.button-state.pressed'], |
|||
[WidgetButtonState.activated, 'widgets.button-state.activated'], |
|||
[WidgetButtonState.disabled, 'widgets.button-state.disabled'] |
|||
] |
|||
); |
|||
|
|||
export interface WidgetButtonCustomStyle { |
|||
overrideMainColor?: boolean; |
|||
mainColor?: string; |
|||
overrideBackgroundColor?: boolean; |
|||
backgroundColor?: string; |
|||
overrideDropShadow?: boolean; |
|||
dropShadow?: boolean; |
|||
} |
|||
|
|||
export type WidgetButtonCustomStyles = Record<WidgetButtonState, WidgetButtonCustomStyle>; |
|||
|
|||
export interface WidgetButtonAppearance { |
|||
type: WidgetButtonType; |
|||
autoScale: boolean; |
|||
showLabel: boolean; |
|||
label: string; |
|||
showIcon: boolean; |
|||
icon: string; |
|||
iconSize: number; |
|||
iconSizeUnit: cssUnit; |
|||
mainColor: string; |
|||
backgroundColor: string; |
|||
customStyle: WidgetButtonCustomStyles; |
|||
} |
|||
|
|||
export const widgetButtonDefaultAppearance: WidgetButtonAppearance = { |
|||
type: WidgetButtonType.outlined, |
|||
autoScale: true, |
|||
showLabel: true, |
|||
label: 'Button', |
|||
showIcon: true, |
|||
icon: 'home', |
|||
iconSize: 24, |
|||
iconSizeUnit: 'px', |
|||
mainColor: defaultMainColor, |
|||
backgroundColor: defaultBackgroundColor, |
|||
customStyle: { |
|||
enabled: null, |
|||
hovered: null, |
|||
pressed: null, |
|||
activated: null, |
|||
disabled: null |
|||
} |
|||
}; |
|||
|
|||
const mainColorVarPrefix = '--tb-widget-button-main-color-'; |
|||
const backgroundColorVarPrefix = '--tb-widget-button-background-color-'; |
|||
const boxShadowColorVarPrefix = '--tb-widget-button-box-shadow-color-'; |
|||
|
|||
abstract class ButtonStateCssGenerator { |
|||
|
|||
constructor() {} |
|||
|
|||
public generateStateCss(appearance: WidgetButtonAppearance): string { |
|||
let mainColor = this.getMainColor(appearance); |
|||
let backgroundColor = this.getBackgroundColor(appearance); |
|||
const shadowEnabledByDefault = appearance.type !== WidgetButtonType.basic; |
|||
let shadowColor = shadowEnabledByDefault ? defaultBoxShadowColor : defaultDisabledBoxShadowColor; |
|||
const stateCustomStyle = appearance.customStyle[this.state]; |
|||
if (stateCustomStyle?.overrideMainColor && stateCustomStyle?.mainColor) { |
|||
mainColor = stateCustomStyle.mainColor; |
|||
} |
|||
if (stateCustomStyle?.overrideBackgroundColor && stateCustomStyle?.backgroundColor) { |
|||
backgroundColor = stateCustomStyle.backgroundColor; |
|||
} |
|||
if (stateCustomStyle?.overrideDropShadow) { |
|||
shadowColor = !!stateCustomStyle.dropShadow ? defaultBoxShadowColor : defaultDisabledBoxShadowColor; |
|||
} |
|||
|
|||
let css = `${mainColorVarPrefix}${this.state}: ${mainColor};\n`+ |
|||
`${backgroundColorVarPrefix}${this.state}: ${backgroundColor};\n`+ |
|||
`${boxShadowColorVarPrefix}${this.state}: ${shadowColor};`; |
|||
const additionalCss = this.generateAdditionalStateCss(mainColor, backgroundColor); |
|||
if (additionalCss) { |
|||
css += `\n${additionalCss}`; |
|||
} |
|||
return css; |
|||
} |
|||
|
|||
protected abstract get state(): WidgetButtonState; |
|||
|
|||
protected getMainColor(appearance: WidgetButtonAppearance): string { |
|||
return appearance.mainColor || defaultMainColor; |
|||
} |
|||
|
|||
protected getBackgroundColor(appearance: WidgetButtonAppearance): string { |
|||
return appearance.backgroundColor || defaultBackgroundColor; |
|||
} |
|||
|
|||
protected generateAdditionalStateCss(_mainColor: string, _backgroundColor: string): string { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
class EnabledButtonStateCssGenerator extends ButtonStateCssGenerator { |
|||
|
|||
protected get state(): WidgetButtonState { |
|||
return WidgetButtonState.enabled; |
|||
} |
|||
} |
|||
|
|||
class HoveredButtonStateCssGenerator extends ButtonStateCssGenerator { |
|||
|
|||
protected get state(): WidgetButtonState { |
|||
return WidgetButtonState.hovered; |
|||
} |
|||
|
|||
protected generateAdditionalStateCss(mainColor: string): string { |
|||
const mainColorHoveredFilled = darkenColor(mainColor, hoveredFilledDarkenAmount); |
|||
return `--tb-widget-button-main-color-hovered-filled: ${mainColorHoveredFilled};`; |
|||
} |
|||
} |
|||
|
|||
class PressedButtonStateCssGenerator extends ButtonStateCssGenerator { |
|||
|
|||
protected get state(): WidgetButtonState { |
|||
return WidgetButtonState.pressed; |
|||
} |
|||
|
|||
protected generateAdditionalStateCss(mainColor: string): string { |
|||
const mainColorPressedFilled = darkenColor(mainColor, pressedFilledDarkenAmount); |
|||
const mainColorInstance = tinycolor(mainColor); |
|||
const mainColorPressedRipple = mainColorInstance.setAlpha(mainColorInstance.getAlpha() * 0.1).toRgbString(); |
|||
const mainColorPressedRippleFilled = darkenColor(mainColor, pressedRippleFilledDarkenAmount); |
|||
return `--tb-widget-button-main-color-pressed-filled: ${mainColorPressedFilled};\n`+ |
|||
`--tb-widget-button-main-color-pressed-ripple: ${mainColorPressedRipple};\n`+ |
|||
`--tb-widget-button-main-color-pressed-ripple-filled: ${mainColorPressedRippleFilled};`; |
|||
} |
|||
} |
|||
|
|||
class ActivatedButtonStateCssGenerator extends ButtonStateCssGenerator { |
|||
|
|||
protected get state(): WidgetButtonState { |
|||
return WidgetButtonState.activated; |
|||
} |
|||
|
|||
protected generateAdditionalStateCss(mainColor: string): string { |
|||
const mainColorActivatedFilled = darkenColor(mainColor, activatedFilledDarkenAmount); |
|||
return `--tb-widget-button-main-color-activated-filled: ${mainColorActivatedFilled};`; |
|||
} |
|||
} |
|||
|
|||
class DisabledButtonStateCssGenerator extends ButtonStateCssGenerator { |
|||
|
|||
protected get state(): WidgetButtonState { |
|||
return WidgetButtonState.disabled; |
|||
} |
|||
|
|||
protected getMainColor(): string { |
|||
return defaultMainColorDisabled; |
|||
} |
|||
|
|||
protected getBackgroundColor(): string { |
|||
return defaultBackgroundColorDisabled; |
|||
} |
|||
} |
|||
|
|||
const buttonStateCssGeneratorsMap = new Map<WidgetButtonState, ButtonStateCssGenerator>( |
|||
[ |
|||
[WidgetButtonState.enabled, new EnabledButtonStateCssGenerator()], |
|||
[WidgetButtonState.hovered, new HoveredButtonStateCssGenerator()], |
|||
[WidgetButtonState.pressed, new PressedButtonStateCssGenerator()], |
|||
[WidgetButtonState.activated, new ActivatedButtonStateCssGenerator()], |
|||
[WidgetButtonState.disabled, new DisabledButtonStateCssGenerator()] |
|||
] |
|||
); |
|||
|
|||
const widgetButtonCssSelector = '.mat-mdc-button.mat-mdc-button-base.tb-widget-button'; |
|||
|
|||
export const generateWidgetButtonAppearanceCss = (appearance: WidgetButtonAppearance): string => { |
|||
let statesCss = ''; |
|||
for (const state of widgetButtonStates) { |
|||
const generator = buttonStateCssGeneratorsMap.get(state); |
|||
statesCss += `\n${generator.generateStateCss(appearance)}`; |
|||
} |
|||
return `${widgetButtonCssSelector} {\n`+ |
|||
`${statesCss}\n`+ |
|||
`}`; |
|||
}; |
|||
|
|||
const darkenColor = (inputColor: string, amount: number): string => { |
|||
const input = tinycolor(inputColor); |
|||
const brightness = input.getBrightness() / 255; |
|||
let ratio: number; |
|||
if (brightness >= 0.4 && brightness <= 0.5) { |
|||
ratio = brightness + 0.2; |
|||
} else { |
|||
ratio = Math.max(0.1, Math.log10(brightness * 8)); |
|||
} |
|||
return input.darken(ratio * amount).toRgbString(); |
|||
}; |
|||
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
Loading…
Reference in new issue