UI: Improve multiple attributes input widget.

This commit is contained in:
Igor Kulikov 2023-09-07 18:05:08 +03:00
parent ac6717b99f
commit 244f8239ba
11 changed files with 370 additions and 279 deletions

View File

@ -21,7 +21,18 @@ export class TbMissingTranslationHandler implements MissingTranslationHandler {
handle(params: MissingTranslationHandlerParams) { handle(params: MissingTranslationHandlerParams) {
if (params.key && !params.key.startsWith(customTranslationsPrefix)) { if (params.key && !params.key.startsWith(customTranslationsPrefix)) {
console.warn('Translation for \'' + params.key + '\' doesn\'t exist'); console.warn('Translation for \'' + params.key + '\' doesn\'t exist');
params.translateService.set(params.key, params.key); let translations: any;
const parts = params.key.split('.');
for (let i=parts.length-1; i>=0; i--) {
const newTranslations = {};
if (i === parts.length-1) {
newTranslations[parts[i]] = params.key;
} else {
newTranslations[parts[i]] = translations;
}
translations = newTranslations;
}
params.translateService.setTranslation(params.translateService.currentLang, translations, true);
} }
} }
} }

View File

@ -23,12 +23,11 @@
<fieldset *ngFor="let source of sources" [ngClass]="{'fields-group': settings.showGroupTitle}"> <fieldset *ngFor="let source of sources" [ngClass]="{'fields-group': settings.showGroupTitle}">
<legend class="group-title" *ngIf="settings.showGroupTitle">{{ getGroupTitle(source.datasource) }} <legend class="group-title" *ngIf="settings.showGroupTitle">{{ getGroupTitle(source.datasource) }}
</legend> </legend>
<div class="tb-multiple-input-layout layout-wrap" <div class="tb-multiple-input-layout"
[ngClass]="{'vertical-alignment': isVerticalAlignment || changeAlignment}"> [style]="{'grid-template-columns': 'repeat(' + columns + ', 1fr)', 'column-gap': settings.columnGap + 'px', 'row-gap': settings.rowGap + 'px'}">
<div *ngFor="let key of visibleKeys(source)" <ng-container *ngFor="let key of visibleKeys(source)">
[ngStyle]="{width: (isVerticalAlignment || changeAlignment) ? '100%' : inputWidthSettings}"> <mat-form-field *ngIf="key.settings.dataKeyValueType === 'string'"
<div class="input-field" *ngIf="key.settings.dataKeyValueType === 'string'"> [appearance]="key.settings.appearance" [subscriptSizing]="key.settings.subscriptSizing">
<mat-form-field class="mat-block" appearance="outline" subscriptSizing="dynamic">
<mat-label>{{key.label}}</mat-label> <mat-label>{{key.label}}</mat-label>
<input matInput <input matInput
formControlName="{{key.formId}}" formControlName="{{key.formId}}"
@ -37,20 +36,15 @@
type="text" type="text"
(focus)="key.isFocused = true; focusInputElement($event)" (focus)="key.isFocused = true; focusInputElement($event)"
(blur)="key.isFocused = false; inputChanged(source, key)"> (blur)="key.isFocused = false; inputChanged(source, key)">
<ng-container *ngIf="key.settings.icon || key.settings.safeCustomIcon" matPrefix> <ng-container *ngIf="key.settings.icon || key.settings.safeCustomIcon" matIconPrefix>
<tb-icon *ngIf="!key.settings.safeCustomIcon; else customToggleIcon">{{key.settings.icon}}</tb-icon> <ng-container *ngTemplateOutlet="iconPrefix; context: {key: key}"></ng-container>
<ng-template #customToggleIcon>
<img class="mat-icon" [src]="key.settings.safeCustomIcon" alt="icon">
</ng-template>
</ng-container> </ng-container>
<mat-error *ngIf="multipleInputFormGroup.get(key.formId).hasError('required')"> <mat-error *ngIf="multipleInputFormGroup.get(key.formId).hasError('required')">
{{ getErrorMessageText(key.settings, 'required') }} {{ getErrorMessageText(key.settings, 'required') }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</div> <mat-form-field *ngIf="['double', 'integer'].includes(key.settings.dataKeyValueType)"
<div class="input-field" *ngIf="key.settings.dataKeyValueType === 'double' || [appearance]="key.settings.appearance" [subscriptSizing]="key.settings.subscriptSizing">
key.settings.dataKeyValueType === 'integer'">
<mat-form-field class="mat-block" appearance="outline" subscriptSizing="dynamic">
<mat-label>{{key.label}}</mat-label> <mat-label>{{key.label}}</mat-label>
<input matInput <input matInput
formControlName="{{key.formId}}" formControlName="{{key.formId}}"
@ -62,11 +56,8 @@
max="{{key.settings.maxValue}}" max="{{key.settings.maxValue}}"
(focus)="key.isFocused = true; focusInputElement($event)" (focus)="key.isFocused = true; focusInputElement($event)"
(blur)="key.isFocused = false; inputChanged(source, key)"> (blur)="key.isFocused = false; inputChanged(source, key)">
<ng-container *ngIf="key.settings.icon || key.settings.safeCustomIcon" matPrefix> <ng-container *ngIf="key.settings.icon || key.settings.safeCustomIcon" matIconPrefix>
<tb-icon *ngIf="!key.settings.safeCustomIcon; else customToggleIcon">{{key.settings.icon}}</tb-icon> <ng-container *ngTemplateOutlet="iconPrefix; context: {key: key}"></ng-container>
<ng-template #customToggleIcon>
<img class="mat-icon" [src]="key.settings.safeCustomIcon" alt="icon">
</ng-template>
</ng-container> </ng-container>
<mat-error *ngIf="multipleInputFormGroup.get(key.formId).hasError('required')"> <mat-error *ngIf="multipleInputFormGroup.get(key.formId).hasError('required')">
{{ getErrorMessageText(key.settings,'required') }} {{ getErrorMessageText(key.settings,'required') }}
@ -78,15 +69,8 @@
{{ getErrorMessageText(key.settings,'max') }} {{ getErrorMessageText(key.settings,'max') }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</div> <mat-form-field *ngIf="key.settings.dataKeyValueType === 'JSON'"
<div class="input-field mat-block" *ngIf="key.settings.dataKeyValueType === 'booleanCheckbox'"> [appearance]="key.settings.appearance" [subscriptSizing]="key.settings.subscriptSizing">
<mat-checkbox formControlName="{{key.formId}}"
(change)="inputChanged(source, key)">
<span class="label-wrapper">{{key.label}}</span>
</mat-checkbox>
</div>
<div class="input-field" *ngIf="key.settings.dataKeyValueType === 'JSON'">
<mat-form-field class="mat-block" appearance="outline" subscriptSizing="dynamic">
<mat-label>{{key.label}}</mat-label> <mat-label>{{key.label}}</mat-label>
<input <input
matInput matInput
@ -98,11 +82,8 @@
(focus)="key.isFocused = true; focusInputElement($event)" (focus)="key.isFocused = true; focusInputElement($event)"
(blur)="key.isFocused = false; inputChanged(source, key)" (blur)="key.isFocused = false; inputChanged(source, key)"
/> />
<ng-container *ngIf="key.settings.icon || key.settings.safeCustomIcon" matPrefix> <ng-container *ngIf="key.settings.icon || key.settings.safeCustomIcon" matIconPrefix>
<tb-icon *ngIf="!key.settings.safeCustomIcon; else customToggleIcon">{{key.settings.icon}}</tb-icon> <ng-container *ngTemplateOutlet="iconPrefix; context: {key: key}"></ng-container>
<ng-template #customToggleIcon>
<img class="mat-icon" [src]="key.settings.safeCustomIcon" alt="icon">
</ng-template>
</ng-container> </ng-container>
<button [disabled]="key.settings.isEditable === 'disabled' || key.settings.isEditable === 'readonly'" <button [disabled]="key.settings.isEditable === 'disabled' || key.settings.isEditable === 'readonly'"
type="button" type="button"
@ -117,24 +98,8 @@
{{ getErrorMessageText(key.settings,'invalidJSON') | translate }} {{ getErrorMessageText(key.settings,'invalidJSON') | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</div> <mat-form-field *ngIf="['dateTime','date', 'time'].includes(key.settings.dataKeyValueType)"
<div class="input-field mat-block" *ngIf="key.settings.dataKeyValueType === 'booleanSwitch'"> [appearance]="key.settings.appearance" [subscriptSizing]="key.settings.subscriptSizing">
<mat-slide-toggle formControlName="{{key.formId}}"
[labelPosition]="key.settings.slideToggleLabelPosition"
(change)="inputChanged(source, key)">
<ng-container *ngIf="key.settings.icon || key.settings.safeCustomIcon">
<tb-icon *ngIf="!key.settings.safeCustomIcon; else customToggleIcon">{{key.settings.icon}}</tb-icon>
<ng-template #customToggleIcon>
<img class="mat-icon" [src]="key.settings.safeCustomIcon" alt="icon">
</ng-template>
</ng-container>
<span class="label-wrapper">{{key.label}}</span>
</mat-slide-toggle>
</div>
<div class="input-field mat-block date-time-input" *ngIf="(key.settings.dataKeyValueType === 'dateTime') ||
(key.settings.dataKeyValueType === 'date') ||
(key.settings.dataKeyValueType === 'time')" fxLayout="column">
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-label>{{key.label}}</mat-label> <mat-label>{{key.label}}</mat-label>
<mat-datetimepicker-toggle [for]="datePicker" matPrefix></mat-datetimepicker-toggle> <mat-datetimepicker-toggle [for]="datePicker" matPrefix></mat-datetimepicker-toggle>
<mat-datetimepicker #datePicker type="{{datePickerType(key.settings.dataKeyValueType)}}" <mat-datetimepicker #datePicker type="{{datePickerType(key.settings.dataKeyValueType)}}"
@ -153,9 +118,8 @@
{{ getErrorMessageText(key.settings, 'invalidDate') }} {{ getErrorMessageText(key.settings, 'invalidDate') }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</div> <mat-form-field *ngIf="key.settings.dataKeyValueType === 'select'"
<div class="input-field" *ngIf="key.settings.dataKeyValueType === 'select'"> [appearance]="key.settings.appearance" [subscriptSizing]="key.settings.subscriptSizing">
<mat-form-field class="mat-block" appearance="outline" subscriptSizing="dynamic">
<mat-label>{{key.label}}</mat-label> <mat-label>{{key.label}}</mat-label>
<mat-select formControlName="{{key.formId}}" <mat-select formControlName="{{key.formId}}"
[required]="key.settings.required" [required]="key.settings.required"
@ -167,30 +131,56 @@
{{ getCustomTranslationText(option.label ? option.label : option.value) }} {{ getCustomTranslationText(option.label ? option.label : option.value) }}
</mat-option> </mat-option>
</mat-select> </mat-select>
<ng-container *ngIf="key.settings.icon || key.settings.safeCustomIcon" matIconPrefix>
<ng-container *ngTemplateOutlet="iconPrefix; context: {key: key}"></ng-container>
</ng-container>
<mat-error *ngIf="multipleInputFormGroup.get(key.formId).hasError('required')"> <mat-error *ngIf="multipleInputFormGroup.get(key.formId).hasError('required')">
{{ getErrorMessageText(key.settings, 'required') }} {{ getErrorMessageText(key.settings, 'required') }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
</div> <mat-form-field *ngIf="key.settings.dataKeyValueType === 'color'"
class="color-input" [appearance]="key.settings.appearance" [subscriptSizing]="key.settings.subscriptSizing"
<div class="tb-form-row space-between color-picker-input" *ngIf="key.settings.dataKeyValueType === 'color'"> (click)="colorInput.openColorPickerPopup($event)">
<div class="label-container"> <mat-label>{{key.label}}</mat-label>
<ng-container *ngIf="key.settings.icon || key.settings.safeCustomIcon"> <input matInput
<tb-icon *ngIf="!key.settings.safeCustomIcon; else customToggleIcon">{{key.settings.icon}}</tb-icon> formControlName="{{key.formId}}"
<ng-template #customToggleIcon>
<img class="mat-icon" [src]="key.settings.safeCustomIcon" alt="icon">
</ng-template>
</ng-container>
{{key.label}}
</div>
<tb-color-input asBoxInput
[required]="key.settings.required" [required]="key.settings.required"
[requiredText]="getErrorMessageText(key.settings, 'required')" [readonly]="key.settings.isEditable === 'readonly'"
openOnInput type="text"
formControlName="{{key.formId}}"> (keydown)="$event.preventDefault();">
<ng-container *ngIf="key.settings.icon || key.settings.safeCustomIcon" matIconPrefix>
<ng-container *ngTemplateOutlet="iconPrefix; context: {key: key}"></ng-container>
</ng-container>
<tb-color-input #colorInput asBoxInput matSuffix
colorClearButton
[disabled]="multipleInputFormGroup.get(key.formId).disabled"
[readonly]="key.settings.isEditable === 'readonly'"
[ngModel]="multipleInputFormGroup.get(key.formId).value"
(ngModelChange)="colorChanged(source, key, $event)"
[ngModelOptions]="{ standalone: true }">
</tb-color-input> </tb-color-input>
</div> <mat-error *ngIf="multipleInputFormGroup.get(key.formId).hasError('required')">
</div> {{ getErrorMessageText(key.settings, 'required') }}
</mat-error>
</mat-form-field>
<mat-checkbox *ngIf="key.settings.dataKeyValueType === 'booleanCheckbox'"
formControlName="{{key.formId}}"
(change)="inputChanged(source, key)">
<ng-container *ngIf="key.settings.icon || key.settings.safeCustomIcon">
<ng-container *ngTemplateOutlet="iconPrefix; context: {key: key}"></ng-container>
</ng-container>
<span class="label-wrapper">{{key.label}}</span>
</mat-checkbox>
<mat-slide-toggle *ngIf="key.settings.dataKeyValueType === 'booleanSwitch'"
formControlName="{{key.formId}}"
[labelPosition]="key.settings.slideToggleLabelPosition"
(change)="inputChanged(source, key)">
<ng-container *ngIf="key.settings.icon || key.settings.safeCustomIcon">
<ng-container *ngTemplateOutlet="iconPrefix; context: {key: key}"></ng-container>
</ng-container>
<span class="label-wrapper">{{key.label}}</span>
</mat-slide-toggle>
</ng-container>
</div> </div>
</fieldset> </fieldset>
</div> </div>
@ -216,3 +206,9 @@
</div> </div>
</div> </div>
</form> </form>
<ng-template #iconPrefix let-key="key">
<tb-icon *ngIf="!key.settings.safeCustomIcon; else customToggleIcon">{{key.settings.icon}}</tb-icon>
<ng-template #customToggleIcon>
<img class="mat-icon" [src]="key.settings.safeCustomIcon" alt="icon">
</ng-template>
</ng-template>

View File

@ -38,34 +38,7 @@
} }
.tb-multiple-input-layout { .tb-multiple-input-layout {
display: flex; display: grid;
flex-direction: row;
align-items: start;
gap: 8px;
}
.color-picker-input {
padding: 7px 16px 7px 12px;
margin: 0 10px 22px 0;
border-color: rgba(0, 0, 0, 0.4);
.label-container {
display: flex;
flex-direction: row;
align-items: center;
}
.mat-icon, img {
margin-right: 5px;
}
}
.input-field {
padding-right: 10px;
mat-form-field {
margin-bottom: 5px;
}
} }
.mat-mdc-slide-toggle { .mat-mdc-slide-toggle {
@ -74,17 +47,6 @@
margin-bottom: 8px; margin-bottom: 8px;
} }
.date-time-input {
.mat-mdc-form-field {
width: 100%;
margin: 2px 0;
}
}
.vertical-alignment {
flex-direction: column;
}
&--buttons-container { &--buttons-container {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -113,7 +75,8 @@
:host ::ng-deep { :host ::ng-deep {
.tb-multiple-input { .tb-multiple-input {
.mat-mdc-slide-toggle .mdc-form-field { .mat-mdc-slide-toggle, .mat-mdc-checkbox {
.mdc-form-field {
width: 100%; width: 100%;
& > label { & > label {
display: flex; display: flex;
@ -121,10 +84,24 @@
width: 100%; width: 100%;
margin: 0; margin: 0;
.mat-icon { .mat-icon {
margin-left: 8px;
margin-right: 8px; margin-right: 8px;
} }
} }
} }
} }
.mat-mdc-slide-toggle {
.mdc-form-field {
& > label {
.mat-icon {
margin-left: 8px;
}
}
}
}
.color-input {
.mat-mdc-form-field-icon-suffix {
padding: 0 8px 0 4px;
}
}
}
} }

View File

@ -48,6 +48,7 @@ import {
} from '@shared/components/dialog/json-object-edit-dialog.component'; } from '@shared/components/dialog/json-object-edit-dialog.component';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field';
type FieldAlignment = 'row' | 'column'; type FieldAlignment = 'row' | 'column';
@ -55,7 +56,7 @@ type MultipleInputWidgetDataKeyType = 'server' | 'shared' | 'timeseries';
export type MultipleInputWidgetDataKeyValueType = 'string' | 'double' | 'integer' | export type MultipleInputWidgetDataKeyValueType = 'string' | 'double' | 'integer' |
'JSON' | 'booleanCheckbox' | 'booleanSwitch' | 'JSON' | 'booleanCheckbox' | 'booleanSwitch' |
'dateTime' | 'date' | 'time' | 'select' | 'color'; 'dateTime' | 'date' | 'time' | 'select' | 'color';
type MultipleInputWidgetDataKeyEditableType = 'editable' | 'disabled' | 'readonly'; export type MultipleInputWidgetDataKeyEditableType = 'editable' | 'disabled' | 'readonly';
type ConvertGetValueFunction = (value: any, ctx: WidgetContext) => any; type ConvertGetValueFunction = (value: any, ctx: WidgetContext) => any;
type ConvertSetValueFunction = (value: any, originValue: any, ctx: WidgetContext) => any; type ConvertSetValueFunction = (value: any, originValue: any, ctx: WidgetContext) => any;
@ -71,6 +72,8 @@ interface MultipleInputWidgetSettings {
groupTitle: string; groupTitle: string;
fieldsAlignment: FieldAlignment; fieldsAlignment: FieldAlignment;
fieldsInRow: number; fieldsInRow: number;
columnGap: number;
rowGap: number;
attributesShared?: boolean; attributesShared?: boolean;
} }
@ -88,6 +91,8 @@ interface MultipleInputWidgetDataKeySettings {
isEditable: MultipleInputWidgetDataKeyEditableType; isEditable: MultipleInputWidgetDataKeyEditableType;
disabledOnDataKey: string; disabledOnDataKey: string;
dataKeyHidden: boolean; dataKeyHidden: boolean;
appearance: MatFormFieldAppearance;
subscriptSizing: SubscriptSizing;
step?: number; step?: number;
minValue?: number; minValue?: number;
maxValue?: number; maxValue?: number;
@ -148,8 +153,7 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
private isSavingInProgress = false; private isSavingInProgress = false;
isVerticalAlignment: boolean; isVerticalAlignment: boolean;
inputWidthSettings: string; columns: number;
changeAlignment: boolean;
saveButtonLabel: string; saveButtonLabel: string;
resetButtonLabel: string; resetButtonLabel: string;
@ -228,15 +232,15 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
if (isUndefined(this.settings.fieldsInRow)) { if (isUndefined(this.settings.fieldsInRow)) {
this.settings.fieldsInRow = 2; this.settings.fieldsInRow = 2;
} }
// For backward compatibility
this.isVerticalAlignment = !(this.settings.fieldsAlignment === 'row'); this.isVerticalAlignment = !(this.settings.fieldsAlignment === 'row');
if (isUndefined(this.settings.columnGap)) {
if (!this.isVerticalAlignment && this.settings.fieldsInRow) { this.settings.columnGap = 10;
this.inputWidthSettings = 100 / this.settings.fieldsInRow + '%'; }
if (isUndefined(this.settings.rowGap)) {
this.settings.rowGap = 5;
} }
this.updateWidgetDisplaying(); this.updateColumns();
} }
private updateDatasources() { private updateDatasources() {
@ -284,6 +288,15 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
dataKey.settings.isEditable = 'editable'; dataKey.settings.isEditable = 'editable';
} }
} }
if (isUndefined(dataKey.settings.appearance)) {
dataKey.settings.appearance = 'outline';
}
if (isUndefined(dataKey.settings.subscriptSizing)) {
dataKey.settings.subscriptSizing = 'fixed';
}
// For backward compatibility // For backward compatibility
if (dataKey.settings.dataKeyValueType === 'select') { if (dataKey.settings.dataKeyValueType === 'select') {
@ -387,12 +400,6 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
} }
}); });
} }
} else if (key.settings.dataKeyValueType === 'color') {
formControl.valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe(() => {
this.inputChanged(source, key);
});
} }
this.multipleInputFormGroup.addControl(key.formId, formControl); this.multipleInputFormGroup.addControl(key.formId, formControl);
} }
@ -452,9 +459,8 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
} }
if (key.settings.isEditable === 'editable' && key.settings.disabledOnDataKey) { if (key.settings.isEditable === 'editable' && key.settings.disabledOnDataKey) {
const conditions = data.filter((item) => { const conditions = data.filter((item) =>
return source.datasource === item.datasource && item.dataKey.name === key.settings.disabledOnDataKey; source.datasource === item.datasource && item.dataKey.name === key.settings.disabledOnDataKey);
});
if (conditions && conditions.length) { if (conditions && conditions.length) {
if (conditions[0].data.length) { if (conditions[0].data.length) {
if (conditions[0].data[0][1] === 'false') { if (conditions[0].data[0][1] === 'false') {
@ -494,8 +500,17 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
return data; return data;
} }
private updateWidgetDisplaying() { private updateColumns() {
this.changeAlignment = (this.ctx.$container && this.ctx.$container[0].offsetWidth < 620); const changeAlignment = (this.ctx.$container && this.ctx.$container[0].offsetWidth < 620);
if (changeAlignment) {
this.columns = 1;
} else {
if (!this.isVerticalAlignment && this.settings.fieldsInRow) {
this.columns = this.settings.fieldsInRow;
} else {
this.columns = 1;
}
}
} }
public onDataUpdated() { public onDataUpdated() {
@ -504,7 +519,7 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
} }
private resize() { private resize() {
this.updateWidgetDisplaying(); this.updateColumns();
this.ctx.detectChanges(); this.ctx.detectChanges();
} }
@ -599,6 +614,13 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
} }
} }
public colorChanged(source: MultipleInputWidgetSource, key: MultipleInputWidgetDataKey, color: string) {
this.multipleInputFormGroup.get(key.formId).setValue(color);
this.multipleInputFormGroup.get(key.formId).markAsDirty();
this.multipleInputFormGroup.get(key.formId).markAsTouched();
this.inputChanged(source, key);
}
public saveForm() { public saveForm() {
if (this.settings.showActionButtons) { if (this.settings.showActionButtons) {
this.save(); this.save();
@ -737,7 +759,7 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
this.multipleInputFormGroup.reset(undefined, {emitEvent: false}); this.multipleInputFormGroup.reset(undefined, {emitEvent: false});
this.sources.forEach((source) => { this.sources.forEach((source) => {
for (const key of this.visibleKeys(source)) { for (const key of this.visibleKeys(source)) {
this.multipleInputFormGroup.get(key.formId).patchValue(key.value, {emitEvent: false}); this.multipleInputFormGroup.get(key.formId).patchValue(key.value);
} }
}); });
this.multipleInputFormGroup.markAsPristine(); this.multipleInputFormGroup.markAsPristine();

View File

@ -93,11 +93,36 @@
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field [fxShow]="updateMultipleAttributesKeySettingsForm.get('isEditable').value !== 'disabled'" fxFlex="50" class="mat-block"> <mat-form-field [fxShow]="updateMultipleAttributesKeySettingsForm.get('isEditable').value === 'editable'" fxFlex="50" class="mat-block">
<mat-label translate>widgets.input-widgets.disable-on-datakey-name</mat-label> <mat-label translate>widgets.input-widgets.disable-on-datakey-name</mat-label>
<input matInput formControlName="disabledOnDataKey"> <input matInput formControlName="disabledOnDataKey">
</mat-form-field> </mat-form-field>
</section> </section>
<section *ngIf="!['booleanSwitch', 'booleanCheckbox'].includes(updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value)"
fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
<mat-form-field fxFlex="50" class="mat-block">
<mat-label translate>widgets.input-widgets.field-appearance</mat-label>
<mat-select formControlName="appearance">
<mat-option [value]="'fill'">
{{ 'widgets.input-widgets.appearance-fill' | translate }}
</mat-option>
<mat-option [value]="'outline'">
{{ 'widgets.input-widgets.appearance-outline' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="50" class="mat-block">
<mat-label translate>widgets.input-widgets.subscript-sizing</mat-label>
<mat-select formControlName="subscriptSizing">
<mat-option [value]="'fixed'">
{{ 'widgets.input-widgets.subscript-sizing-fixed' | translate }}
</mat-option>
<mat-option [value]="'dynamic'">
{{ 'widgets.input-widgets.subscript-sizing-dynamic' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</section>
</fieldset> </fieldset>
<fieldset [fxShow]="!updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').value && <fieldset [fxShow]="!updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').value &&
updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value === 'booleanSwitch'" class="fields-group"> updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value === 'booleanSwitch'" class="fields-group">
@ -208,7 +233,9 @@
</mat-form-field> </mat-form-field>
</section> </section>
</fieldset> </fieldset>
<fieldset [fxShow]="!updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').value" class="fields-group"> <fieldset [fxShow]="!updateMultipleAttributesKeySettingsForm.get('dataKeyHidden').value &&
!['date', 'dateTime', 'time'].includes(updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value)"
class="fields-group">
<legend class="group-title" translate>widgets.input-widgets.icon-settings</legend> <legend class="group-title" translate>widgets.input-widgets.icon-settings</legend>
<mat-slide-toggle formControlName="useCustomIcon" class="slide-block"> <mat-slide-toggle formControlName="useCustomIcon" class="slide-block">
{{ 'widgets.input-widgets.use-custom-icon' | translate }} {{ 'widgets.input-widgets.use-custom-icon' | translate }}

View File

@ -24,7 +24,10 @@ import {
dataKeySelectOptionValidator dataKeySelectOptionValidator
} from '@home/components/widget/lib/settings/input/datakey-select-option.component'; } from '@home/components/widget/lib/settings/input/datakey-select-option.component';
import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { MultipleInputWidgetDataKeyValueType } from '@home/components/widget/lib/multiple-input-widget.component'; import {
MultipleInputWidgetDataKeyEditableType,
MultipleInputWidgetDataKeyValueType
} from '@home/components/widget/lib/multiple-input-widget.component';
@Component({ @Component({
selector: 'tb-update-multiple-attributes-key-settings', selector: 'tb-update-multiple-attributes-key-settings',
@ -52,6 +55,8 @@ export class UpdateMultipleAttributesKeySettingsComponent extends WidgetSettings
required: false, required: false,
isEditable: 'editable', isEditable: 'editable',
disabledOnDataKey: '', disabledOnDataKey: '',
appearance: 'outline',
subscriptSizing: 'fixed',
slideToggleLabelPosition: 'after', slideToggleLabelPosition: 'after',
selectOptions: [], selectOptions: [],
@ -92,6 +97,8 @@ export class UpdateMultipleAttributesKeySettingsComponent extends WidgetSettings
required: [settings.required, []], required: [settings.required, []],
isEditable: [settings.isEditable, []], isEditable: [settings.isEditable, []],
disabledOnDataKey: [settings.disabledOnDataKey, []], disabledOnDataKey: [settings.disabledOnDataKey, []],
appearance: [settings.appearance, []],
subscriptSizing: [settings.subscriptSizing, []],
// Slide toggle settings // Slide toggle settings
@ -146,7 +153,7 @@ export class UpdateMultipleAttributesKeySettingsComponent extends WidgetSettings
const dataKeyValueType: MultipleInputWidgetDataKeyValueType = const dataKeyValueType: MultipleInputWidgetDataKeyValueType =
this.updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value; this.updateMultipleAttributesKeySettingsForm.get('dataKeyValueType').value;
const required: boolean = this.updateMultipleAttributesKeySettingsForm.get('required').value; const required: boolean = this.updateMultipleAttributesKeySettingsForm.get('required').value;
const isEditable: string = this.updateMultipleAttributesKeySettingsForm.get('isEditable').value; const isEditable: MultipleInputWidgetDataKeyEditableType = this.updateMultipleAttributesKeySettingsForm.get('isEditable').value;
const useCustomIcon: boolean = this.updateMultipleAttributesKeySettingsForm.get('useCustomIcon').value; const useCustomIcon: boolean = this.updateMultipleAttributesKeySettingsForm.get('useCustomIcon').value;
const useGetValueFunction: boolean = this.updateMultipleAttributesKeySettingsForm.get('useGetValueFunction').value; const useGetValueFunction: boolean = this.updateMultipleAttributesKeySettingsForm.get('useGetValueFunction').value;
const useSetValueFunction: boolean = this.updateMultipleAttributesKeySettingsForm.get('useSetValueFunction').value; const useSetValueFunction: boolean = this.updateMultipleAttributesKeySettingsForm.get('useSetValueFunction').value;
@ -163,10 +170,15 @@ export class UpdateMultipleAttributesKeySettingsComponent extends WidgetSettings
this.updateMultipleAttributesKeySettingsForm.get('useGetValueFunction').enable({emitEvent: false}); this.updateMultipleAttributesKeySettingsForm.get('useGetValueFunction').enable({emitEvent: false});
this.updateMultipleAttributesKeySettingsForm.get('useSetValueFunction').enable({emitEvent: false}); this.updateMultipleAttributesKeySettingsForm.get('useSetValueFunction').enable({emitEvent: false});
if (isEditable !== 'disabled') { if (isEditable === 'editable') {
this.updateMultipleAttributesKeySettingsForm.get('disabledOnDataKey').enable({emitEvent: false}); this.updateMultipleAttributesKeySettingsForm.get('disabledOnDataKey').enable({emitEvent: false});
} }
if (!['booleanSwitch', 'booleanCheckbox'].includes(dataKeyValueType)) {
this.updateMultipleAttributesKeySettingsForm.get('appearance').enable({emitEvent: false});
this.updateMultipleAttributesKeySettingsForm.get('subscriptSizing').enable({emitEvent: false});
}
if (dataKeyValueType === 'booleanSwitch') { if (dataKeyValueType === 'booleanSwitch') {
this.updateMultipleAttributesKeySettingsForm.get('slideToggleLabelPosition').enable({emitEvent: false}); this.updateMultipleAttributesKeySettingsForm.get('slideToggleLabelPosition').enable({emitEvent: false});
} else if (dataKeyValueType === 'select') { } else if (dataKeyValueType === 'select') {

View File

@ -72,7 +72,7 @@
</section> </section>
</fieldset> </fieldset>
<fieldset class="fields-group"> <fieldset class="fields-group">
<legend class="group-title" translate>widgets.input-widgets.fields-alignment</legend> <legend class="group-title" translate>widgets.input-widgets.layout</legend>
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center"> <section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
<mat-form-field fxFlex="50" class="mat-block"> <mat-form-field fxFlex="50" class="mat-block">
<mat-label translate>widgets.input-widgets.fields-alignment</mat-label> <mat-label translate>widgets.input-widgets.fields-alignment</mat-label>
@ -90,5 +90,15 @@
<input matInput type="number" min="1" step="1" formControlName="fieldsInRow"> <input matInput type="number" min="1" step="1" formControlName="fieldsInRow">
</mat-form-field> </mat-form-field>
</section> </section>
<section fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap.gt-xs="8px" fxLayoutAlign.gt-xs="start center">
<mat-form-field fxFlex="50" class="mat-block">
<mat-label translate>widgets.input-widgets.row-gap</mat-label>
<input matInput type="number" min="0" step="1" formControlName="rowGap">
</mat-form-field>
<mat-form-field fxFlex="50" [fxShow]="updateMultipleAttributesWidgetSettingsForm.get('fieldsAlignment').value === 'row'" class="mat-block">
<mat-label translate>widgets.input-widgets.column-gap</mat-label>
<input matInput type="number" min="0" step="1" formControlName="columnGap">
</mat-form-field>
</section>
</fieldset> </fieldset>
</section> </section>

View File

@ -49,7 +49,9 @@ export class UpdateMultipleAttributesWidgetSettingsComponent extends WidgetSetti
showGroupTitle: false, showGroupTitle: false,
groupTitle: '', groupTitle: '',
fieldsAlignment: 'row', fieldsAlignment: 'row',
fieldsInRow: 2 fieldsInRow: 2,
rowGap: 5,
columnGap: 10
}; };
} }
@ -77,6 +79,11 @@ export class UpdateMultipleAttributesWidgetSettingsComponent extends WidgetSetti
fieldsAlignment: [settings.fieldsAlignment, []], fieldsAlignment: [settings.fieldsAlignment, []],
fieldsInRow: [settings.fieldsInRow, [Validators.min(1)]], fieldsInRow: [settings.fieldsInRow, [Validators.min(1)]],
// Layout gap
rowGap: [settings.rowGap, [Validators.min(0)]],
columnGap: [settings.columnGap, [Validators.min(0)]]
}); });
} }
@ -105,13 +112,16 @@ export class UpdateMultipleAttributesWidgetSettingsComponent extends WidgetSetti
} }
if (fieldsAlignment === 'row') { if (fieldsAlignment === 'row') {
this.updateMultipleAttributesWidgetSettingsForm.get('fieldsInRow').enable(); this.updateMultipleAttributesWidgetSettingsForm.get('fieldsInRow').enable();
this.updateMultipleAttributesWidgetSettingsForm.get('columnGap').enable();
} else { } else {
this.updateMultipleAttributesWidgetSettingsForm.get('fieldsInRow').disable(); this.updateMultipleAttributesWidgetSettingsForm.get('fieldsInRow').disable();
this.updateMultipleAttributesWidgetSettingsForm.get('columnGap').disable();
} }
this.updateMultipleAttributesWidgetSettingsForm.get('updateAllValues').updateValueAndValidity({emitEvent}); this.updateMultipleAttributesWidgetSettingsForm.get('updateAllValues').updateValueAndValidity({emitEvent});
this.updateMultipleAttributesWidgetSettingsForm.get('saveButtonLabel').updateValueAndValidity({emitEvent}); this.updateMultipleAttributesWidgetSettingsForm.get('saveButtonLabel').updateValueAndValidity({emitEvent});
this.updateMultipleAttributesWidgetSettingsForm.get('resetButtonLabel').updateValueAndValidity({emitEvent}); this.updateMultipleAttributesWidgetSettingsForm.get('resetButtonLabel').updateValueAndValidity({emitEvent});
this.updateMultipleAttributesWidgetSettingsForm.get('groupTitle').updateValueAndValidity({emitEvent}); this.updateMultipleAttributesWidgetSettingsForm.get('groupTitle').updateValueAndValidity({emitEvent});
this.updateMultipleAttributesWidgetSettingsForm.get('fieldsInRow').updateValueAndValidity({emitEvent}); this.updateMultipleAttributesWidgetSettingsForm.get('fieldsInRow').updateValueAndValidity({emitEvent});
this.updateMultipleAttributesWidgetSettingsForm.get('columnGap').updateValueAndValidity({emitEvent});
} }
} }

View File

@ -40,7 +40,7 @@
class="tb-box-button" class="tb-box-button"
[disabled]="disabled" [disabled]="disabled"
#matButton #matButton
(click)="openColorPickerPopup($event, matButton)"> (click)="openColorPickerPopup($event, matButton._elementRef)">
<div class="tb-color-preview no-margin box" [ngClass]="{'disabled': disabled}"> <div class="tb-color-preview no-margin box" [ngClass]="{'disabled': disabled}">
<div class="tb-color-result" [style]="!disabled ? {background: colorFormGroup.get('color').value} : {}"></div> <div class="tb-color-result" [style]="!disabled ? {background: colorFormGroup.get('color').value} : {}"></div>
</div> </div>

View File

@ -14,7 +14,16 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { ChangeDetectorRef, Component, forwardRef, Input, OnInit, Renderer2, ViewContainerRef } from '@angular/core'; import {
ChangeDetectorRef,
Component,
ElementRef,
forwardRef,
Input,
OnInit,
Renderer2,
ViewContainerRef
} from '@angular/core';
import { PageComponent } from '@shared/components/page.component'; import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
@ -84,6 +93,10 @@ export class ColorInputComponent extends PageComponent implements OnInit, Contro
@Input() @Input()
disabled: boolean; disabled: boolean;
@Input()
@coerceBoolean()
readonly = false;
private modelValue: string; private modelValue: string;
private propagateChange = null; private propagateChange = null;
@ -151,6 +164,7 @@ export class ColorInputComponent extends PageComponent implements OnInit, Contro
showColorPicker($event: MouseEvent) { showColorPicker($event: MouseEvent) {
$event.stopPropagation(); $event.stopPropagation();
if (!this.disabled && !this.readonly) {
this.dialogs.colorPicker(this.colorFormGroup.get('color').value, this.dialogs.colorPicker(this.colorFormGroup.get('color').value,
this.colorClearButton).subscribe( this.colorClearButton).subscribe(
(result) => { (result) => {
@ -163,12 +177,14 @@ export class ColorInputComponent extends PageComponent implements OnInit, Contro
} }
); );
} }
}
openColorPickerPopup($event: Event, matButton: MatButton) { openColorPickerPopup($event: Event, element?: ElementRef) {
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
} }
const trigger = matButton._elementRef.nativeElement; if (!this.disabled && !this.readonly) {
const trigger = element ? element.nativeElement : $event.target;
if (this.popoverService.hasPopover(trigger)) { if (this.popoverService.hasPopover(trigger)) {
this.popoverService.hidePopover(trigger); this.popoverService.hidePopover(trigger);
} else { } else {
@ -190,6 +206,7 @@ export class ColorInputComponent extends PageComponent implements OnInit, Contro
}); });
} }
} }
}
clear() { clear() {
this.colorFormGroup.get('color').patchValue(null, {emitEvent: true}); this.colorFormGroup.get('color').patchValue(null, {emitEvent: true});

View File

@ -5329,6 +5329,9 @@
"input-fields-alignment": "Input fields alignment", "input-fields-alignment": "Input fields alignment",
"input-fields-alignment-column": "Column (default)", "input-fields-alignment-column": "Column (default)",
"input-fields-alignment-row": "Row", "input-fields-alignment-row": "Row",
"layout": "Layout",
"row-gap": "Gap between rows in pixels",
"column-gap": "Gap between columns in pixels",
"latitude-field-required": "Latitude field required", "latitude-field-required": "Latitude field required",
"longitude-field-required": "Longitude field required", "longitude-field-required": "Longitude field required",
"attribute-settings": "Attribute settings", "attribute-settings": "Attribute settings",
@ -5384,6 +5387,12 @@
"ability-to-edit-attribute-disabled": "Disabled", "ability-to-edit-attribute-disabled": "Disabled",
"ability-to-edit-attribute-readonly": "Read-only", "ability-to-edit-attribute-readonly": "Read-only",
"disable-on-datakey-name": "Disable on false value of another datakey (specify datakey name)", "disable-on-datakey-name": "Disable on false value of another datakey (specify datakey name)",
"field-appearance": "Field appearance",
"appearance-fill": "Fill",
"appearance-outline": "Outline",
"subscript-sizing": "Subscript sizing",
"subscript-sizing-fixed": "Fixed",
"subscript-sizing-dynamic": "Dynamic",
"slide-toggle-settings": "Slide toggle settings", "slide-toggle-settings": "Slide toggle settings",
"slide-toggle-label-position": "Slide toggle label position", "slide-toggle-label-position": "Slide toggle label position",
"slide-toggle-label-position-after": "After", "slide-toggle-label-position-after": "After",