Commit a1225636198de80f243c47b5de5ef6a73e25a1e5

Authored by Igor Kulikov
1 parent 19dac7d5

Propagate UI changes

Showing 25 changed files with 470 additions and 108 deletions
... ... @@ -395,7 +395,7 @@ export class UtilsService {
395 395 } else if (variableName === 'deviceName') {
396 396 label = label.split(variable).join(datasource.entityName);
397 397 } else if (variableName === 'entityLabel') {
398   - label = label.split(variable).join(datasource.entityLabel);
  398 + label = label.split(variable).join(datasource.entityLabel || datasource.entityName);
399 399 } else if (variableName === 'aliasName') {
400 400 label = label.split(variable).join(datasource.aliasName);
401 401 } else if (variableName === 'entityDescription') {
... ...
... ... @@ -82,14 +82,19 @@
82 82 <div *ngIf="widget.hasWidgetTitleTemplate">
83 83 TODO:
84 84 </div>
85   - <span [fxShow]="widget.showTitle" [ngStyle]="widget.titleStyle" class="mat-subheading-2 title">
  85 + <span [fxShow]="widget.showTitle"
  86 + [ngStyle]="widget.titleStyle"
  87 + [matTooltip]="widget.titleTooltip"
  88 + matTooltipPosition="above"
  89 + class="mat-subheading-2 title">
86 90 <mat-icon *ngIf="widget.showTitleIcon" [ngStyle]="widget.titleIconStyle">{{widget.titleIcon}}</mat-icon>
87 91 {{widget.title}}
88 92 </span>
89 93 <tb-timewindow *ngIf="widget.hasTimewindow"
90 94 #timewindowComponent
91 95 aggregation="{{widget.hasAggregation}}"
92   - [ngModel]="widgetComponent.widget.config.timewindow"
  96 + [isEdit]="isEdit"
  97 + [(ngModel)]="widgetComponent.widget.config.timewindow"
93 98 (ngModelChange)="widgetComponent.onTimewindowChanged($event)">
94 99 </tb-timewindow>
95 100 </div>
... ...
... ... @@ -32,6 +32,7 @@
32 32 <section fxFlex fxLayout="row" fxLayoutAlign="start center" style="margin-bottom: 16px;">
33 33 <span [ngClass]="{'tb-disabled-label': dataSettings.get('useDashboardTimewindow').value}" translate style="padding-right: 8px;">widget-config.timewindow</span>
34 34 <tb-timewindow asButton="true"
  35 + isEdit="true"
35 36 aggregation="{{ widgetType === widgetTypes.timeseries }}"
36 37 fxFlex formControlName="timewindow"></tb-timewindow>
37 38 </section>
... ... @@ -307,6 +308,10 @@
307 308 <mat-label translate>widget-config.icon-size</mat-label>
308 309 <input matInput formControlName="iconSize">
309 310 </mat-form-field>
  311 + <mat-form-field fxFlex>
  312 + <mat-label translate>widget-config.title-tooltip</mat-label>
  313 + <input matInput formControlName="titleTooltip">
  314 + </mat-form-field>
310 315 </div>
311 316 <div fxLayout="column" fxLayoutAlign="center" fxLayout.gt-sm="row" fxLayoutAlign.gt-sm="start center"
312 317 fxLayoutGap="8px">
... ...
... ... @@ -173,6 +173,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
173 173 titleIcon: [null, []],
174 174 iconColor: [null, []],
175 175 iconSize: [null, []],
  176 + titleTooltip: [null, []],
176 177 showTitle: [null, []],
177 178 dropShadow: [null, []],
178 179 enableFullscreen: [null, []],
... ... @@ -344,6 +345,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
344 345 titleIcon: isDefined(config.titleIcon) ? config.titleIcon : '',
345 346 iconColor: isDefined(config.iconColor) ? config.iconColor : 'rgba(0, 0, 0, 0.87)',
346 347 iconSize: isDefined(config.iconSize) ? config.iconSize : '24px',
  348 + titleTooltip: isDefined(config.titleTooltip) ? config.titleTooltip : '',
347 349 showTitle: config.showTitle,
348 350 dropShadow: isDefined(config.dropShadow) ? config.dropShadow : true,
349 351 enableFullscreen: isDefined(config.enableFullscreen) ? config.enableFullscreen : true,
... ...
... ... @@ -1052,9 +1052,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
1052 1052 if (e.id) {
1053 1053 const descriptors = this.getActionDescriptors('elementClick');
1054 1054 if (descriptors.length) {
1055   - $event.stopPropagation();
1056 1055 descriptors.forEach((descriptor) => {
1057 1056 if (descriptor.name === e.id) {
  1057 + $event.stopPropagation();
1058 1058 const entityInfo = this.getActiveEntityInfo();
1059 1059 const entityId = entityInfo ? entityInfo.entityId : null;
1060 1060 const entityName = entityInfo ? entityInfo.entityName : null;
... ...
... ... @@ -295,6 +295,7 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
295 295 margin: string;
296 296
297 297 title: string;
  298 + titleTooltip: string;
298 299 showTitle: boolean;
299 300 titleStyle: {[klass: string]: any};
300 301
... ... @@ -360,6 +361,8 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget {
360 361
361 362 this.title = isDefined(this.widgetContext.widgetTitle)
362 363 && this.widgetContext.widgetTitle.length ? this.widgetContext.widgetTitle : this.widget.config.title;
  364 + this.titleTooltip = isDefined(this.widgetContext.widgetTitleTooltip)
  365 + && this.widgetContext.widgetTitleTooltip.length ? this.widgetContext.widgetTitleTooltip : this.widget.config.titleTooltip;
363 366 this.showTitle = isDefined(this.widget.config.showTitle) ? this.widget.config.showTitle : true;
364 367 this.titleStyle = this.widget.config.titleStyle ? this.widget.config.titleStyle : {};
365 368
... ...
... ... @@ -178,6 +178,7 @@ export class WidgetContext {
178 178
179 179 widgetTitleTemplate?: string;
180 180 widgetTitle?: string;
  181 + widgetTitleTooltip?: string;
181 182 customHeaderActions?: Array<WidgetHeaderAction>;
182 183 widgetActions?: Array<WidgetAction>;
183 184
... ...
... ... @@ -55,6 +55,7 @@
55 55 </button>
56 56 <tb-timewindow [fxShow]="isEdit || displayDashboardTimewindow()"
57 57 isToolbar="true"
  58 + [isEdit]="isEdit"
58 59 direction="left"
59 60 tooltipPosition="below"
60 61 aggregation="true"
... ...
... ... @@ -87,6 +87,7 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato
87 87 },
88 88 onModelChange: this.onModelChange.bind(this),
89 89 onColorClick: this.onColorClick.bind(this),
  90 + onIconClick: this.onIconClick.bind(this),
90 91 onToggleFullscreen: this.onToggleFullscreen.bind(this)
91 92 };
92 93
... ... @@ -201,6 +202,16 @@ export class JsonFormComponent implements OnInit, ControlValueAccessor, Validato
201 202 });
202 203 }
203 204
  205 + private onIconClick(key: (string | number)[],
  206 + val: string,
  207 + iconSelectedFn: (icon: string) => void) {
  208 + this.dialogs.materialIconPicker(val).subscribe((icon) => {
  209 + if (icon && iconSelectedFn) {
  210 + iconSelectedFn(icon);
  211 + }
  212 + });
  213 + }
  214 +
204 215 private onToggleFullscreen(element: HTMLElement, fullscreenFinishFn?: () => void) {
205 216 this.targetFullscreenElement = element;
206 217 this.isFullscreen = !this.isFullscreen;
... ...
... ... @@ -143,7 +143,7 @@ class ThingsboardArray extends React.Component<JsonFormFieldProps, ThingsboardAr
143 143 const forms = (this.props.form.items as JsonFormData[]).map((form, index) => {
144 144 const copy = this.copyWithIndex(form, i);
145 145 return this.props.builder(copy, this.props.model, index, this.props.onChange,
146   - this.props.onColorClick, this.props.onToggleFullscreen, this.props.mapper);
  146 + this.props.onColorClick, this.props.onIconClick, this.props.onToggleFullscreen, this.props.mapper);
147 147 });
148 148 arrays.push(
149 149 <li key={keys[i]} className='list-group-item'>
... ...
... ... @@ -25,7 +25,7 @@ class ThingsboardFieldSet extends React.Component<JsonFormFieldProps, JsonFormFi
25 25 render() {
26 26 const forms = (this.props.form.items as JsonFormData[]).map((form: JsonFormData, index) => {
27 27 return this.props.builder(form, this.props.model, index, this.props.onChange,
28   - this.props.onColorClick, this.props.onToggleFullscreen, this.props.mapper);
  28 + this.props.onColorClick, this.props.onIconClick, this.props.onToggleFullscreen, this.props.mapper);
29 29 });
30 30
31 31 return (
... ...
  1 +/*
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +import * as React from 'react';
  17 +import * as ReactDOM from 'react-dom';
  18 +import ThingsboardBaseComponent from './json-form-base-component';
  19 +import reactCSS from 'reactcss';
  20 +import TextField from '@material-ui/core/TextField';
  21 +import IconButton from '@material-ui/core/IconButton';
  22 +import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
  23 +import ClearIcon from '@material-ui/icons/Clear';
  24 +import Icon from '@material-ui/core/Icon';
  25 +import Tooltip from '@material-ui/core/Tooltip';
  26 +
  27 +interface ThingsboardIconState extends JsonFormFieldState {
  28 + icon: string | null;
  29 + focused: boolean;
  30 +}
  31 +
  32 +class ThingsboardIcon extends React.Component<JsonFormFieldProps, ThingsboardIconState> {
  33 +
  34 + constructor(props) {
  35 + super(props);
  36 + this.onBlur = this.onBlur.bind(this);
  37 + this.onFocus = this.onFocus.bind(this);
  38 + this.onValueChanged = this.onValueChanged.bind(this);
  39 + this.onIconClick = this.onIconClick.bind(this);
  40 + this.onClear = this.onClear.bind(this);
  41 + const icon = props.value ? props.value : '';
  42 + this.state = {
  43 + icon,
  44 + focused: false
  45 + };
  46 + }
  47 +
  48 + onBlur() {
  49 + this.setState({focused: false});
  50 + }
  51 +
  52 + onFocus() {
  53 + this.setState({focused: true});
  54 + }
  55 +
  56 + componentDidMount() {
  57 + const node = ReactDOM.findDOMNode(this);
  58 + const iconContainer = $(node).children('#icon-container');
  59 + iconContainer.click((event) => {
  60 + if (!this.props.form.readonly) {
  61 + this.onIconClick(event);
  62 + }
  63 + });
  64 + }
  65 +
  66 + componentWillUnmount () {
  67 + const node = ReactDOM.findDOMNode(this);
  68 + const iconContainer = $(node).children('#icon-container');
  69 + iconContainer.off( 'click' );
  70 + }
  71 +
  72 + onValueChanged(value: string | null) {
  73 + const icon = value;
  74 + this.setState({
  75 + icon: value
  76 + });
  77 + this.props.onChange(this.props.form.key, value);
  78 + }
  79 +
  80 + onIconClick(event) {
  81 + this.props.onIconClick(this.props.form.key, this.state.icon,
  82 + (color) => {
  83 + this.onValueChanged(color);
  84 + }
  85 + );
  86 + }
  87 +
  88 + onClear(event) {
  89 + if (event) {
  90 + event.stopPropagation();
  91 + }
  92 + this.onValueChanged('');
  93 + }
  94 +
  95 + render() {
  96 +
  97 + const styles = reactCSS({
  98 + default: {
  99 + container: {
  100 + display: 'flex',
  101 + flexDirection: 'row',
  102 + alignItems: 'center'
  103 + },
  104 + icon: {
  105 + marginRight: '10px',
  106 + marginBottom: 'auto',
  107 + cursor: 'pointer',
  108 + border: 'solid 1px rgba(0, 0, 0, .27)',
  109 + borderRadius: '0'
  110 + },
  111 + iconContainer: {
  112 + display: 'flex',
  113 + width: '100%'
  114 + },
  115 + iconText: {
  116 + width: '100%'
  117 + },
  118 + },
  119 + });
  120 +
  121 + let fieldClass = 'tb-field';
  122 + if (this.props.form.required) {
  123 + fieldClass += ' tb-required';
  124 + }
  125 + if (this.state.focused) {
  126 + fieldClass += ' tb-focused';
  127 + }
  128 +
  129 + let pickedIcon = 'more_horiz';
  130 + let icon = '';
  131 + if (this.state.icon !== '') {
  132 + pickedIcon = this.state.icon;
  133 + icon = this.state.icon;
  134 + }
  135 +
  136 + return (
  137 + <div style={ styles.container }>
  138 + <div id='icon-container' style={ styles.iconContainer }>
  139 + <IconButton style={ styles.icon }>
  140 + <Icon>{pickedIcon}</Icon>
  141 + </IconButton>
  142 + <TextField
  143 + className={fieldClass}
  144 + label={this.props.form.title}
  145 + error={!this.props.valid}
  146 + helperText={this.props.valid ? this.props.form.placeholder : this.props.error}
  147 + value={icon}
  148 + disabled={this.props.form.readonly}
  149 + onFocus={this.onFocus}
  150 + onBlur={this.onBlur}
  151 + style={ styles.iconText } />
  152 + </div>
  153 + <Tooltip title='Clear' placement='top'><IconButton onClick={this.onClear}><ClearIcon/></IconButton></Tooltip>
  154 + </div>
  155 + );
  156 + }
  157 +}
  158 +
  159 +export default ThingsboardBaseComponent(ThingsboardIcon);
... ...
... ... @@ -32,7 +32,8 @@ import ThingsboardImage from './json-form-image';
32 32 import ThingsboardCheckbox from './json-form-checkbox';
33 33 import ThingsboardHelp from './json-form-help';
34 34 import ThingsboardFieldSet from './json-form-fieldset';
35   -import { JsonFormProps, JsonFormData, onChangeFn, OnColorClickFn } from './json-form.models';
  35 +import ThingsboardIcon from './json-form-icon';
  36 +import { JsonFormProps, JsonFormData, onChangeFn, OnColorClickFn, OnIconClickFn } from './json-form.models';
36 37
37 38 import _ from 'lodash';
38 39 import * as tinycolor_ from 'tinycolor2';
... ... @@ -65,11 +66,13 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> {
65 66 css: ThingsboardCss,
66 67 color: ThingsboardColor,
67 68 'rc-select': ThingsboardRcSelect,
68   - fieldset: ThingsboardFieldSet
  69 + fieldset: ThingsboardFieldSet,
  70 + icon: ThingsboardIcon
69 71 };
70 72
71 73 this.onChange = this.onChange.bind(this);
72 74 this.onColorClick = this.onColorClick.bind(this);
  75 + this.onIconClick = this.onIconClick.bind(this);
73 76 this.onToggleFullscreen = this.onToggleFullscreen.bind(this);
74 77 this.hasConditions = false;
75 78 }
... ... @@ -86,6 +89,11 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> {
86 89 this.props.onColorClick(key, val, colorSelectedFn);
87 90 }
88 91
  92 + onIconClick(key: (string | number)[], val: string,
  93 + iconSelectedFn: (icon: string) => void) {
  94 + this.props.onIconClick(key, val, iconSelectedFn);
  95 + }
  96 +
89 97 onToggleFullscreen(element: HTMLElement, fullscreenFinishFn?: () => void) {
90 98 this.props.onToggleFullscreen(element, fullscreenFinishFn);
91 99 }
... ... @@ -96,6 +104,7 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> {
96 104 index: number,
97 105 onChange: onChangeFn,
98 106 onColorClick: OnColorClickFn,
  107 + onIconClick: OnIconClickFn,
99 108 onToggleFullscreen: () => void,
100 109 mapper: {[type: string]: any}): JSX.Element {
101 110 const type = form.type;
... ... @@ -113,6 +122,7 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> {
113 122 }
114 123 return <Field model={model} form={form} key={index} onChange={onChange}
115 124 onColorClick={onColorClick}
  125 + onIconClick={onIconClick}
116 126 onToggleFullscreen={onToggleFullscreen}
117 127 mapper={mapper} builder={this.builder}/>;
118 128 }
... ... @@ -124,7 +134,8 @@ class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> {
124 134 mapper = _.merge(this.mapper, this.props.mapper);
125 135 }
126 136 const forms = merged.map(function(form, index) {
127   - return this.builder(form, this.props.model, index, this.onChange, this.onColorClick, this.onToggleFullscreen, mapper);
  137 + return this.builder(form, this.props.model, index, this.onChange, this.onColorClick,
  138 + this.onIconClick, this.onToggleFullscreen, mapper);
128 139 }.bind(this));
129 140
130 141 let formClass = 'SchemaForm';
... ...
... ... @@ -49,6 +49,8 @@ export interface DefaultsFormOptions {
49 49 export type onChangeFn = (key: (string | number)[], val: any, forceUpdate?: boolean) => void;
50 50 export type OnColorClickFn = (key: (string | number)[], val: tinycolor.ColorFormats.RGBA,
51 51 colorSelectedFn: (color: tinycolor.ColorFormats.RGBA) => void) => void;
  52 +export type OnIconClickFn = (key: (string | number)[], val: string,
  53 + iconSelectedFn: (icon: string) => void) => void;
52 54 export type onToggleFullscreenFn = (element: HTMLElement, fullscreenFinishFn?: () => void) => void;
53 55
54 56 export interface JsonFormProps {
... ... @@ -61,6 +63,7 @@ export interface JsonFormProps {
61 63 option: FormOption;
62 64 onModelChange?: onChangeFn;
63 65 onColorClick?: OnColorClickFn;
  66 + onIconClick?: OnIconClickFn;
64 67 onToggleFullscreen?: onToggleFullscreenFn;
65 68 mapper?: {[type: string]: any};
66 69 }
... ... @@ -107,6 +110,7 @@ export type ComponentBuilderFn = (form: JsonFormData,
107 110 index: number,
108 111 onChange: onChangeFn,
109 112 onColorClick: OnColorClickFn,
  113 + onIconClick: OnIconClickFn,
110 114 onToggleFullscreen: onToggleFullscreenFn,
111 115 mapper: {[type: string]: any}) => JSX.Element;
112 116
... ... @@ -118,6 +122,7 @@ export interface JsonFormFieldProps {
118 122 mapper?: {[type: string]: any};
119 123 onChange?: onChangeFn;
120 124 onColorClick?: OnColorClickFn;
  125 + onIconClick?: OnIconClickFn;
121 126 onChangeValidate?: (e: any, forceUpdate?: boolean) => void;
122 127 onToggleFullscreen?: onToggleFullscreenFn;
123 128 valid?: boolean;
... ...
... ... @@ -21,13 +21,13 @@
21 21 <mat-placeholder translate>datetime.date-from</mat-placeholder>
22 22 <mat-datetimepicker-toggle [for]="startDatePicker" matPrefix></mat-datetimepicker-toggle>
23 23 <mat-datetimepicker #startDatePicker type="date" openOnFocus="true"></mat-datetimepicker>
24   - <input matInput [(ngModel)]="startDate" [matDatetimepicker]="startDatePicker" (ngModelChange)="onStartDateChange()">
  24 + <input matInput [disabled]="disabled" [(ngModel)]="startDate" [matDatetimepicker]="startDatePicker" (ngModelChange)="onStartDateChange()">
25 25 </mat-form-field>
26 26 <mat-form-field>
27 27 <mat-placeholder translate>datetime.time-from</mat-placeholder>
28 28 <mat-datetimepicker-toggle [for]="startTimePicker" matPrefix></mat-datetimepicker-toggle>
29 29 <mat-datetimepicker #startTimePicker type="time" openOnFocus="true"></mat-datetimepicker>
30   - <input matInput [(ngModel)]="startDate" [matDatetimepicker]="startTimePicker" (ngModelChange)="onStartDateChange()">
  30 + <input matInput [disabled]="disabled" [(ngModel)]="startDate" [matDatetimepicker]="startTimePicker" (ngModelChange)="onStartDateChange()">
31 31 </mat-form-field>
32 32 </section>
33 33 <section fxLayout="row" fxLayoutAlign="start start" fxLayoutGap="16px">
... ... @@ -35,13 +35,13 @@
35 35 <mat-placeholder translate>datetime.date-to</mat-placeholder>
36 36 <mat-datetimepicker-toggle [for]="endDatePicker" matPrefix></mat-datetimepicker-toggle>
37 37 <mat-datetimepicker #endDatePicker type="date" openOnFocus="true"></mat-datetimepicker>
38   - <input matInput [(ngModel)]="endDate" [matDatetimepicker]="endDatePicker" (ngModelChange)="onEndDateChange()">
  38 + <input matInput [disabled]="disabled" [(ngModel)]="endDate" [matDatetimepicker]="endDatePicker" (ngModelChange)="onEndDateChange()">
39 39 </mat-form-field>
40 40 <mat-form-field>
41 41 <mat-placeholder translate>datetime.time-to</mat-placeholder>
42 42 <mat-datetimepicker-toggle [for]="endTimePicker" matPrefix></mat-datetimepicker-toggle>
43 43 <mat-datetimepicker #endTimePicker type="time" openOnFocus="true"></mat-datetimepicker>
44   - <input matInput [(ngModel)]="endDate" [matDatetimepicker]="endTimePicker" (ngModelChange)="onEndDateChange()">
  44 + <input matInput [disabled]="disabled" [(ngModel)]="endDate" [matDatetimepicker]="endTimePicker" (ngModelChange)="onEndDateChange()">
45 45 </mat-form-field>
46 46 </section>
47 47 </section>
... ...
... ... @@ -16,39 +16,43 @@
16 16
17 17 -->
18 18 <section fxLayout="row">
19   - <section class="interval-section" fxLayout="column" fxFlex [fxShow]="advanced">
  19 + <section fxLayout="column" [fxShow]="isEdit">
  20 + <label class="tb-small hide-label" translate>timewindow.hide</label>
  21 + <mat-checkbox [(ngModel)]="hideFlag" (ngModelChange)="onHideFlagChange()"></mat-checkbox>
  22 + </section>
  23 + <section class="interval-section" fxLayout="column" fxFlex [fxShow]="advanced && (isEdit || !hideFlag)">
20 24 <label class="tb-small interval-label" translate>{{ predefinedName }}</label>
21 25 <section fxLayout="row" fxLayoutAlign="start start" fxFlex fxLayoutGap="6px">
22 26 <mat-form-field class="number-input">
23 27 <mat-label translate>timeinterval.days</mat-label>
24   - <input matInput type="number" step="1" min="0" [(ngModel)]="days" (ngModelChange)="onTimeInputChange('days')"/>
  28 + <input matInput [disabled]="hideFlag || disabled" type="number" step="1" min="0" [(ngModel)]="days" (ngModelChange)="onTimeInputChange('days')"/>
25 29 </mat-form-field>
26 30 <mat-form-field class="number-input">
27 31 <mat-label translate>timeinterval.hours</mat-label>
28   - <input matInput type="number" step="1" [(ngModel)]="hours" (ngModelChange)="onTimeInputChange('hours')"/>
  32 + <input matInput [disabled]="hideFlag || disabled" type="number" step="1" [(ngModel)]="hours" (ngModelChange)="onTimeInputChange('hours')"/>
29 33 </mat-form-field>
30 34 <mat-form-field class="number-input">
31 35 <mat-label translate>timeinterval.minutes</mat-label>
32   - <input matInput type="number" step="1" [(ngModel)]="mins" (ngModelChange)="onTimeInputChange('mins')"/>
  36 + <input matInput [disabled]="hideFlag || disabled" type="number" step="1" [(ngModel)]="mins" (ngModelChange)="onTimeInputChange('mins')"/>
33 37 </mat-form-field>
34 38 <mat-form-field class="number-input">
35 39 <mat-label translate>timeinterval.seconds</mat-label>
36   - <input matInput type="number" step="1" [(ngModel)]="secs" (ngModelChange)="onTimeInputChange('secs')"/>
  40 + <input matInput [disabled]="hideFlag || disabled" type="number" step="1" [(ngModel)]="secs" (ngModelChange)="onTimeInputChange('secs')"/>
37 41 </mat-form-field>
38 42 </section>
39 43 </section>
40   - <section class="interval-section" fxLayout="row" fxFlex [fxShow]="!advanced">
  44 + <section class="interval-section" fxLayout="row" fxFlex [fxShow]="!advanced && (isEdit || !hideFlag)">
41 45 <mat-form-field fxFlex>
42 46 <mat-label translate>{{ predefinedName }}</mat-label>
43   - <mat-select matInput [(ngModel)]="intervalMs" (ngModelChange)="onIntervalMsChange()" style="min-width: 150px;">
  47 + <mat-select matInput [disabled]="hideFlag || disabled" [(ngModel)]="intervalMs" (ngModelChange)="onIntervalMsChange()" style="min-width: 150px;">
44 48 <mat-option *ngFor="let interval of intervals" [value]="interval.value">
45 49 {{ interval.name | translate:interval.translateParams }}
46 50 </mat-option>
47 51 </mat-select>
48 52 </mat-form-field>
49 53 </section>
50   - <section fxLayout="column" fxLayoutAlign="center center">
  54 + <section fxLayout="column" fxLayoutAlign="center center" [fxShow]="(isEdit || !hideFlag)">
51 55 <label class="tb-small advanced-label" translate>timeinterval.advanced</label>
52   - <mat-slide-toggle class="advanced-switch" [(ngModel)]="advanced" (ngModelChange)="onAdvancedChange()"></mat-slide-toggle>
  56 + <mat-slide-toggle [disabled]="hideFlag || disabled" class="advanced-switch" [(ngModel)]="advanced" (ngModelChange)="onAdvancedChange()"></mat-slide-toggle>
53 57 </section>
54 58 </section>
... ...
... ... @@ -24,6 +24,11 @@
24 24 margin: 5px 0;
25 25 }
26 26
  27 + .hide-label {
  28 + margin-bottom: 5px;
  29 + margin-right: 5px;
  30 + }
  31 +
27 32 .interval-section {
28 33 min-height: 66px;
29 34 .interval-label {
... ...
... ... @@ -14,7 +14,7 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import { ChangeDetectorRef, Component, forwardRef, Input, OnInit } from '@angular/core';
  17 +import { ChangeDetectorRef, Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core';
18 18 import {
19 19 ControlValueAccessor,
20 20 FormControl,
... ... @@ -24,6 +24,7 @@ import {
24 24 } from '@angular/forms';
25 25 import { Timewindow } from '@shared/models/time/time.models';
26 26 import { TimeInterval, TimeService } from '@core/services/time.service';
  27 +import { coerceBooleanProperty } from '@angular/cdk/coercion';
27 28
28 29 @Component({
29 30 selector: 'tb-timeinterval',
... ... @@ -61,6 +62,31 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor {
61 62 }
62 63
63 64 @Input() predefinedName: string;
  65 +
  66 + isEditValue = false;
  67 +
  68 + @Input()
  69 + set isEdit(val) {
  70 + this.isEditValue = coerceBooleanProperty(val);
  71 + }
  72 +
  73 + get isEdit() {
  74 + return this.isEditValue;
  75 + }
  76 +
  77 + hideFlagValue = false;
  78 +
  79 + @Input()
  80 + get hideFlag() {
  81 + return this.hideFlagValue;
  82 + }
  83 +
  84 + set hideFlag(val) {
  85 + this.hideFlagValue = val;
  86 + }
  87 +
  88 + @Output() hideFlagChange = new EventEmitter<boolean>();
  89 +
64 90 @Input() disabled: boolean;
65 91
66 92 days = 0;
... ... @@ -189,6 +215,10 @@ export class TimeintervalComponent implements OnInit, ControlValueAccessor {
189 215 this.updateView();
190 216 }
191 217
  218 + onHideFlagChange() {
  219 + this.hideFlagChange.emit(this.hideFlagValue);
  220 + }
  221 +
192 222 onTimeInputChange(type: string) {
193 223 switch (type) {
194 224 case 'secs':
... ...
... ... @@ -23,6 +23,9 @@
23 23 <mat-tab label="{{ 'timewindow.realtime' | translate }}">
24 24 <div formGroupName="realtime" class="mat-content mat-padding" fxLayout="column">
25 25 <tb-timeinterval
  26 + [(hideFlag)]="timewindow.hideInterval"
  27 + (hideFlagChange)="onHideIntervalChanged()"
  28 + [isEdit]="isEdit"
26 29 formControlName="timewindowMs"
27 30 predefinedName="timewindow.last"
28 31 [required]="timewindow.selectedTab === timewindowTypes.REALTIME"
... ... @@ -30,66 +33,95 @@
30 33 </div>
31 34 </mat-tab>
32 35 <mat-tab label="{{ 'timewindow.history' | translate }}">
33   - <div formGroupName="history" class="mat-content mat-padding" style="padding-top: 8px;">
34   - <mat-radio-group formControlName="historyType">
35   - <mat-radio-button [value]="historyTypes.LAST_INTERVAL" color="primary">
36   - <section fxLayout="column">
37   - <tb-timeinterval
38   - formControlName="timewindowMs"
39   - predefinedName="timewindow.last"
40   - [fxShow]="timewindowForm.get('history').get('historyType').value === historyTypes.LAST_INTERVAL"
41   - [required]="timewindow.selectedTab === timewindowTypes.HISTORY &&
  36 + <section fxLayout="row">
  37 + <section *ngIf="isEdit" fxLayout="column" style="padding-top: 8px; padding-left: 16px;">
  38 + <label class="tb-small hide-label" translate>timewindow.hide</label>
  39 + <mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideInterval"
  40 + (ngModelChange)="onHideIntervalChanged()"></mat-checkbox>
  41 + </section>
  42 + <section fxLayout="column" [fxShow]="isEdit || !timewindow.hideInterval">
  43 + <div formGroupName="history" class="mat-content mat-padding" style="padding-top: 8px;">
  44 + <mat-radio-group formControlName="historyType">
  45 + <mat-radio-button [value]="historyTypes.LAST_INTERVAL" color="primary">
  46 + <section fxLayout="column">
  47 + <tb-timeinterval
  48 + formControlName="timewindowMs"
  49 + predefinedName="timewindow.last"
  50 + [fxShow]="timewindowForm.get('history').get('historyType').value === historyTypes.LAST_INTERVAL"
  51 + [required]="timewindow.selectedTab === timewindowTypes.HISTORY &&
42 52 timewindowForm.get('history').get('historyType').value === historyTypes.LAST_INTERVAL"
43   - style="padding-top: 8px;"></tb-timeinterval>
44   - </section>
45   - </mat-radio-button>
46   - <mat-radio-button [value]="historyTypes.FIXED" color="primary">
47   - <section fxLayout="column">
48   - <span translate>timewindow.time-period</span>
49   - <tb-datetime-period
50   - formControlName="fixedTimewindow"
51   - [fxShow]="timewindowForm.get('history').get('historyType').value === historyTypes.FIXED"
52   - [required]="timewindow.selectedTab === timewindowTypes.HISTORY &&
  53 + style="padding-top: 8px;"></tb-timeinterval>
  54 + </section>
  55 + </mat-radio-button>
  56 + <mat-radio-button [value]="historyTypes.FIXED" color="primary">
  57 + <section fxLayout="column">
  58 + <span translate>timewindow.time-period</span>
  59 + <tb-datetime-period
  60 + formControlName="fixedTimewindow"
  61 + [fxShow]="timewindowForm.get('history').get('historyType').value === historyTypes.FIXED"
  62 + [required]="timewindow.selectedTab === timewindowTypes.HISTORY &&
53 63 timewindowForm.get('history').get('historyType').value === historyTypes.FIXED"
54   - style="padding-top: 8px;"></tb-datetime-period>
55   - </section>
56   - </mat-radio-button>
57   - </mat-radio-group>
58   - </div>
  64 + style="padding-top: 8px;"></tb-datetime-period>
  65 + </section>
  66 + </mat-radio-button>
  67 + </mat-radio-group>
  68 + </div>
  69 + </section>
  70 + </section>
59 71 </mat-tab>
60 72 </mat-tab-group>
61 73 <div *ngIf="aggregation" formGroupName="aggregation" class="mat-content mat-padding" fxLayout="column">
62   - <mat-form-field>
63   - <mat-label translate>aggregation.function</mat-label>
64   - <mat-select matInput formControlName="type" style="min-width: 150px;">
65   - <mat-option *ngFor="let aggregation of aggregations" [value]="aggregation">
66   - {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }}
67   - </mat-option>
68   - </mat-select>
69   - </mat-form-field>
70   - <div *ngIf="timewindowForm.get('aggregation').get('type').value === aggregationTypes.NONE"
71   - class="limit-slider-container"
72   - fxLayout="row" fxLayoutAlign="start center">
73   - <span translate>aggregation.limit</span>
74   - <mat-slider fxFlex formControlName="limit"
75   - thumbLabel
76   - [value]="timewindowForm.get('aggregation').get('limit').value"
77   - min="{{minDatapointsLimit()}}"
78   - max="{{maxDatapointsLimit()}}">
79   - </mat-slider>
80   - <mat-form-field style="max-width: 80px;">
81   - <input matInput formControlName="limit" type="number" step="1"
82   - [value]="timewindowForm.get('aggregation').get('limit').value"
83   - min="{{minDatapointsLimit()}}"
84   - max="{{maxDatapointsLimit()}}"/>
85   - </mat-form-field>
86   - </div>
  74 + <section fxLayout="row">
  75 + <section fxLayout="column" [fxShow]="isEdit">
  76 + <label class="tb-small hide-label" translate>timewindow.hide</label>
  77 + <mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideAggregation"
  78 + (ngModelChange)="onHideAggregationChanged()"></mat-checkbox>
  79 + </section>
  80 + <section fxFlex fxLayout="column" [fxShow]="isEdit || !timewindow.hideAggregation">
  81 + <mat-form-field>
  82 + <mat-label translate>aggregation.function</mat-label>
  83 + <mat-select matInput formControlName="type" style="min-width: 150px;">
  84 + <mat-option *ngFor="let aggregation of aggregations" [value]="aggregation">
  85 + {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }}
  86 + </mat-option>
  87 + </mat-select>
  88 + </mat-form-field>
  89 + </section>
  90 + </section>
  91 + <section fxLayout="row" [fxShow]="timewindowForm.get('aggregation').get('type').value === aggregationTypes.NONE">
  92 + <section fxLayout="column" [fxShow]="isEdit">
  93 + <label class="tb-small hide-label" translate>timewindow.hide</label>
  94 + <mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideAggInterval"
  95 + (ngModelChange)="onHideAggIntervalChanged()"></mat-checkbox>
  96 + </section>
  97 + <section fxLayout="column" [fxShow]="isEdit || !timewindow.hideAggInterval">
  98 + <div class="limit-slider-container"
  99 + fxLayout="row" fxLayoutAlign="start center">
  100 + <span translate>aggregation.limit</span>
  101 + <mat-slider fxFlex formControlName="limit"
  102 + thumbLabel
  103 + [value]="timewindowForm.get('aggregation').get('limit').value"
  104 + min="{{minDatapointsLimit()}}"
  105 + max="{{maxDatapointsLimit()}}">
  106 + </mat-slider>
  107 + <mat-form-field style="max-width: 80px;">
  108 + <input matInput formControlName="limit" type="number" step="1"
  109 + [value]="timewindowForm.get('aggregation').get('limit').value"
  110 + min="{{minDatapointsLimit()}}"
  111 + max="{{maxDatapointsLimit()}}"/>
  112 + </mat-form-field>
  113 + </div>
  114 + </section>
  115 + </section>
87 116 </div>
88 117 <div formGroupName="realtime"
89 118 *ngIf="aggregation && timewindowForm.get('aggregation').get('type').value !== aggregationTypes.NONE &&
90 119 timewindow.selectedTab === timewindowTypes.REALTIME" class="mat-content mat-padding" fxLayout="column">
91 120 <tb-timeinterval
92 121 formControlName="interval"
  122 + [isEdit]="isEdit"
  123 + [(hideFlag)]="timewindow.hideAggInterval"
  124 + (hideFlagChange)="onHideAggIntervalChanged()"
93 125 [min]="minRealtimeAggInterval()" [max]="maxRealtimeAggInterval()"
94 126 predefinedName="aggregation.group-interval">
95 127 </tb-timeinterval>
... ... @@ -99,6 +131,9 @@
99 131 timewindow.selectedTab === timewindowTypes.HISTORY" class="mat-content mat-padding" fxLayout="column">
100 132 <tb-timeinterval
101 133 formControlName="interval"
  134 + [isEdit]="isEdit"
  135 + [(hideFlag)]="timewindow.hideAggInterval"
  136 + (hideFlagChange)="onHideAggIntervalChanged()"
102 137 [min]="minHistoryAggInterval()" [max]="maxHistoryAggInterval()"
103 138 predefinedName="aggregation.group-interval">
104 139 </tb-timeinterval>
... ...
... ... @@ -30,6 +30,11 @@
30 30 padding: 0 16px;
31 31 }
32 32
  33 + .hide-label {
  34 + margin-bottom: 5px;
  35 + margin-right: 5px;
  36 + }
  37 +
33 38 .limit-slider-container {
34 39 >:first-child {
35 40 margin-right: 16px;
... ...
... ... @@ -48,6 +48,7 @@ export interface TimewindowPanelData {
48 48 historyOnly: boolean;
49 49 timewindow: Timewindow;
50 50 aggregation: boolean;
  51 + isEdit: boolean;
51 52 }
52 53
53 54 @Component({
... ... @@ -61,6 +62,8 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
61 62
62 63 aggregation = false;
63 64
  65 + isEdit = false;
  66 +
64 67 timewindow: Timewindow;
65 68
66 69 result: Timewindow;
... ... @@ -82,18 +85,19 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
82 85 protected store: Store<AppState>,
83 86 public fb: FormBuilder,
84 87 private timeService: TimeService,
85   - private translate: TranslateService,
86   - private millisecondsToTimeStringPipe: MillisecondsToTimeStringPipe,
87   - private datePipe: DatePipe,
88   - private overlay: Overlay,
89 88 public viewContainerRef: ViewContainerRef) {
90 89 super(store);
91 90 this.historyOnly = data.historyOnly;
92 91 this.timewindow = data.timewindow;
93 92 this.aggregation = data.aggregation;
  93 + this.isEdit = data.isEdit;
94 94 }
95 95
96 96 ngOnInit(): void {
  97 + const hideInterval = this.timewindow.hideInterval || false;
  98 + const hideAggregation = this.timewindow.hideAggregation || false;
  99 + const hideAggInterval = this.timewindow.hideAggInterval || false;
  100 +
97 101 this.timewindowForm = this.fb.group({
98 102 realtime: this.fb.group(
99 103 {
... ... @@ -109,42 +113,46 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
109 113 ),
110 114 history: this.fb.group(
111 115 {
112   - historyType: [
113   - this.timewindow.history && typeof this.timewindow.history.historyType !== 'undefined'
114   - ? this.timewindow.history.historyType : HistoryWindowType.LAST_INTERVAL
115   - ],
116   - timewindowMs: [
117   - this.timewindow.history && typeof this.timewindow.history.timewindowMs !== 'undefined'
118   - ? this.timewindow.history.timewindowMs : null
119   - ],
  116 + historyType: this.fb.control({
  117 + value: this.timewindow.history && typeof this.timewindow.history.historyType !== 'undefined'
  118 + ? this.timewindow.history.historyType : HistoryWindowType.LAST_INTERVAL,
  119 + disabled: hideInterval
  120 + }),
  121 + timewindowMs: this.fb.control({
  122 + value: this.timewindow.history && typeof this.timewindow.history.timewindowMs !== 'undefined'
  123 + ? this.timewindow.history.timewindowMs : null,
  124 + disabled: hideInterval
  125 + }),
120 126 interval: [
121 127 this.timewindow.history && typeof this.timewindow.history.interval !== 'undefined'
122 128 ? this.timewindow.history.interval : null
123 129 ],
124   - fixedTimewindow: [
125   - this.timewindow.history && typeof this.timewindow.history.fixedTimewindow !== 'undefined'
126   - ? this.timewindow.history.fixedTimewindow : null
127   - ]
  130 + fixedTimewindow: this.fb.control({
  131 + value: this.timewindow.history && typeof this.timewindow.history.fixedTimewindow !== 'undefined'
  132 + ? this.timewindow.history.fixedTimewindow : null,
  133 + disabled: hideInterval
  134 + })
128 135 }
129 136 ),
130 137 aggregation: this.fb.group(
131 138 {
132   - type: [
133   - this.timewindow.aggregation && typeof this.timewindow.aggregation.type !== 'undefined'
134   - ? this.timewindow.aggregation.type : null
135   - ],
136   - limit: [
137   - this.timewindow.aggregation && typeof this.timewindow.aggregation.limit !== 'undefined'
  139 + type: this.fb.control({
  140 + value: this.timewindow.aggregation && typeof this.timewindow.aggregation.type !== 'undefined'
  141 + ? this.timewindow.aggregation.type : null,
  142 + disabled: hideAggregation
  143 + }),
  144 + limit: this.fb.control({
  145 + value: this.timewindow.aggregation && typeof this.timewindow.aggregation.limit !== 'undefined'
138 146 ? this.timewindow.aggregation.limit : null,
139   - [Validators.min(this.minDatapointsLimit()), Validators.max(this.maxDatapointsLimit())]
140   - ]
  147 + disabled: hideAggInterval
  148 + }, [Validators.min(this.minDatapointsLimit()), Validators.max(this.maxDatapointsLimit())])
141 149 }
142 150 )
143 151 });
144 152 }
145 153
146 154 update() {
147   - const timewindowFormValue = this.timewindowForm.value;
  155 + const timewindowFormValue = this.timewindowForm.getRawValue();
148 156 this.timewindow.realtime = {
149 157 timewindowMs: timewindowFormValue.realtime.timewindowMs,
150 158 interval: timewindowFormValue.realtime.interval
... ... @@ -194,7 +202,7 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
194 202 }
195 203
196 204 currentHistoryTimewindow() {
197   - const timewindowFormValue = this.timewindowForm.value;
  205 + const timewindowFormValue = this.timewindowForm.getRawValue();
198 206 if (timewindowFormValue.history.historyType === HistoryWindowType.LAST_INTERVAL) {
199 207 return timewindowFormValue.history.timewindowMs;
200 208 } else {
... ... @@ -203,4 +211,35 @@ export class TimewindowPanelComponent extends PageComponent implements OnInit {
203 211 }
204 212 }
205 213
  214 + onHideIntervalChanged() {
  215 + if (this.timewindow.hideInterval) {
  216 + this.timewindowForm.get('history').get('historyType').disable({emitEvent: false});
  217 + this.timewindowForm.get('history').get('timewindowMs').disable({emitEvent: false});
  218 + this.timewindowForm.get('history').get('fixedTimewindow').disable({emitEvent: false});
  219 + } else {
  220 + this.timewindowForm.get('history').get('historyType').enable({emitEvent: false});
  221 + this.timewindowForm.get('history').get('timewindowMs').enable({emitEvent: false});
  222 + this.timewindowForm.get('history').get('fixedTimewindow').enable({emitEvent: false});
  223 + }
  224 + this.timewindowForm.markAsDirty();
  225 + }
  226 +
  227 + onHideAggregationChanged() {
  228 + if (this.timewindow.hideAggregation) {
  229 + this.timewindowForm.get('aggregation').get('type').disable({emitEvent: false});
  230 + } else {
  231 + this.timewindowForm.get('aggregation').get('type').enable({emitEvent: false});
  232 + }
  233 + this.timewindowForm.markAsDirty();
  234 + }
  235 +
  236 + onHideAggIntervalChanged() {
  237 + if (this.timewindow.hideAggInterval) {
  238 + this.timewindowForm.get('aggregation').get('limit').disable({emitEvent: false});
  239 + } else {
  240 + this.timewindowForm.get('aggregation').get('limit').enable({emitEvent: false});
  241 + }
  242 + this.timewindowForm.markAsDirty();
  243 + }
  244 +
206 245 }
... ...
... ... @@ -15,7 +15,7 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<button *ngIf="asButton" cdkOverlayOrigin #timewindowPanelOrigin="cdkOverlayOrigin" [disabled]="disabled"
  18 +<button *ngIf="asButton" cdkOverlayOrigin #timewindowPanelOrigin="cdkOverlayOrigin" [disabled]="timewindowDisabled"
19 19 type="button"
20 20 mat-raised-button color="primary" (click)="openEditMode()">
21 21 <mat-icon class="material-icons">query_builder</mat-icon>
... ... @@ -23,7 +23,7 @@
23 23 </button>
24 24 <section *ngIf="!asButton" cdkOverlayOrigin #timewindowPanelOrigin="cdkOverlayOrigin"
25 25 class="tb-timewindow" fxLayout="row" fxLayoutAlign="start center">
26   - <button *ngIf="direction === 'left'" [disabled]="disabled" mat-button mat-icon-button class="tb-mat-32"
  26 + <button *ngIf="direction === 'left'" [disabled]="timewindowDisabled" mat-button mat-icon-button class="tb-mat-32"
27 27 type="button"
28 28 (click)="openEditMode()"
29 29 matTooltip="{{ 'timewindow.edit' | translate }}"
... ... @@ -36,7 +36,7 @@
36 36 [matTooltipPosition]="tooltipPosition">
37 37 {{innerValue?.displayValue}}
38 38 </span>
39   - <button *ngIf="direction === 'right'" [disabled]="disabled" mat-button mat-icon-button class="tb-mat-32"
  39 + <button *ngIf="direction === 'right'" [disabled]="timewindowDisabled" mat-button mat-icon-button class="tb-mat-32"
40 40 type="button"
41 41 (click)="openEditMode()"
42 42 matTooltip="{{ 'timewindow.edit' | translate }}"
... ...
... ... @@ -53,6 +53,7 @@ import { WINDOW } from '@core/services/window.service';
53 53 import { TimeService } from '@core/services/time.service';
54 54 import { TooltipPosition } from '@angular/material/tooltip';
55 55 import { deepClone } from '@core/utils';
  56 +import { coerceBooleanProperty } from '@angular/cdk/coercion';
56 57
57 58 // @dynamic
58 59 @Component({
... ... @@ -73,7 +74,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
73 74
74 75 @Input()
75 76 set historyOnly(val) {
76   - this.historyOnlyValue = true;
  77 + this.historyOnlyValue = coerceBooleanProperty(val);
77 78 }
78 79
79 80 get historyOnly() {
... ... @@ -84,7 +85,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
84 85
85 86 @Input()
86 87 set aggregation(val) {
87   - this.aggregationValue = true;
  88 + this.aggregationValue = coerceBooleanProperty(val);
88 89 }
89 90
90 91 get aggregation() {
... ... @@ -95,7 +96,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
95 96
96 97 @Input()
97 98 set isToolbar(val) {
98   - this.isToolbarValue = true;
  99 + this.isToolbarValue = coerceBooleanProperty(val);
99 100 }
100 101
101 102 get isToolbar() {
... ... @@ -106,13 +107,25 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
106 107
107 108 @Input()
108 109 set asButton(val) {
109   - this.asButtonValue = true;
  110 + this.asButtonValue = coerceBooleanProperty(val);
110 111 }
111 112
112 113 get asButton() {
113 114 return this.asButtonValue;
114 115 }
115 116
  117 + isEditValue = false;
  118 +
  119 + @Input()
  120 + set isEdit(val) {
  121 + this.isEditValue = coerceBooleanProperty(val);
  122 + this.timewindowDisabled = this.isTimewindowDisabled();
  123 + }
  124 +
  125 + get isEdit() {
  126 + return this.isEditValue;
  127 + }
  128 +
116 129 @Input()
117 130 direction: 'left' | 'right' = 'left';
118 131
... ... @@ -125,6 +138,8 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
125 138
126 139 innerValue: Timewindow;
127 140
  141 + timewindowDisabled: boolean;
  142 +
128 143 private propagateChange = (_: any) => {};
129 144
130 145 constructor(private translate: TranslateService,
... ... @@ -145,7 +160,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
145 160 }
146 161
147 162 openEditMode() {
148   - if (this.disabled) {
  163 + if (this.timewindowDisabled) {
149 164 return;
150 165 }
151 166 const isGtSm = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']);
... ... @@ -212,7 +227,8 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
212 227 {
213 228 timewindow: deepClone(this.innerValue),
214 229 historyOnly: this.historyOnly,
215   - aggregation: this.aggregation
  230 + aggregation: this.aggregation,
  231 + isEdit: this.isEdit
216 232 }
217 233 );
218 234
... ... @@ -220,6 +236,7 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
220 236 componentRef.onDestroy(() => {
221 237 if (componentRef.instance.result) {
222 238 this.innerValue = componentRef.instance.result;
  239 + this.timewindowDisabled = this.isTimewindowDisabled();
223 240 this.updateDisplayValue();
224 241 this.notifyChanged();
225 242 }
... ... @@ -243,10 +260,12 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
243 260
244 261 setDisabledState(isDisabled: boolean): void {
245 262 this.disabled = isDisabled;
  263 + this.timewindowDisabled = this.isTimewindowDisabled();
246 264 }
247 265
248 266 writeValue(obj: Timewindow): void {
249 267 this.innerValue = initModelFromDefaultTimewindow(obj, this.timeService);
  268 + this.timewindowDisabled = this.isTimewindowDisabled();
250 269 this.updateDisplayValue();
251 270 }
252 271
... ... @@ -276,4 +295,10 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
276 295 return this.isToolbar && !this.breakpointObserver.isMatched(MediaBreakpoints['gt-md']);
277 296 }
278 297
  298 + private isTimewindowDisabled(): boolean {
  299 + return this.disabled ||
  300 + (!this.isEdit && (!this.innerValue || this.innerValue.hideInterval &&
  301 + (!this.aggregation || this.innerValue.hideAggregation && this.innerValue.hideAggInterval)));
  302 + }
  303 +
279 304 }
... ...
... ... @@ -79,6 +79,9 @@ export interface Aggregation {
79 79
80 80 export interface Timewindow {
81 81 displayValue?: string;
  82 + hideInterval?: boolean;
  83 + hideAggregation?: boolean;
  84 + hideAggInterval?: boolean;
82 85 selectedTab?: TimewindowType;
83 86 realtime?: IntervalWindow;
84 87 history?: HistoryWindow;
... ... @@ -115,9 +118,12 @@ export function historyInterval(timewindowMs: number): Timewindow {
115 118 }
116 119
117 120 export function defaultTimewindow(timeService: TimeService): Timewindow {
118   - const currentTime = Date.now();
  121 + const currentTime = moment().valueOf();
119 122 const timewindow: Timewindow = {
120 123 displayValue: '',
  124 + hideInterval: false,
  125 + hideAggregation: false,
  126 + hideAggInterval: false,
121 127 selectedTab: TimewindowType.REALTIME,
122 128 realtime: {
123 129 interval: SECOND,
... ... @@ -143,6 +149,9 @@ export function defaultTimewindow(timeService: TimeService): Timewindow {
143 149 export function initModelFromDefaultTimewindow(value: Timewindow, timeService: TimeService): Timewindow {
144 150 const model = defaultTimewindow(timeService);
145 151 if (value) {
  152 + model.hideInterval = value.hideInterval;
  153 + model.hideAggregation = value.hideAggregation;
  154 + model.hideAggInterval = value.hideAggInterval;
146 155 if (isUndefined(value.selectedTab)) {
147 156 if (value.realtime) {
148 157 model.selectedTab = TimewindowType.REALTIME;
... ... @@ -206,6 +215,9 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number,
206 215 limit = timeService.getMaxDatapointsLimit();
207 216 }
208 217 const historyTimewindow: Timewindow = {
  218 + hideInterval: timewindow.hideInterval || false,
  219 + hideAggregation: timewindow.hideAggregation || false,
  220 + hideAggInterval: timewindow.hideAggInterval || false,
209 221 selectedTab: TimewindowType.HISTORY,
210 222 history: {
211 223 historyType: HistoryWindowType.FIXED,
... ... @@ -319,6 +331,9 @@ export function createTimewindowForComparison(subscriptionTimewindow: Subscripti
319 331
320 332 export function cloneSelectedTimewindow(timewindow: Timewindow): Timewindow {
321 333 const cloned: Timewindow = {};
  334 + cloned.hideInterval = timewindow.hideInterval || false;
  335 + cloned.hideAggregation = timewindow.hideAggregation || false;
  336 + cloned.hideAggInterval = timewindow.hideAggInterval || false;
322 337 if (isDefined(timewindow.selectedTab)) {
323 338 cloned.selectedTab = timewindow.selectedTab;
324 339 if (timewindow.selectedTab === TimewindowType.REALTIME) {
... ...
... ... @@ -345,6 +345,7 @@ export interface WidgetConfig {
345 345 showTitleIcon?: boolean;
346 346 iconColor?: string;
347 347 iconSize?: string;
  348 + titleTooltip?: string;
348 349 dropShadow?: boolean;
349 350 enableFullscreen?: boolean;
350 351 useDashboardTimewindow?: boolean;
... ...