52 changed files with 2611 additions and 313 deletions
@ -0,0 +1,57 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2019 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<form #widgetForm="ngForm" [formGroup]="widgetFormGroup" (ngSubmit)="add()" style="width: 900px;"> |
||||
|
<mat-toolbar fxLayout="row" color="primary"> |
||||
|
<h2 translate>widget.add</h2> |
||||
|
<span fxFlex></span> |
||||
|
<div [tb-help]="helpLinkIdForWidgetType()"></div> |
||||
|
<button mat-button mat-icon-button |
||||
|
(click)="cancel()" |
||||
|
type="button"> |
||||
|
<mat-icon class="material-icons">close</mat-icon> |
||||
|
</button> |
||||
|
</mat-toolbar> |
||||
|
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
||||
|
</mat-progress-bar> |
||||
|
<div mat-dialog-content> |
||||
|
<fieldset [disabled]="isLoading$ | async" style="position: relative; height: 600px;"> |
||||
|
<tb-widget-config |
||||
|
[aliasController]="aliasController" |
||||
|
[functionsOnly]="false" |
||||
|
[entityAliases]="dashboard.configuration.entityAliases" |
||||
|
[dashboardStates]="dashboard.configuration.states" |
||||
|
formControlName="widgetConfig"> |
||||
|
</tb-widget-config> |
||||
|
</fieldset> |
||||
|
</div> |
||||
|
<div mat-dialog-actions fxLayout="row"> |
||||
|
<span fxFlex></span> |
||||
|
<button mat-button mat-raised-button color="primary" |
||||
|
type="submit" |
||||
|
[disabled]="(isLoading$ | async) || widgetFormGroup.invalid"> |
||||
|
{{ 'action.add' | translate }} |
||||
|
</button> |
||||
|
<button mat-button color="primary" |
||||
|
style="margin-right: 20px;" |
||||
|
type="button" |
||||
|
[disabled]="(isLoading$ | async)" |
||||
|
(click)="cancel()" cdkFocusInitial> |
||||
|
{{ 'action.cancel' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</form> |
||||
@ -0,0 +1,137 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2019 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; |
||||
|
import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm } from '@angular/forms'; |
||||
|
import { Router } from '@angular/router'; |
||||
|
import { DialogComponent } from '@app/shared/components/dialog.component'; |
||||
|
import { Widget, widgetTypesData } from '@shared/models/widget.models'; |
||||
|
import { UtilsService } from '@core/services/utils.service'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
import { EntityService } from '@core/http/entity.service'; |
||||
|
import { Dashboard } from '@app/shared/models/dashboard.models'; |
||||
|
import { IAliasController } from '@core/api/widget-api.models'; |
||||
|
import { WidgetConfigComponentData, WidgetInfo } from '@home/models/widget-component.models'; |
||||
|
import { isDefined, isString } from '@core/utils'; |
||||
|
|
||||
|
export interface AddWidgetDialogData { |
||||
|
dashboard: Dashboard; |
||||
|
aliasController: IAliasController; |
||||
|
widget: Widget; |
||||
|
widgetInfo: WidgetInfo; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-add-widget-dialog', |
||||
|
templateUrl: './add-widget-dialog.component.html', |
||||
|
providers: [{provide: ErrorStateMatcher, useExisting: AddWidgetDialogComponent}], |
||||
|
styleUrls: [] |
||||
|
}) |
||||
|
export class AddWidgetDialogComponent extends DialogComponent<AddWidgetDialogComponent, Widget> |
||||
|
implements OnInit, ErrorStateMatcher { |
||||
|
|
||||
|
widgetFormGroup: FormGroup; |
||||
|
|
||||
|
dashboard: Dashboard; |
||||
|
aliasController: IAliasController; |
||||
|
widget: Widget; |
||||
|
|
||||
|
submitted = false; |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
protected router: Router, |
||||
|
@Inject(MAT_DIALOG_DATA) public data: AddWidgetDialogData, |
||||
|
@SkipSelf() private errorStateMatcher: ErrorStateMatcher, |
||||
|
public dialogRef: MatDialogRef<AddWidgetDialogComponent, Widget>, |
||||
|
private fb: FormBuilder, |
||||
|
private utils: UtilsService, |
||||
|
private translate: TranslateService, |
||||
|
private entityService: EntityService) { |
||||
|
super(store, router, dialogRef); |
||||
|
|
||||
|
this.dashboard = this.data.dashboard; |
||||
|
this.aliasController = this.data.aliasController; |
||||
|
this.widget = this.data.widget; |
||||
|
|
||||
|
const widgetInfo = this.data.widgetInfo; |
||||
|
|
||||
|
const rawSettingsSchema = widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema; |
||||
|
const rawDataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema; |
||||
|
const typeParameters = widgetInfo.typeParameters; |
||||
|
const actionSources = widgetInfo.actionSources; |
||||
|
const isDataEnabled = isDefined(widgetInfo.typeParameters) ? !widgetInfo.typeParameters.useCustomDatasources : true; |
||||
|
let settingsSchema; |
||||
|
if (!rawSettingsSchema || rawSettingsSchema === '') { |
||||
|
settingsSchema = {}; |
||||
|
} else { |
||||
|
settingsSchema = isString(rawSettingsSchema) ? JSON.parse(rawSettingsSchema) : rawSettingsSchema; |
||||
|
} |
||||
|
let dataKeySettingsSchema; |
||||
|
if (!rawDataKeySettingsSchema || rawDataKeySettingsSchema === '') { |
||||
|
dataKeySettingsSchema = {}; |
||||
|
} else { |
||||
|
dataKeySettingsSchema = isString(rawDataKeySettingsSchema) ? JSON.parse(rawDataKeySettingsSchema) : rawDataKeySettingsSchema; |
||||
|
} |
||||
|
const widgetConfig: WidgetConfigComponentData = { |
||||
|
config: this.widget.config, |
||||
|
layout: {}, |
||||
|
widgetType: this.widget.type, |
||||
|
typeParameters, |
||||
|
actionSources, |
||||
|
isDataEnabled, |
||||
|
settingsSchema, |
||||
|
dataKeySettingsSchema |
||||
|
}; |
||||
|
|
||||
|
this.widgetFormGroup = this.fb.group({ |
||||
|
widgetConfig: [widgetConfig, []] |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
} |
||||
|
|
||||
|
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { |
||||
|
const originalErrorState = this.errorStateMatcher.isErrorState(control, form); |
||||
|
const customErrorState = !!(control && control.invalid && this.submitted); |
||||
|
return originalErrorState || customErrorState; |
||||
|
} |
||||
|
|
||||
|
helpLinkIdForWidgetType(): string { |
||||
|
let link = 'widgetsConfig'; |
||||
|
if (this.widget && this.widget.type) { |
||||
|
link = widgetTypesData.get(this.widget.type).configHelpLinkId; |
||||
|
} |
||||
|
return link; |
||||
|
} |
||||
|
|
||||
|
cancel(): void { |
||||
|
this.dialogRef.close(null); |
||||
|
} |
||||
|
|
||||
|
add(): void { |
||||
|
this.submitted = true; |
||||
|
const widgetConfig: WidgetConfigComponentData = this.widgetFormGroup.get('widgetConfig').value; |
||||
|
this.widget.config = widgetConfig.config; |
||||
|
this.widget.config.mobileOrder = widgetConfig.layout.mobileOrder; |
||||
|
this.widget.config.mobileHeight = widgetConfig.layout.mobileHeight; |
||||
|
this.dialogRef.close(this.widget); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,167 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2019 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<form #settingsForm="ngForm" (ngSubmit)="save()"> |
||||
|
<mat-toolbar fxLayout="row" color="primary"> |
||||
|
<h2 translate>{{settings ? 'dashboard.settings' : 'layout.settings'}}</h2> |
||||
|
<span fxFlex></span> |
||||
|
<button mat-button mat-icon-button |
||||
|
(click)="cancel()" |
||||
|
type="button"> |
||||
|
<mat-icon class="material-icons">close</mat-icon> |
||||
|
</button> |
||||
|
</mat-toolbar> |
||||
|
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
||||
|
</mat-progress-bar> |
||||
|
<div mat-dialog-content> |
||||
|
<fieldset [disabled]="isLoading$ | async"> |
||||
|
<div *ngIf="settings" [formGroup]="settingsFormGroup"> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label translate>dashboard.state-controller</mat-label> |
||||
|
<mat-select required matInput formControlName="stateControllerId"> |
||||
|
<mat-option *ngFor="let stateControllerId of stateControllerIds" [value]="stateControllerId"> |
||||
|
{{stateControllerId}} |
||||
|
</mat-option> |
||||
|
</mat-select> |
||||
|
</mat-form-field> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<mat-checkbox fxFlex formControlName="toolbarAlwaysOpen"> |
||||
|
{{ 'dashboard.toolbar-always-open' | translate }} |
||||
|
</mat-checkbox> |
||||
|
<mat-checkbox fxFlex formControlName="showTitle"> |
||||
|
{{ 'dashboard.display-title' | translate }} |
||||
|
</mat-checkbox> |
||||
|
<tb-color-input fxFlex |
||||
|
label="{{'dashboard.title-color' | translate}}" |
||||
|
icon="format_color_fill" |
||||
|
openOnInput |
||||
|
formControlName="titleColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<mat-checkbox fxFlex formControlName="showDashboardsSelect"> |
||||
|
{{ 'dashboard.display-dashboards-selection' | translate }} |
||||
|
</mat-checkbox> |
||||
|
<mat-checkbox fxFlex formControlName="showEntitiesSelect"> |
||||
|
{{ 'dashboard.display-entities-selection' | translate }} |
||||
|
</mat-checkbox> |
||||
|
<mat-checkbox fxFlex formControlName="showDashboardTimewindow"> |
||||
|
{{ 'dashboard.display-dashboard-timewindow' | translate }} |
||||
|
</mat-checkbox> |
||||
|
<mat-checkbox fxFlex formControlName="showDashboardExport"> |
||||
|
{{ 'dashboard.display-dashboard-export' | translate }} |
||||
|
</mat-checkbox> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div *ngIf="gridSettings" [formGroup]="gridSettingsFormGroup"> |
||||
|
<tb-color-input fxFlex |
||||
|
label="{{'layout.color' | translate}}" |
||||
|
icon="format_color_fill" |
||||
|
openOnInput |
||||
|
formControlName="color"> |
||||
|
</tb-color-input> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label translate>dashboard.columns-count</mat-label> |
||||
|
<input matInput formControlName="columns" type="number" step="any" min="10" |
||||
|
max="1000" required> |
||||
|
<mat-error *ngIf="gridSettingsFormGroup.get('columns').hasError('required')"> |
||||
|
{{ 'dashboard.columns-count-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gridSettingsFormGroup.get('columns').hasError('min')"> |
||||
|
{{ 'dashboard.min-columns-count-message' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gridSettingsFormGroup.get('columns').hasError('max')"> |
||||
|
{{ 'dashboard.max-columns-count-message' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label translate>dashboard.widgets-margins</mat-label> |
||||
|
<input matInput formControlName="margin" type="number" step="any" min="0" |
||||
|
max="50" required> |
||||
|
<mat-error *ngIf="gridSettingsFormGroup.get('margin').hasError('required')"> |
||||
|
{{ 'dashboard.margin-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gridSettingsFormGroup.get('margin').hasError('min')"> |
||||
|
{{ 'dashboard.min-margin-message' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gridSettingsFormGroup.get('margin').hasError('max')"> |
||||
|
{{ 'dashboard.max-margin-message' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-checkbox fxFlex formControlName="autoFillHeight" style="display: block; padding-bottom: 12px;"> |
||||
|
{{ 'dashboard.autofill-height' | translate }} |
||||
|
</mat-checkbox> |
||||
|
<tb-color-input fxFlex |
||||
|
label="{{'dashboard.background-color' | translate}}" |
||||
|
icon="format_color_fill" |
||||
|
openOnInput |
||||
|
formControlName="backgroundColor"> |
||||
|
</tb-color-input> |
||||
|
<tb-image-input fxFlex |
||||
|
label="{{'dashboard.background-image' | translate}}" |
||||
|
formControlName="backgroundImageUrl"> |
||||
|
</tb-image-input> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label translate>dashboard.background-size-mode</mat-label> |
||||
|
<mat-select matInput formControlName="backgroundSizeMode"> |
||||
|
<mat-option value="100%">Fit width</mat-option> |
||||
|
<mat-option value="auto 100%">Fit height</mat-option> |
||||
|
<mat-option value="cover">Cover</mat-option> |
||||
|
<mat-option value="contain">Contain</mat-option> |
||||
|
<mat-option value="auto">Original size</mat-option> |
||||
|
</mat-select> |
||||
|
</mat-form-field> |
||||
|
<small translate>dashboard.mobile-layout</small> |
||||
|
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<mat-checkbox fxFlex formControlName="mobileAutoFillHeight"> |
||||
|
{{ 'dashboard.autofill-height' | translate }} |
||||
|
</mat-checkbox> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label translate>dashboard.mobile-row-height</mat-label> |
||||
|
<input matInput formControlName="mobileRowHeight" type="number" step="any" min="5" |
||||
|
max="200" required> |
||||
|
<mat-error *ngIf="gridSettingsFormGroup.get('mobileRowHeight').hasError('required')"> |
||||
|
{{ 'dashboard.mobile-row-height-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gridSettingsFormGroup.get('mobileRowHeight').hasError('min')"> |
||||
|
{{ 'dashboard.min-mobile-row-height-message' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="gridSettingsFormGroup.get('mobileRowHeight').hasError('max')"> |
||||
|
{{ 'dashboard.max-mobile-row-height-message' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
</div> |
||||
|
</fieldset> |
||||
|
</div> |
||||
|
<div mat-dialog-actions fxLayout="row"> |
||||
|
<span fxFlex></span> |
||||
|
<button mat-button mat-raised-button color="primary" |
||||
|
type="submit" |
||||
|
[disabled]="(isLoading$ | async) || settingsFormGroup.invalid || gridSettingsFormGroup.invalid |
||||
|
|| (!settingsFormGroup.dirty && !gridSettingsFormGroup.dirty)"> |
||||
|
{{ 'action.save' | translate }} |
||||
|
</button> |
||||
|
<button mat-button color="primary" |
||||
|
style="margin-right: 20px;" |
||||
|
type="button" |
||||
|
[disabled]="(isLoading$ | async)" |
||||
|
(click)="cancel()" cdkFocusInitial> |
||||
|
{{ 'action.cancel' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</form> |
||||
@ -0,0 +1,137 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2019 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; |
||||
|
import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; |
||||
|
import { Router } from '@angular/router'; |
||||
|
import { DialogComponent } from '@app/shared/components/dialog.component'; |
||||
|
import { UtilsService } from '@core/services/utils.service'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
import { DashboardSettings, GridSettings, StateControllerId } from '@app/shared/models/dashboard.models'; |
||||
|
import { isUndefined } from '@core/utils'; |
||||
|
import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; |
||||
|
import { StatesControllerService } from './states/states-controller.service'; |
||||
|
|
||||
|
export interface DashboardSettingsDialogData { |
||||
|
settings?: DashboardSettings; |
||||
|
gridSettings?: GridSettings; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-dashboard-settings-dialog', |
||||
|
templateUrl: './dashboard-settings-dialog.component.html', |
||||
|
providers: [{provide: ErrorStateMatcher, useExisting: DashboardSettingsDialogComponent}], |
||||
|
styleUrls: [] |
||||
|
}) |
||||
|
export class DashboardSettingsDialogComponent extends DialogComponent<DashboardSettingsDialogComponent, DashboardSettingsDialogData> |
||||
|
implements OnInit, ErrorStateMatcher { |
||||
|
|
||||
|
settings: DashboardSettings; |
||||
|
gridSettings: GridSettings; |
||||
|
|
||||
|
settingsFormGroup: FormGroup; |
||||
|
gridSettingsFormGroup: FormGroup; |
||||
|
|
||||
|
stateControllerIds: string[]; |
||||
|
|
||||
|
submitted = false; |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
protected router: Router, |
||||
|
@Inject(MAT_DIALOG_DATA) public data: DashboardSettingsDialogData, |
||||
|
@SkipSelf() private errorStateMatcher: ErrorStateMatcher, |
||||
|
public dialogRef: MatDialogRef<DashboardSettingsDialogComponent, DashboardSettingsDialogData>, |
||||
|
private fb: FormBuilder, |
||||
|
private utils: UtilsService, |
||||
|
private dashboardUtils: DashboardUtilsService, |
||||
|
private translate: TranslateService, |
||||
|
private statesControllerService: StatesControllerService) { |
||||
|
super(store, router, dialogRef); |
||||
|
|
||||
|
this.stateControllerIds = Object.keys(this.statesControllerService.getStateControllers()); |
||||
|
|
||||
|
this.settings = this.data.settings; |
||||
|
this.gridSettings = this.data.gridSettings; |
||||
|
|
||||
|
if (this.settings) { |
||||
|
this.settingsFormGroup = this.fb.group({ |
||||
|
stateControllerId: [isUndefined(this.settings.stateControllerId) ? 'entity' : this.settings.stateControllerId, []], |
||||
|
toolbarAlwaysOpen: [isUndefined(this.settings.toolbarAlwaysOpen) ? true : this.settings.toolbarAlwaysOpen, []], |
||||
|
showTitle: [isUndefined(this.settings.showTitle) ? true : this.settings.showTitle, []], |
||||
|
titleColor: [isUndefined(this.settings.titleColor) ? 'rgba(0,0,0,0.870588)' : this.settings.titleColor, []], |
||||
|
showDashboardsSelect: [isUndefined(this.settings.showDashboardsSelect) ? true : this.settings.showDashboardsSelect, []], |
||||
|
showEntitiesSelect: [isUndefined(this.settings.showEntitiesSelect) ? true : this.settings.showEntitiesSelect, []], |
||||
|
showDashboardTimewindow: [isUndefined(this.settings.showDashboardTimewindow) ? true : this.settings.showDashboardTimewindow, []], |
||||
|
showDashboardExport: [isUndefined(this.settings.showDashboardExport) ? true : this.settings.showDashboardExport, []] |
||||
|
}); |
||||
|
this.settingsFormGroup.get('stateControllerId').valueChanges.subscribe( |
||||
|
(stateControllerId: StateControllerId) => { |
||||
|
if (stateControllerId !== 'default') { |
||||
|
this.settingsFormGroup.get('toolbarAlwaysOpen').setValue(true); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
} else { |
||||
|
this.settingsFormGroup = this.fb.group({}); |
||||
|
} |
||||
|
|
||||
|
if (this.gridSettings) { |
||||
|
this.gridSettingsFormGroup = this.fb.group({ |
||||
|
color: [this.gridSettings.color || 'rgba(0,0,0,0.870588)', []], |
||||
|
columns: [this.gridSettings.columns || 24, [Validators.required, Validators.min(10), Validators.max(1000)]], |
||||
|
margin: [this.gridSettings.margin || 10, [Validators.required, Validators.min(0), Validators.max(50)]], |
||||
|
autoFillHeight: [isUndefined(this.gridSettings.autoFillHeight) ? false : this.gridSettings.autoFillHeight, []], |
||||
|
backgroundColor: [this.gridSettings.backgroundColor || 'rgba(0,0,0,0)', []], |
||||
|
backgroundImageUrl: [this.gridSettings.backgroundImageUrl, []], |
||||
|
backgroundSizeMode: [this.gridSettings.backgroundSizeMode || '100%', []], |
||||
|
mobileAutoFillHeight: [isUndefined(this.gridSettings.mobileAutoFillHeight) ? false : this.gridSettings.mobileAutoFillHeight, []], |
||||
|
mobileRowHeight: [isUndefined(this.gridSettings.mobileRowHeight) ? 70 : this.gridSettings.mobileRowHeight, |
||||
|
[Validators.required, Validators.min(5), Validators.max(200)]] |
||||
|
}); |
||||
|
} else { |
||||
|
this.gridSettingsFormGroup = this.fb.group({}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
} |
||||
|
|
||||
|
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { |
||||
|
const originalErrorState = this.errorStateMatcher.isErrorState(control, form); |
||||
|
const customErrorState = !!(control && control.invalid && this.submitted); |
||||
|
return originalErrorState || customErrorState; |
||||
|
} |
||||
|
|
||||
|
cancel(): void { |
||||
|
this.dialogRef.close(null); |
||||
|
} |
||||
|
|
||||
|
save(): void { |
||||
|
this.submitted = true; |
||||
|
let settings: DashboardSettings = null; |
||||
|
let gridSettings: GridSettings = null; |
||||
|
if (this.settings) { |
||||
|
settings = {...this.settings, ...this.settingsFormGroup.value}; |
||||
|
} |
||||
|
if (this.gridSettings) { |
||||
|
gridSettings = {...this.gridSettings, ...this.gridSettingsFormGroup.value}; |
||||
|
} |
||||
|
this.dialogRef.close({settings, gridSettings}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,84 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2019 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<div> |
||||
|
<mat-tab-group *ngIf="hasWidgetTypes()" class="tb-absolute-fill" fxFlex> |
||||
|
<mat-tab *ngIf="timeseriesWidgetTypes.length" style="height: 100%;" label="{{ 'widget.timeseries' | translate }}"> |
||||
|
<tb-dashboard [aliasController]="aliasController" |
||||
|
[widgets]="timeseriesWidgetTypes" |
||||
|
[widgetLayouts]="{}" |
||||
|
[isEdit]="false" |
||||
|
[isMobile]="true" |
||||
|
[isEditActionEnabled]="false" |
||||
|
[isExportActionEnabled]="false" |
||||
|
[isRemoveActionEnabled]="false" |
||||
|
[callbacks]="callbacks"></tb-dashboard> |
||||
|
</mat-tab> |
||||
|
<mat-tab *ngIf="latestWidgetTypes.length" style="height: 100%;" label="{{ 'widget.latest-values' | translate }}"> |
||||
|
<tb-dashboard [aliasController]="aliasController" |
||||
|
[widgets]="latestWidgetTypes" |
||||
|
[widgetLayouts]="{}" |
||||
|
[isEdit]="false" |
||||
|
[isMobile]="true" |
||||
|
[isEditActionEnabled]="false" |
||||
|
[isExportActionEnabled]="false" |
||||
|
[isRemoveActionEnabled]="false" |
||||
|
[callbacks]="callbacks"></tb-dashboard> |
||||
|
</mat-tab> |
||||
|
<mat-tab *ngIf="rpcWidgetTypes.length" style="height: 100%;" label="{{ 'widget.rpc' | translate }}"> |
||||
|
<tb-dashboard [aliasController]="aliasController" |
||||
|
[widgets]="rpcWidgetTypes" |
||||
|
[widgetLayouts]="{}" |
||||
|
[isEdit]="false" |
||||
|
[isMobile]="true" |
||||
|
[isEditActionEnabled]="false" |
||||
|
[isExportActionEnabled]="false" |
||||
|
[isRemoveActionEnabled]="false" |
||||
|
[callbacks]="callbacks"></tb-dashboard> |
||||
|
</mat-tab> |
||||
|
<mat-tab *ngIf="alarmWidgetTypes.length" style="height: 100%;" label="{{ 'widget.alarm' | translate }}"> |
||||
|
<tb-dashboard [aliasController]="aliasController" |
||||
|
[widgets]="alarmWidgetTypes" |
||||
|
[widgetLayouts]="{}" |
||||
|
[isEdit]="false" |
||||
|
[isMobile]="true" |
||||
|
[isEditActionEnabled]="false" |
||||
|
[isExportActionEnabled]="false" |
||||
|
[isRemoveActionEnabled]="false" |
||||
|
[callbacks]="callbacks"></tb-dashboard> |
||||
|
</mat-tab> |
||||
|
<mat-tab *ngIf="staticWidgetTypes.length" style="height: 100%;" label="{{ 'widget.static' | translate }}"> |
||||
|
<tb-dashboard [aliasController]="aliasController" |
||||
|
[widgets]="staticWidgetTypes" |
||||
|
[widgetLayouts]="{}" |
||||
|
[isEdit]="false" |
||||
|
[isMobile]="true" |
||||
|
[isEditActionEnabled]="false" |
||||
|
[isExportActionEnabled]="false" |
||||
|
[isRemoveActionEnabled]="false" |
||||
|
[callbacks]="callbacks"></tb-dashboard> |
||||
|
</mat-tab> |
||||
|
</mat-tab-group> |
||||
|
<span translate *ngIf="widgetsBundle && !hasWidgetTypes()" |
||||
|
style="text-transform: uppercase; display: flex;" |
||||
|
fxLayoutAlign="center center" |
||||
|
class="mat-headline tb-absolute-fill">widgets-bundle.empty</span> |
||||
|
<span translate *ngIf="!widgetsBundle" |
||||
|
style="text-transform: uppercase; display: flex;" |
||||
|
fxLayoutAlign="center center" |
||||
|
class="mat-headline tb-absolute-fill">widget.select-widgets-bundle</span> |
||||
|
</div> |
||||
@ -0,0 +1,23 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2019 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
:host ::ng-deep { |
||||
|
.mat-tab-group { |
||||
|
.mat-tab-body-wrapper { |
||||
|
height: 100%; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,144 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2019 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { |
||||
|
Component, |
||||
|
OnDestroy, |
||||
|
OnInit, |
||||
|
ViewEncapsulation, |
||||
|
Input, |
||||
|
Output, |
||||
|
EventEmitter, |
||||
|
OnChanges, |
||||
|
SimpleChanges |
||||
|
} from '@angular/core'; |
||||
|
import { PageComponent } from '@shared/components/page.component'; |
||||
|
import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; |
||||
|
import { IAliasController } from '@core/api/widget-api.models'; |
||||
|
import { NULL_UUID } from '@shared/models/id/has-uuid'; |
||||
|
import { WidgetService } from '@core/http/widget.service'; |
||||
|
import { widgetType, Widget } from '@shared/models/widget.models'; |
||||
|
import { toWidgetInfo } from '@home/models/widget-component.models'; |
||||
|
import { DashboardCallbacks } from '../../models/dashboard-component.models'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-dashboard-widget-select', |
||||
|
templateUrl: './dashboard-widget-select.component.html', |
||||
|
styleUrls: ['./dashboard-widget-select.component.scss'] |
||||
|
}) |
||||
|
export class DashboardWidgetSelectComponent implements OnInit, OnChanges { |
||||
|
|
||||
|
@Input() |
||||
|
widgetsBundle: WidgetsBundle; |
||||
|
|
||||
|
@Input() |
||||
|
aliasController: IAliasController; |
||||
|
|
||||
|
@Output() |
||||
|
widgetSelected: EventEmitter<Widget> = new EventEmitter<Widget>(); |
||||
|
|
||||
|
timeseriesWidgetTypes: Array<Widget> = []; |
||||
|
latestWidgetTypes: Array<Widget> = []; |
||||
|
rpcWidgetTypes: Array<Widget> = []; |
||||
|
alarmWidgetTypes: Array<Widget> = []; |
||||
|
staticWidgetTypes: Array<Widget> = []; |
||||
|
|
||||
|
callbacks: DashboardCallbacks = { |
||||
|
onWidgetClicked: this.onWidgetClicked.bind(this) |
||||
|
}; |
||||
|
|
||||
|
constructor(private widgetsService: WidgetService) { |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
} |
||||
|
|
||||
|
ngOnChanges(changes: SimpleChanges): void { |
||||
|
for (const propName of Object.keys(changes)) { |
||||
|
const change = changes[propName]; |
||||
|
if (change.currentValue !== change.previousValue && change.currentValue) { |
||||
|
if (propName === 'widgetsBundle') { |
||||
|
this.loadLibrary(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private loadLibrary() { |
||||
|
this.timeseriesWidgetTypes.length = 0; |
||||
|
this.latestWidgetTypes.length = 0; |
||||
|
this.rpcWidgetTypes.length = 0; |
||||
|
this.alarmWidgetTypes.length = 0; |
||||
|
this.staticWidgetTypes.length = 0; |
||||
|
const bundleAlias = this.widgetsBundle.alias; |
||||
|
const isSystem = this.widgetsBundle.tenantId.id === NULL_UUID; |
||||
|
this.widgetsService.getBundleWidgetTypes(bundleAlias, |
||||
|
isSystem).subscribe( |
||||
|
(types) => { |
||||
|
types = types.sort((a, b) => b.createdTime - a.createdTime); |
||||
|
let top = 0; |
||||
|
types.forEach((type) => { |
||||
|
const widgetTypeInfo = toWidgetInfo(type); |
||||
|
const widget: Widget = { |
||||
|
typeId: type.id, |
||||
|
isSystemType: isSystem, |
||||
|
bundleAlias, |
||||
|
typeAlias: widgetTypeInfo.alias, |
||||
|
type: widgetTypeInfo.type, |
||||
|
title: widgetTypeInfo.widgetName, |
||||
|
sizeX: widgetTypeInfo.sizeX, |
||||
|
sizeY: widgetTypeInfo.sizeY, |
||||
|
row: top, |
||||
|
col: 0, |
||||
|
config: JSON.parse(widgetTypeInfo.defaultConfig) |
||||
|
}; |
||||
|
widget.config.title = widgetTypeInfo.widgetName; |
||||
|
switch (widgetTypeInfo.type) { |
||||
|
case widgetType.timeseries: |
||||
|
this.timeseriesWidgetTypes.push(widget); |
||||
|
break; |
||||
|
case widgetType.latest: |
||||
|
this.latestWidgetTypes.push(widget); |
||||
|
break; |
||||
|
case widgetType.rpc: |
||||
|
this.rpcWidgetTypes.push(widget); |
||||
|
break; |
||||
|
case widgetType.alarm: |
||||
|
this.alarmWidgetTypes.push(widget); |
||||
|
break; |
||||
|
case widgetType.static: |
||||
|
this.staticWidgetTypes.push(widget); |
||||
|
break; |
||||
|
} |
||||
|
top += widget.sizeY; |
||||
|
}); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
hasWidgetTypes() { |
||||
|
return this.timeseriesWidgetTypes.length > 0 || |
||||
|
this.latestWidgetTypes.length > 0 || |
||||
|
this.rpcWidgetTypes.length > 0 || |
||||
|
this.alarmWidgetTypes.length > 0 || |
||||
|
this.staticWidgetTypes.length > 0; |
||||
|
} |
||||
|
|
||||
|
private onWidgetClicked($event: Event, widget: Widget, index: number): void { |
||||
|
this.widgetSelected.emit(widget); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2019 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
:host ::ng-deep { |
||||
|
button.tb-layout-button { |
||||
|
width: 100%; |
||||
|
max-width: 240px; |
||||
|
height: 100%; |
||||
|
.mat-button-wrapper { |
||||
|
padding: 40px; |
||||
|
line-height: 18px; |
||||
|
span { |
||||
|
font-size: 18px; |
||||
|
font-weight: 400; |
||||
|
white-space: normal; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,71 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2019 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<form #widgetForm="ngForm" [formGroup]="layoutsFormGroup" (ngSubmit)="save()"> |
||||
|
<mat-toolbar fxLayout="row" color="primary"> |
||||
|
<h2 translate>layout.manage</h2> |
||||
|
<span fxFlex></span> |
||||
|
<button mat-button mat-icon-button |
||||
|
(click)="cancel()" |
||||
|
type="button"> |
||||
|
<mat-icon class="material-icons">close</mat-icon> |
||||
|
</button> |
||||
|
</mat-toolbar> |
||||
|
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
||||
|
</mat-progress-bar> |
||||
|
<div mat-dialog-content> |
||||
|
<fieldset [disabled]="isLoading$ | async" fxLayout="column" fxLayoutGap="8px"> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<mat-checkbox fxFlex formControlName="main"> |
||||
|
{{ 'layout.main' | translate }} |
||||
|
</mat-checkbox> |
||||
|
<mat-checkbox fxFlex formControlName="right"> |
||||
|
{{ 'layout.right' | translate }} |
||||
|
</mat-checkbox> |
||||
|
</div> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<button fxFlex fxLayout="column" |
||||
|
type="button" mat-button mat-raised-button color="primary" |
||||
|
class="tb-layout-button" |
||||
|
(click)="openLayoutSettings('main')"> |
||||
|
<span translate>layout.main</span> |
||||
|
</button> |
||||
|
<button fxFlex fxLayout="column" [fxShow]="layoutsFormGroup.get('right').value" |
||||
|
type="button" mat-button mat-raised-button color="primary" |
||||
|
class="tb-layout-button" |
||||
|
(click)="openLayoutSettings('right')"> |
||||
|
<span translate>layout.right</span> |
||||
|
</button> |
||||
|
</div> |
||||
|
</fieldset> |
||||
|
</div> |
||||
|
<div mat-dialog-actions fxLayout="row"> |
||||
|
<span fxFlex></span> |
||||
|
<button mat-button mat-raised-button color="primary" |
||||
|
type="submit" |
||||
|
[disabled]="(isLoading$ | async) || layoutsFormGroup.invalid || !layoutsFormGroup.dirty"> |
||||
|
{{ 'action.save' | translate }} |
||||
|
</button> |
||||
|
<button mat-button color="primary" |
||||
|
style="margin-right: 20px;" |
||||
|
type="button" |
||||
|
[disabled]="(isLoading$ | async)" |
||||
|
(click)="cancel()" cdkFocusInitial> |
||||
|
{{ 'action.cancel' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</form> |
||||
@ -0,0 +1,125 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2019 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; |
||||
|
import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm } from '@angular/forms'; |
||||
|
import { Router } from '@angular/router'; |
||||
|
import { DialogComponent } from '@app/shared/components/dialog.component'; |
||||
|
import { Widget, widgetTypesData } from '@shared/models/widget.models'; |
||||
|
import { UtilsService } from '@core/services/utils.service'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
import { EntityService } from '@core/http/entity.service'; |
||||
|
import { Dashboard, DashboardLayoutId, DashboardStateLayouts } from '@app/shared/models/dashboard.models'; |
||||
|
import { IAliasController } from '@core/api/widget-api.models'; |
||||
|
import { WidgetConfigComponentData, WidgetInfo } from '@home/models/widget-component.models'; |
||||
|
import { deepClone, isDefined, isString } from '@core/utils'; |
||||
|
import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; |
||||
|
import { MatDialog } from '@angular/material/dialog'; |
||||
|
import { |
||||
|
DashboardSettingsDialogComponent, |
||||
|
DashboardSettingsDialogData |
||||
|
} from '@home/pages/dashboard/dashboard-settings-dialog.component'; |
||||
|
|
||||
|
export interface ManageDashboardLayoutsDialogData { |
||||
|
layouts: DashboardStateLayouts; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-manage-dashboard-layouts-dialog', |
||||
|
templateUrl: './manage-dashboard-layouts-dialog.component.html', |
||||
|
providers: [{provide: ErrorStateMatcher, useExisting: ManageDashboardLayoutsDialogComponent}], |
||||
|
styleUrls: ['./layout-button.scss'] |
||||
|
}) |
||||
|
export class ManageDashboardLayoutsDialogComponent extends DialogComponent<ManageDashboardLayoutsDialogComponent, DashboardStateLayouts> |
||||
|
implements OnInit, ErrorStateMatcher { |
||||
|
|
||||
|
layoutsFormGroup: FormGroup; |
||||
|
|
||||
|
layouts: DashboardStateLayouts; |
||||
|
|
||||
|
submitted = false; |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
protected router: Router, |
||||
|
@Inject(MAT_DIALOG_DATA) public data: ManageDashboardLayoutsDialogData, |
||||
|
@SkipSelf() private errorStateMatcher: ErrorStateMatcher, |
||||
|
public dialogRef: MatDialogRef<ManageDashboardLayoutsDialogComponent, DashboardStateLayouts>, |
||||
|
private fb: FormBuilder, |
||||
|
private utils: UtilsService, |
||||
|
private dashboardUtils: DashboardUtilsService, |
||||
|
private translate: TranslateService, |
||||
|
private dialog: MatDialog) { |
||||
|
super(store, router, dialogRef); |
||||
|
|
||||
|
this.layouts = this.data.layouts; |
||||
|
this.layoutsFormGroup = this.fb.group({ |
||||
|
main: [{value: isDefined(this.layouts.main), disabled: true}, []], |
||||
|
right: [isDefined(this.layouts.right), []], |
||||
|
} |
||||
|
); |
||||
|
for (const l of Object.keys(this.layoutsFormGroup.controls)) { |
||||
|
const control = this.layoutsFormGroup.controls[l]; |
||||
|
if (!this.layouts[l]) { |
||||
|
this.layouts[l] = this.dashboardUtils.createDefaultLayoutData(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
} |
||||
|
|
||||
|
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { |
||||
|
const originalErrorState = this.errorStateMatcher.isErrorState(control, form); |
||||
|
const customErrorState = !!(control && control.invalid && this.submitted); |
||||
|
return originalErrorState || customErrorState; |
||||
|
} |
||||
|
|
||||
|
openLayoutSettings(layoutId: DashboardLayoutId) { |
||||
|
const gridSettings = deepClone(this.layouts[layoutId].gridSettings); |
||||
|
this.dialog.open<DashboardSettingsDialogComponent, DashboardSettingsDialogData, |
||||
|
DashboardSettingsDialogData>(DashboardSettingsDialogComponent, { |
||||
|
disableClose: true, |
||||
|
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
||||
|
data: { |
||||
|
settings: null, |
||||
|
gridSettings |
||||
|
} |
||||
|
}).afterClosed().subscribe((data) => { |
||||
|
if (data && data.gridSettings) { |
||||
|
this.dashboardUtils.updateLayoutSettings(this.layouts[layoutId], data.gridSettings); |
||||
|
this.layoutsFormGroup.markAsDirty(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
cancel(): void { |
||||
|
this.dialogRef.close(null); |
||||
|
} |
||||
|
|
||||
|
save(): void { |
||||
|
this.submitted = true; |
||||
|
for (const l of Object.keys(this.layoutsFormGroup.controls)) { |
||||
|
const control = this.layoutsFormGroup.controls[l]; |
||||
|
if (!control.value) { |
||||
|
delete this.layouts[l]; |
||||
|
} |
||||
|
} |
||||
|
this.dialogRef.close(this.layouts); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2019 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<form #widgetForm="ngForm"> |
||||
|
<mat-toolbar fxLayout="row" color="primary"> |
||||
|
<h2 translate>layout.select</h2> |
||||
|
<span fxFlex></span> |
||||
|
<button mat-button mat-icon-button |
||||
|
(click)="cancel()" |
||||
|
type="button"> |
||||
|
<mat-icon class="material-icons">close</mat-icon> |
||||
|
</button> |
||||
|
</mat-toolbar> |
||||
|
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
||||
|
</mat-progress-bar> |
||||
|
<div mat-dialog-content> |
||||
|
<fieldset [disabled]="isLoading$ | async" fxLayout="column" fxLayoutGap="8px"> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<button fxFlex fxLayout="column" |
||||
|
type="button" mat-button mat-raised-button color="primary" |
||||
|
class="tb-layout-button" |
||||
|
(click)="selectLayout('main')"> |
||||
|
<span translate>layout.main</span> |
||||
|
</button> |
||||
|
<button fxFlex fxLayout="column" |
||||
|
type="button" mat-button mat-raised-button color="primary" |
||||
|
class="tb-layout-button" |
||||
|
(click)="selectLayout('right')"> |
||||
|
<span translate>layout.right</span> |
||||
|
</button> |
||||
|
</div> |
||||
|
</fieldset> |
||||
|
</div> |
||||
|
</form> |
||||
@ -0,0 +1,50 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2019 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { Component, OnInit } from '@angular/core'; |
||||
|
import { MatDialogRef } from '@angular/material'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { Router } from '@angular/router'; |
||||
|
import { DialogComponent } from '@app/shared/components/dialog.component'; |
||||
|
import { DashboardLayoutId } from '@app/shared/models/dashboard.models'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-select-target-layout-dialog', |
||||
|
templateUrl: './select-target-layout-dialog.component.html', |
||||
|
styleUrls: ['./layout-button.scss'] |
||||
|
}) |
||||
|
export class SelectTargetLayoutDialogComponent extends DialogComponent<SelectTargetLayoutDialogComponent, DashboardLayoutId> |
||||
|
implements OnInit { |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
protected router: Router, |
||||
|
public dialogRef: MatDialogRef<SelectTargetLayoutDialogComponent, DashboardLayoutId>) { |
||||
|
super(store, router, dialogRef); |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
} |
||||
|
|
||||
|
selectLayout(layoutId: DashboardLayoutId) { |
||||
|
this.dialogRef.close(layoutId); |
||||
|
} |
||||
|
|
||||
|
cancel(): void { |
||||
|
this.dialogRef.close(null); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2019 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<div class="tb-container"> |
||||
|
<label class="tb-title">{{label}}</label> |
||||
|
<ng-container #flow="flow" |
||||
|
[flowConfig]="{singleFile: true}"> |
||||
|
<div class="tb-image-select-container"> |
||||
|
<div class="tb-image-preview-container"> |
||||
|
<div *ngIf="!safeImageUrl" translate>dashboard.no-image</div> |
||||
|
<img *ngIf="safeImageUrl" class="tb-image-preview" [src]="safeImageUrl" /> |
||||
|
</div> |
||||
|
<div class="tb-image-clear-container"> |
||||
|
<button mat-button mat-icon-button color="primary" |
||||
|
type="button" |
||||
|
(click)="clearImage()" |
||||
|
class="tb-image-clear-btn" |
||||
|
matTooltip="{{ 'action.remove' | translate }}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon>close</mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
<div class="drop-area tb-flow-drop" |
||||
|
flowDrop |
||||
|
[flow]="flow.flowJs"> |
||||
|
<label for="select" translate>dashboard.drop-image</label> |
||||
|
<input class="file-input" flowButton [flow]="flow.flowJs" [flowAttributes]="{accept: 'image/*'}" id="select"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
</div> |
||||
@ -0,0 +1,104 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2019 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
@import "../../../scss/constants"; |
||||
|
|
||||
|
$previewSize: 100px !default; |
||||
|
|
||||
|
:host { |
||||
|
|
||||
|
.tb-container { |
||||
|
margin-top: 0px; |
||||
|
label.tb-title { |
||||
|
display: block; |
||||
|
padding-bottom: 8px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.tb-image-select-container { |
||||
|
position: relative; |
||||
|
width: 100%; |
||||
|
height: $previewSize; |
||||
|
} |
||||
|
|
||||
|
.tb-image-preview { |
||||
|
width: auto; |
||||
|
max-width: $previewSize; |
||||
|
height: auto; |
||||
|
max-height: $previewSize; |
||||
|
} |
||||
|
|
||||
|
.tb-image-preview-container { |
||||
|
position: relative; |
||||
|
float: left; |
||||
|
width: $previewSize; |
||||
|
height: $previewSize; |
||||
|
margin-right: 12px; |
||||
|
vertical-align: top; |
||||
|
border: solid 1px; |
||||
|
|
||||
|
div { |
||||
|
width: 100%; |
||||
|
font-size: 18px; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
div, |
||||
|
.tb-image-preview { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.tb-image-clear-container { |
||||
|
position: relative; |
||||
|
float: right; |
||||
|
width: 48px; |
||||
|
height: $previewSize; |
||||
|
} |
||||
|
|
||||
|
.tb-image-clear-btn { |
||||
|
position: absolute !important; |
||||
|
top: 50%; |
||||
|
transform: translate(0%, -50%) !important; |
||||
|
} |
||||
|
|
||||
|
.file-input { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
.tb-flow-drop { |
||||
|
position: relative; |
||||
|
height: $previewSize; |
||||
|
overflow: hidden; |
||||
|
border: dashed 2px; |
||||
|
|
||||
|
label { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
justify-content: center; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
font-size: 16px; |
||||
|
text-align: center; |
||||
|
|
||||
|
@media #{$mat-gt-sm} { |
||||
|
font-size: 24px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,143 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2019 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild, AfterViewInit, OnDestroy } from '@angular/core'; |
||||
|
import { PageComponent } from '@shared/components/page.component'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { DataKey, DatasourceType } from '@shared/models/widget.models'; |
||||
|
import { |
||||
|
ControlValueAccessor, |
||||
|
FormBuilder, |
||||
|
FormControl, |
||||
|
FormGroup, |
||||
|
NG_VALIDATORS, |
||||
|
NG_VALUE_ACCESSOR, |
||||
|
Validator, |
||||
|
Validators |
||||
|
} from '@angular/forms'; |
||||
|
import { UtilsService } from '@core/services/utils.service'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
import { MatDialog } from '@angular/material/dialog'; |
||||
|
import { EntityService } from '@core/http/entity.service'; |
||||
|
import { DataKeysCallbacks } from '@home/components/widget/data-keys.component.models'; |
||||
|
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; |
||||
|
import { Observable, of, Subscription } from 'rxjs'; |
||||
|
import { map, mergeMap, tap } from 'rxjs/operators'; |
||||
|
import { alarmFields } from '@shared/models/alarm.models'; |
||||
|
import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
||||
|
import { DialogService } from '@core/services/dialog.service'; |
||||
|
import { FlowDirective } from '@flowjs/ngx-flow'; |
||||
|
import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-image-input', |
||||
|
templateUrl: './image-input.component.html', |
||||
|
styleUrls: ['./image-input.component.scss'], |
||||
|
providers: [ |
||||
|
{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => ImageInputComponent), |
||||
|
multi: true |
||||
|
} |
||||
|
] |
||||
|
}) |
||||
|
export class ImageInputComponent extends PageComponent implements AfterViewInit, OnDestroy, ControlValueAccessor { |
||||
|
|
||||
|
@Input() |
||||
|
label: string; |
||||
|
|
||||
|
private requiredValue: boolean; |
||||
|
get required(): boolean { |
||||
|
return this.requiredValue; |
||||
|
} |
||||
|
@Input() |
||||
|
set required(value: boolean) { |
||||
|
const newVal = coerceBooleanProperty(value); |
||||
|
if (this.requiredValue !== newVal) { |
||||
|
this.requiredValue = newVal; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Input() |
||||
|
disabled: boolean; |
||||
|
|
||||
|
imageUrl: string; |
||||
|
safeImageUrl: SafeUrl; |
||||
|
|
||||
|
@ViewChild('flow', {static: true}) |
||||
|
flow: FlowDirective; |
||||
|
|
||||
|
autoUploadSubscription: Subscription; |
||||
|
|
||||
|
private propagateChange = null; |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
private sanitizer: DomSanitizer) { |
||||
|
super(store); |
||||
|
} |
||||
|
|
||||
|
ngAfterViewInit() { |
||||
|
this.autoUploadSubscription = this.flow.events$.subscribe(event => { |
||||
|
if (event.type === 'fileAdded') { |
||||
|
const file = (event.event[0] as flowjs.FlowFile).file; |
||||
|
const reader = new FileReader(); |
||||
|
reader.onload = (loadEvent) => { |
||||
|
if (typeof reader.result === 'string' && reader.result.startsWith('data:image/')) { |
||||
|
this.imageUrl = reader.result; |
||||
|
this.safeImageUrl = this.sanitizer.bypassSecurityTrustUrl(this.imageUrl); |
||||
|
this.updateModel(); |
||||
|
} |
||||
|
}; |
||||
|
reader.readAsDataURL(file); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy() { |
||||
|
this.autoUploadSubscription.unsubscribe(); |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any): void { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: any): void { |
||||
|
} |
||||
|
|
||||
|
setDisabledState(isDisabled: boolean): void { |
||||
|
this.disabled = isDisabled; |
||||
|
} |
||||
|
|
||||
|
writeValue(value: string): void { |
||||
|
this.imageUrl = value; |
||||
|
if (this.imageUrl) { |
||||
|
this.safeImageUrl = this.sanitizer.bypassSecurityTrustUrl(this.imageUrl); |
||||
|
} else { |
||||
|
this.safeImageUrl = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private updateModel() { |
||||
|
this.propagateChange(this.imageUrl); |
||||
|
} |
||||
|
|
||||
|
clearImage() { |
||||
|
this.imageUrl = null; |
||||
|
this.safeImageUrl = null; |
||||
|
this.updateModel(); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue