UI: Widget header button action

This commit is contained in:
Artem Dzhereleiko 2025-03-07 11:09:39 +02:00
parent 06b5706d87
commit bbedb8797b
9 changed files with 260 additions and 16 deletions

View File

@ -88,12 +88,74 @@
</mat-icon>
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div>{{'widget-config.icon' | translate}}</div>
<tb-material-icon-select asBoxInput
formControlName="icon">
</tb-material-icon-select>
</div>
@if (widgetActionFormGroup.get('actionSourceId').value === 'headerButton') {
<div class="tb-form-panel stroked">
<div class="tb-form-panel-title" translate>widget-config.header-button.button-settings</div>
<div class="tb-form-row">
<div class="fixed-title-width" translate>widget-config.header-button.button-type</div>
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-select required formControlName="buttonType" placeholder="{{ 'widget-config.set' | translate }}">
<mat-option *ngFor="let button of widgetHeaderActionButtonTypes" [value]="button">
{{ widgetHeaderActionButtonTypeTranslationMap.get(widgetHeaderActionButtonType[button]) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div class="flex flex-row items-center justify-start gap-2">
<mat-slide-toggle class="mat-slide" formControlName="showIcon">
</mat-slide-toggle>
<div translate>widget-config.icon</div>
</div>
<tb-material-icon-select asBoxInput
formControlName="icon">
</tb-material-icon-select>
</div>
<div class="tb-form-row space-between column-xs">
<div class="fixed-title-width">{{ 'widget-config.header-button.colors' | translate }}</div>
<div class="flex flex-row items-center justify-start gap-2">
<div class="tb-small-label" translate>widget-config.header-button.color</div>
<tb-color-input asBoxInput
formControlName="buttonColor">
</tb-color-input>
<mat-divider vertical></mat-divider>
<div class="tb-small-label" translate>widget-config.header-button.background</div>
<tb-color-input asBoxInput
formControlName="buttonFillColor">
</tb-color-input>
<mat-divider vertical></mat-divider>
<div class="tb-small-label" translate>widget-config.header-button.border</div>
<tb-color-input asBoxInput
formControlName="buttonBorderColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-panel stroked">
<mat-expansion-panel class="tb-settings">
<mat-expansion-panel-header>
<mat-panel-description class="flex items-stretch justify-start" translate>
widget-config.header-button.advanced-button-style
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<tb-json-object-edit
[editorStyle]="{minHeight: '100px'}"
label="{{ 'widget-config.header-button.button-style' | translate }}"
formControlName="customButtonStyle"
></tb-json-object-edit>
</ng-template>
</mat-expansion-panel>
</div>
</div>
} @else {
<div class="tb-form-row space-between">
<div>{{'widget-config.icon' | translate}}</div>
<tb-material-icon-select asBoxInput
formControlName="icon">
</tb-material-icon-select>
</div>
}
<div class="tb-form-panel stroked tb-slide-toggle" *ngIf="displayShowWidgetActionForm()">
<mat-expansion-panel class="tb-settings" [expanded]="widgetActionFormGroup.get('useShowWidgetActionFunction').value"
[disabled]="!widgetActionFormGroup.get('useShowWidgetActionFunction').value">

View File

@ -43,6 +43,10 @@ import {
CellClickColumnInfo,
defaultWidgetAction,
WidgetActionSource,
WidgetHeaderActionButtonType,
widgetHeaderActionButtonTypeMap,
WidgetHeaderActionButtonTypes,
widgetHeaderActionButtonTypeTranslationMap,
widgetType
} from '@shared/models/widget.models';
import { takeUntil } from 'rxjs/operators';
@ -85,6 +89,11 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
configuredColumns: Array<CellClickColumnInfo> = [];
usedCellClickColumns: Array<number> = [];
widgetHeaderActionButtonType = WidgetHeaderActionButtonType
widgetHeaderActionButtonTypes = WidgetHeaderActionButtonTypes;
widgetHeaderActionButtonTypeTranslationMap = widgetHeaderActionButtonTypeTranslationMap;
widgetHeaderActionButtonTypeMap = widgetHeaderActionButtonTypeMap;
@ViewChild('columnIndexSelect') columnIndexSelect: MatSelect;
columnIndexPlaceholderText = this.translate.instant('widget-config.select-column-index');
@ -120,12 +129,19 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
actionSourceId: [this.action.actionSourceId, Validators.required],
columnIndex: [{value: this.checkColumnIndex(this.action.columnIndex), disabled: true}, Validators.required],
name: [this.action.name, [this.validateActionName(), Validators.required]],
buttonType: [isDefinedAndNotNull(this.action.buttonType) ? this.action.buttonType : WidgetHeaderActionButtonType.icon, []],
showIcon: [isDefinedAndNotNull(this.action.showIcon) ? this.action.showIcon : true, []],
icon: [this.action.icon, Validators.required],
buttonColor: [isDefinedAndNotNull(this.action.buttonColor) ? this.action.buttonColor : 'rgba(0, 0, 0, 0.87)', []],
buttonFillColor: [isDefinedAndNotNull(this.action.buttonFillColor) ? this.action.buttonFillColor : '#3F52DD', []],
buttonBorderColor: [isDefinedAndNotNull(this.action.buttonBorderColor) ? this.action.buttonBorderColor : '#3F52DD', []],
customButtonStyle: [isDefinedAndNotNull(this.action.customButtonStyle) ? this.action.customButtonStyle : '', []],
useShowWidgetActionFunction: [this.action.useShowWidgetActionFunction],
showWidgetActionFunction: [this.action.showWidgetActionFunction || 'return true;'],
widgetAction: [actionDescriptorToAction(toWidgetActionDescriptor(this.action)), Validators.required]
});
this.updateShowWidgetActionForm();
this.widgetHeaderButtonValidators();
this.widgetActionFormGroup.get('actionSourceId').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((value) => {
@ -143,6 +159,9 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
).subscribe(() => {
this.updateShowWidgetActionForm();
});
this.widgetActionFormGroup.get('buttonType').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(() => this.widgetHeaderButtonValidators());
setTimeout(() => {
if (this.action?.actionSourceId === 'cellClick') {
this.widgetActionFormGroup.get('columnIndex').enable();
@ -160,6 +179,31 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
super.ngOnDestroy();
}
widgetHeaderButtonValidators() {
const buttonType = this.widgetActionFormGroup.get('buttonType').value;
this.widgetActionFormGroup.get('showIcon').disable({emitEvent: false});
this.widgetActionFormGroup.get('buttonFillColor').disable({emitEvent: false});
this.widgetActionFormGroup.get('buttonBorderColor').disable({emitEvent: false});
switch (buttonType) {
case WidgetHeaderActionButtonType.basic:
this.widgetActionFormGroup.get('showIcon').enable({emitEvent: false});
break;
case WidgetHeaderActionButtonType.raised:
this.widgetActionFormGroup.get('showIcon').enable({emitEvent: false});
this.widgetActionFormGroup.get('buttonFillColor').enable({emitEvent: false});
break;
case WidgetHeaderActionButtonType.stroked:
case WidgetHeaderActionButtonType.flat:
this.widgetActionFormGroup.get('showIcon').enable({emitEvent: false});
this.widgetActionFormGroup.get('buttonFillColor').enable({emitEvent: false});
this.widgetActionFormGroup.get('buttonBorderColor').enable({emitEvent: false});
break;
case WidgetHeaderActionButtonType.miniFab:
this.widgetActionFormGroup.get('buttonFillColor').enable({emitEvent: false});
break;
}
}
displayShowWidgetActionForm(): boolean {
return !!this.data.actionsData.actionSources[this.widgetActionFormGroup.get('actionSourceId').value]?.hasShowCondition;
}

View File

@ -37,14 +37,79 @@
class="tb-widget-actions"
[class]="{'tb-widget-actions-absolute': !(widget.showWidgetTitlePanel && !widgetComponent.widgetContext?.embedTitlePanel && (widget.showTitle||widget.hasAggregation))}"
(mousedown)="$event.stopPropagation()">
<button mat-icon-button *ngFor="let action of widget.customHeaderActions"
type="button"
[class.!hidden]="isEdit"
(click)="action.onAction($event)"
matTooltip="{{ action.displayName }}"
matTooltipPosition="above">
<tb-icon>{{ action.icon }}</tb-icon>
</button>
<div class="flex items-center gap-2">
<ng-container *ngFor="let action of widget.customHeaderActions">
@switch (action.buttonType) {
@case (widgetHeaderActionButtonType.icon) {
<button mat-icon-button
[style]="action.customButtonStyle"
[class.!hidden]="isEdit"
(click)="action.onAction($event)"
matTooltip="{{ action.displayName }}"
matTooltipPosition="above">
<tb-icon [style.color]="action.buttonColor">{{ action.icon }}</tb-icon>
</button>
}
@case (widgetHeaderActionButtonType.miniFab) {
<button mat-mini-fab
[style]="action.customButtonStyle"
[class.!hidden]="isEdit"
[style.background-color]="action.buttonFillColor"
(click)="action.onAction($event)"
matTooltip="{{ action.displayName }}"
matTooltipPosition="above">
<tb-icon [style.color]="action.buttonColor">{{ action.icon }}</tb-icon>
</button>
}
@case (widgetHeaderActionButtonType.basic) {
<button [class.!hidden]="isEdit" mat-button
[style]="action.customButtonStyle"
(click)="action.onAction($event)"
matTooltip="{{ action.displayName }}"
matTooltipPosition="above">
<tb-icon matButtonIcon *ngIf="action.showIcon" [style.color]="action.buttonColor">{{ action.icon }}</tb-icon>
<span [style.color]="action.buttonColor">{{ action.displayName }}</span>
</button>
}
@case (widgetHeaderActionButtonType.raised) {
<button [class.!hidden]="isEdit" mat-raised-button
[style]="action.customButtonStyle"
[style.background-color]="action.buttonFillColor"
(click)="action.onAction($event)"
matTooltip="{{ action.displayName }}"
matTooltipPosition="above">
<tb-icon matButtonIcon *ngIf="action.showIcon" [style.color]="action.buttonColor">{{ action.icon }}</tb-icon>
<span [style.color]="action.buttonColor">{{ action.displayName }}</span>
</button>
}
@case (widgetHeaderActionButtonType.stroked) {
<button [class.!hidden]="isEdit" mat-stroked-button
[style]="action.customButtonStyle"
[style.border-color]="action.buttonBorderColor"
[style.background-color]="action.buttonFillColor"
(click)="action.onAction($event)"
matTooltip="{{ action.displayName }}"
matTooltipPosition="above">
<tb-icon matButtonIcon *ngIf="action.showIcon" [style.color]="action.buttonColor">{{ action.icon }}</tb-icon>
<span [style.color]="action.buttonColor">{{ action.displayName }}</span>
</button>
}
@case (widgetHeaderActionButtonType.flat) {
<button [class.!hidden]="isEdit" mat-flat-button
[style]="action.customButtonStyle"
[style.background-color]="action.buttonFillColor"
[style.border-color]="action.buttonBorderColor"
(click)="action.onAction($event)"
matTooltip="{{ action.displayName }}"
matTooltipPosition="above">
<tb-icon matButtonIcon *ngIf="action.showIcon" [style.color]="action.buttonColor">{{ action.icon }}</tb-icon>
<span [style.color]="action.buttonColor">{{ action.displayName }}</span>
</button>
}
}
</ng-container>
</div>
<button mat-icon-button *ngFor="let action of widget.widgetActions"
type="button"
[class.!hidden]="isEdit || !action.show"

View File

@ -87,7 +87,7 @@ div.tb-widget {
place-content: center flex-start;
align-items: center;
z-index: 19;
margin: 5px 0 0;
margin: 5px 0 5px;
&-absolute {
position: absolute;

View File

@ -44,8 +44,9 @@ import { GridsterItemComponent } from 'angular-gridster2';
import { UtilsService } from '@core/services/utils.service';
import { from } from 'rxjs';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance;
import { TbContextMenuEvent } from '@shared/models/jquery-event.models';
import { WidgetHeaderActionButtonType } from '@shared/models/widget.models';
import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance;
export enum WidgetComponentActionType {
MOUSE_DOWN,
@ -130,6 +131,8 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O
return (this.isEditActionEnabled || this.isRemoveActionEnabled || this.isExportActionEnabled) && !this.widget?.isFullscreen;
}
widgetHeaderActionButtonType = WidgetHeaderActionButtonType;
private cssClass: string;
private editWidgetActionsTooltip: ITooltipsterInstance;

View File

@ -295,7 +295,13 @@ export class WidgetComponent extends PageComponent implements OnInit, OnChanges,
const headerAction: WidgetHeaderAction = {
name: descriptor.name,
displayName: descriptor.displayName,
buttonType: descriptor.buttonType,
showIcon: descriptor.showIcon,
icon: descriptor.icon,
buttonColor: descriptor.buttonColor,
buttonFillColor: descriptor.buttonFillColor,
buttonBorderColor: descriptor.buttonBorderColor,
customButtonStyle: descriptor.customButtonStyle,
descriptor,
useShowWidgetHeaderActionFunction,
showWidgetHeaderActionFunction,

View File

@ -26,6 +26,7 @@ import {
WidgetActionSource,
WidgetConfig,
WidgetControllerDescriptor,
WidgetHeaderActionButtonType,
WidgetType,
widgetType,
WidgetTypeDescriptor,
@ -118,6 +119,12 @@ export type ShowWidgetHeaderActionFunction = (ctx: WidgetContext, data: Formatte
export interface WidgetHeaderAction extends IWidgetAction {
displayName: string;
descriptor: WidgetActionDescriptor;
buttonType?: WidgetHeaderActionButtonType;
showIcon?:boolean;
buttonColor?: string;
buttonFillColor?: string;
buttonBorderColor?: string;
customButtonStyle?: string;
useShowWidgetHeaderActionFunction: boolean;
showWidgetHeaderActionFunction: CompiledTbFunction<ShowWidgetHeaderActionFunction>;
}

View File

@ -567,6 +567,35 @@ export interface LegendData {
data: Array<LegendKeyData>;
}
export enum WidgetHeaderActionButtonType {
basic = 'basic',
raised = 'raised',
stroked = 'stroked',
flat = 'flat',
icon = 'icon',
miniFab = 'miniFab'
}
export const WidgetHeaderActionButtonTypes = Object.keys(WidgetHeaderActionButtonType) as WidgetHeaderActionButtonType[];
export const widgetHeaderActionButtonTypeTranslationMap = new Map<WidgetHeaderActionButtonType, string>([
[WidgetHeaderActionButtonType.basic, 'widget-config.header-button.button-type-basic'],
[WidgetHeaderActionButtonType.raised, 'widget-config.header-button.button-type-raised'],
[WidgetHeaderActionButtonType.stroked, 'widget-config.header-button.button-type-stroked'],
[WidgetHeaderActionButtonType.flat, 'widget-config.header-button.button-type-flat'],
[WidgetHeaderActionButtonType.icon, 'widget-config.header-button.button-type-icon'],
[WidgetHeaderActionButtonType.miniFab, 'widget-config.header-button.button-type-mini-fab']
]);
export const widgetHeaderActionButtonTypeMap = new Map<WidgetHeaderActionButtonType, string>([
[WidgetHeaderActionButtonType.basic, 'mat-button'],
[WidgetHeaderActionButtonType.raised, 'mat-raised-button'],
[WidgetHeaderActionButtonType.stroked, 'mat-stroked-button'],
[WidgetHeaderActionButtonType.flat, 'mat-flat-button'],
[WidgetHeaderActionButtonType.icon, 'mat-icon-button'],
[WidgetHeaderActionButtonType.miniFab, 'mat-mini-fab']
]);
export enum WidgetActionType {
doNothing = 'doNothing',
openDashboardState = 'openDashboardState',
@ -718,7 +747,13 @@ export interface WidgetAction extends CustomActionDescriptor {
export interface WidgetActionDescriptor extends WidgetAction {
id: string;
name: string;
buttonType?: WidgetHeaderActionButtonType;
showIcon?: boolean;
icon: string;
buttonColor?: string;
buttonFillColor?: string;
buttonBorderColor?: string;
customButtonStyle?: string;
displayName?: string;
useShowWidgetActionFunction?: boolean;
showWidgetActionFunction?: TbFunction;
@ -729,7 +764,13 @@ export const actionDescriptorToAction = (descriptor: WidgetActionDescriptor): Wi
const result: WidgetActionDescriptor = {...descriptor};
delete result.id;
delete result.name;
delete result.buttonType;
delete result.showIcon;
delete result.icon;
delete result.buttonColor;
delete result.buttonFillColor;
delete result.buttonBorderColor;
delete result.customButtonStyle;
delete result.displayName;
delete result.useShowWidgetActionFunction;
delete result.showWidgetActionFunction;

View File

@ -6537,6 +6537,22 @@
"action-name-required": "Action name is required.",
"action-name-not-unique": "Another action with the same name already exists.\nAction name should be unique within the same action source.",
"action-icon": "Icon",
"header-button": {
"button-settings": "Button settings",
"button-type": "Button type",
"button-type-basic": "Basic",
"button-type-raised": "Raised",
"button-type-stroked": "Stroked",
"button-type-flat": "Flat",
"button-type-icon": "Icon",
"button-type-mini-fab": "FAB",
"colors": "Colors",
"color": "Color",
"background": "Background",
"border": "Border",
"advanced-button-style": "Advanced button style",
"button-style": "Button style"
},
"show-hide-action-using-function": "Show/hide action using function",
"show-action-function": "Show action function",
"action-type": "Type",