Browse Source

Merge branch 'develop/3.0' of https://github.com/thingsboard/thingsboard into map/3.0

pull/2664/head
Adsumus 7 years ago
parent
commit
9766267dbe
  1. 1
      application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java
  2. 3
      pom.xml
  3. 2
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
  4. 1
      ui-ngx/src/app/core/public-api.ts
  5. 2
      ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.html
  6. 7
      ui-ngx/src/app/modules/home/components/entity/entities-table.component.html
  7. 4
      ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts
  8. 94
      ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts
  9. 4
      ui-ngx/src/app/modules/home/models/datasource/entity-datasource.ts
  10. 5
      ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts
  11. 13
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts
  12. 4
      ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html
  13. 9
      ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts
  14. 6
      ui-ngx/src/app/modules/home/pages/tenant/tenant.module.ts
  15. 7
      ui-ngx/src/app/modules/home/pages/tenant/tenants-table-config.resolver.ts
  16. 17
      ui-ngx/src/app/shared/components/breadcrumb.component.ts
  17. 2
      ui-ngx/src/app/shared/components/breadcrumb.ts
  18. 21
      ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.scss
  19. 20
      ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts
  20. 16
      ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts
  21. 3
      ui-ngx/src/app/shared/components/json-form/react/json-form-ace-editor.tsx
  22. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-array.tsx
  23. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-base-component.tsx
  24. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-checkbox.tsx
  25. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-color.tsx
  26. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-css.tsx
  27. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-date.tsx
  28. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-fieldset.tsx
  29. 15
      ui-ngx/src/app/shared/components/json-form/react/json-form-help.tsx
  30. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-html.tsx
  31. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-image.tsx
  32. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-javascript.tsx
  33. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-json.tsx
  34. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-number.tsx
  35. 15
      ui-ngx/src/app/shared/components/json-form/react/json-form-radios.tsx
  36. 3
      ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx
  37. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-react.tsx
  38. 2
      ui-ngx/src/app/shared/components/json-form/react/json-form-schema-form.tsx
  39. 15
      ui-ngx/src/app/shared/components/json-form/react/json-form-select.tsx
  40. 14
      ui-ngx/src/app/shared/components/json-form/react/json-form-text.tsx
  41. 9
      ui-ngx/src/app/shared/components/json-form/react/json-form.scss
  42. 2
      ui-ngx/src/app/shared/components/nav-tree.component.ts
  43. 1
      ui-ngx/src/app/shared/components/public-api.ts
  44. 4
      ui-ngx/src/app/shared/components/time/datetime-period.component.html
  45. 6
      ui-ngx/src/app/shared/components/time/datetime-period.component.scss
  46. 3
      ui-ngx/src/app/shared/components/time/datetime.component.scss
  47. 6
      ui-ngx/src/app/shared/components/time/datetime.component.ts
  48. 4
      ui-ngx/src/app/shared/components/time/timeinterval.component.html
  49. 6
      ui-ngx/src/app/shared/components/time/timeinterval.component.scss
  50. 11
      ui-ngx/src/app/shared/components/time/timeinterval.component.ts
  51. 8
      ui-ngx/src/app/shared/components/time/timewindow-panel.component.html
  52. 4
      ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss
  53. 20
      ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts
  54. 4
      ui-ngx/src/app/shared/components/time/timewindow.component.html
  55. 1
      ui-ngx/src/app/shared/components/time/timewindow.component.scss
  56. 32
      ui-ngx/src/app/shared/components/time/timewindow.component.ts
  57. 46
      ui-ngx/src/app/shared/models/page/page-link.ts
  58. 4
      ui-ngx/src/app/shared/models/rule-node.models.ts
  59. 0
      ui-ngx/src/assets/.gitkeep
  60. 3
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  61. 35
      ui/src/app/widget/lib/web-camera-input-widget.js

1
application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java

@ -280,6 +280,7 @@ public class DefaultMailService implements MailService {
} else { } else {
message = exception.getMessage(); message = exception.getMessage();
} }
log.warn("Unable to send mail: {}", message);
return new ThingsboardException(String.format("Unable to send mail: %s", message), return new ThingsboardException(String.format("Unable to send mail: %s", message),
ThingsboardErrorCode.GENERAL); ThingsboardErrorCode.GENERAL);
} }

3
pom.xml

@ -320,12 +320,15 @@
<exclude>docker/haproxy/**</exclude> <exclude>docker/haproxy/**</exclude>
<exclude>docker/tb-node/**</exclude> <exclude>docker/tb-node/**</exclude>
<exclude>ui/**</exclude> <exclude>ui/**</exclude>
<exclude>src/browserslist</exclude>
<exclude>**/*.raw</exclude>
</excludes> </excludes>
<mapping> <mapping>
<proto>JAVADOC_STYLE</proto> <proto>JAVADOC_STYLE</proto>
<cql>DOUBLEDASHES_STYLE</cql> <cql>DOUBLEDASHES_STYLE</cql>
<scss>JAVADOC_STYLE</scss> <scss>JAVADOC_STYLE</scss>
<jsx>SLASHSTAR_STYLE</jsx> <jsx>SLASHSTAR_STYLE</jsx>
<tsx>SLASHSTAR_STYLE</tsx>
<conf>SCRIPT_STYLE</conf> <conf>SCRIPT_STYLE</conf>
<gradle>JAVADOC_STYLE</gradle> <gradle>JAVADOC_STYLE</gradle>
</mapping> </mapping>

2
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js

File diff suppressed because one or more lines are too long

1
ui-ngx/src/app/core/public-api.ts

@ -21,3 +21,4 @@ export * from './services/public-api';
export * from './ws/telemetry-websocket.service'; export * from './ws/telemetry-websocket.service';
export * from './core.state'; export * from './core.state';
export * from './core.module'; export * from './core.module';
export * from './utils';

2
ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.html

@ -35,7 +35,7 @@
<div mat-dialog-actions fxLayoutAlign="end center"> <div mat-dialog-actions fxLayoutAlign="end center">
<button mat-raised-button color="primary" <button mat-raised-button color="primary"
type="submit" type="submit"
[disabled]="(isLoading$ | async) || detailsForm.invalid || !detailsForm.dirty"> [disabled]="(isLoading$ | async) || detailsForm?.invalid || !detailsForm?.dirty">
{{ 'action.add' | translate }} {{ 'action.add' | translate }}
</button> </button>
<button mat-button color="primary" <button mat-button color="primary"

7
ui-ngx/src/app/modules/home/components/entity/entities-table.component.html

@ -225,9 +225,12 @@
'tb-current-entity': dataSource.isCurrentEntity(entity)}" 'tb-current-entity': dataSource.isCurrentEntity(entity)}"
*matRowDef="let entity; columns: displayedColumns;" (click)="onRowClick($event, entity)"></mat-row> *matRowDef="let entity; columns: displayedColumns;" (click)="onRowClick($event, entity)"></mat-row>
</table> </table>
<span [fxShow]="!(isLoading$ | async) && (dataSource.isEmpty() | async)" <span [fxShow]="!(isLoading$ | async) && (dataSource.isEmpty() | async) && !dataSource.dataLoading"
fxLayoutAlign="center center" fxLayoutAlign="center center"
class="no-data-found" translate>{{ translations.noEntities }}</span> class="no-data-found">{{ translations.noEntities | translate }}</span>
<span [fxShow]="dataSource.dataLoading"
fxLayoutAlign="center center"
class="no-data-found">{{ 'common.loading' | translate }}</span>
</div> </div>
<mat-divider *ngIf="displayPagination"></mat-divider> <mat-divider *ngIf="displayPagination"></mat-divider>
<mat-paginator *ngIf="displayPagination" <mat-paginator *ngIf="displayPagination"

4
ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts

@ -345,6 +345,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
(entity) => { (entity) => {
if (entity) { if (entity) {
this.updateData(); this.updateData();
this.entitiesTableConfig.entityAdded(entity);
} }
} }
); );
@ -352,6 +353,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
onEntityUpdated(entity: BaseData<HasId>) { onEntityUpdated(entity: BaseData<HasId>) {
this.updateData(false); this.updateData(false);
this.entitiesTableConfig.entityUpdated(entity);
} }
onEntityAction(action: EntityAction<BaseData<HasId>>) { onEntityAction(action: EntityAction<BaseData<HasId>>) {
@ -375,6 +377,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
this.entitiesTableConfig.deleteEntity(entity.id).subscribe( this.entitiesTableConfig.deleteEntity(entity.id).subscribe(
() => { () => {
this.updateData(); this.updateData();
this.entitiesTableConfig.entitiesDeleted([entity.id]);
} }
); );
} }
@ -402,6 +405,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
forkJoin(tasks).subscribe( forkJoin(tasks).subscribe(
() => { () => {
this.updateData(); this.updateData();
this.entitiesTableConfig.entitiesDeleted(entities.map((e) => e.id));
} }
); );
} }

94
ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts

@ -18,7 +18,7 @@ import {
AfterViewInit, AfterViewInit,
ChangeDetectionStrategy, ChangeDetectorRef, ChangeDetectionStrategy, ChangeDetectorRef,
Component, Component,
ComponentFactoryResolver, ComponentFactoryResolver, ComponentRef,
EventEmitter, Injector, EventEmitter, Injector,
Input, Input,
OnDestroy, OnDestroy,
@ -60,7 +60,10 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit
@Output() @Output()
entityAction = new EventEmitter<EntityAction<BaseData<HasId>>>(); entityAction = new EventEmitter<EntityAction<BaseData<HasId>>>();
entityComponentRef: ComponentRef<EntityComponent<BaseData<HasId>>>;
entityComponent: EntityComponent<BaseData<HasId>>; entityComponent: EntityComponent<BaseData<HasId>>;
entityTabsComponentRef: ComponentRef<EntityTabsComponent<BaseData<HasId>>>;
entityTabsComponent: EntityTabsComponent<BaseData<HasId>>; entityTabsComponent: EntityTabsComponent<BaseData<HasId>>;
detailsForm: NgForm; detailsForm: NgForm;
@ -84,7 +87,9 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit
editingEntity: BaseData<HasId>; editingEntity: BaseData<HasId>;
private currentEntityId: HasId; private currentEntityId: HasId;
private entityActionSubscription: Subscription; private subscriptions: Subscription[] = [];
private viewInited = false;
private pendingTabs: MatTab[];
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
private injector: Injector, private injector: Injector,
@ -103,12 +108,14 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit
@Input() @Input()
set entitiesTableConfig(entitiesTableConfig: EntityTableConfig<BaseData<HasId>>) { set entitiesTableConfig(entitiesTableConfig: EntityTableConfig<BaseData<HasId>>) {
this.entitiesTableConfigValue = entitiesTableConfig; if (this.entitiesTableConfigValue !== entitiesTableConfig) {
if (this.entityComponent) { this.entitiesTableConfigValue = entitiesTableConfig;
this.entityComponent.entitiesTableConfig = entitiesTableConfig; if (this.entitiesTableConfigValue) {
} this.currentEntityId = null;
if (this.entityTabsComponent) { this.isEdit = false;
this.entityTabsComponent.entitiesTableConfig = entitiesTableConfig; this.entity = null;
this.init();
}
} }
} }
@ -131,19 +138,33 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit
} }
ngOnInit(): void { ngOnInit(): void {
this.init();
}
private init() {
this.translations = this.entitiesTableConfig.entityTranslations; this.translations = this.entitiesTableConfig.entityTranslations;
this.resources = this.entitiesTableConfig.entityResources; this.resources = this.entitiesTableConfig.entityResources;
this.buildEntityComponent(); this.buildEntityComponent();
} }
private clearSubscriptions() {
this.subscriptions.forEach((subscription) => {
subscription.unsubscribe();
});
this.subscriptions.length = 0;
}
ngOnDestroy(): void { ngOnDestroy(): void {
super.ngOnDestroy(); super.ngOnDestroy();
if (this.entityActionSubscription) { this.clearSubscriptions();
this.entityActionSubscription.unsubscribe();
}
} }
buildEntityComponent() { buildEntityComponent() {
this.clearSubscriptions();
if (this.entityComponentRef) {
this.entityComponentRef.destroy();
this.entityComponentRef = null;
}
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.entitiesTableConfig.entityComponent); const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.entitiesTableConfig.entityComponent);
const viewContainerRef = this.entityDetailsFormAnchor.viewContainerRef; const viewContainerRef = this.entityDetailsFormAnchor.viewContainerRef;
viewContainerRef.clear(); viewContainerRef.clear();
@ -162,29 +183,46 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit
parent: this.injector parent: this.injector
} }
); );
const componentRef = viewContainerRef.createComponent(componentFactory, 0, injector); this.entityComponentRef = viewContainerRef.createComponent(componentFactory, 0, injector);
this.entityComponent = componentRef.instance; this.entityComponent = this.entityComponentRef.instance;
this.entityComponent.isEdit = this.isEdit; this.entityComponent.isEdit = this.isEdit;
this.detailsForm = this.entityComponent.entityNgForm; this.detailsForm = this.entityComponent.entityNgForm;
this.entityActionSubscription = this.entityComponent.entityAction.subscribe((action) => { this.subscriptions.push(this.entityComponent.entityAction.subscribe((action) => {
this.entityAction.emit(action); this.entityAction.emit(action);
}); }));
this.buildEntityTabsComponent(); this.buildEntityTabsComponent();
this.entityComponent.entityForm.valueChanges.subscribe(() => { this.subscriptions.push(this.entityComponent.entityForm.valueChanges.subscribe(() => {
this.cd.detectChanges(); this.cd.detectChanges();
}); }));
} }
buildEntityTabsComponent() { buildEntityTabsComponent() {
if (this.entityTabsComponentRef) {
this.entityTabsComponentRef.destroy();
this.entityTabsComponentRef = null;
}
const viewContainerRef = this.entityTabsAnchor.viewContainerRef;
viewContainerRef.clear();
this.entityTabsComponent = null;
if (this.entitiesTableConfig.entityTabsComponent) { if (this.entitiesTableConfig.entityTabsComponent) {
const componentTabsFactory = this.componentFactoryResolver.resolveComponentFactory(this.entitiesTableConfig.entityTabsComponent); const componentTabsFactory = this.componentFactoryResolver.resolveComponentFactory(this.entitiesTableConfig.entityTabsComponent);
const viewContainerRef = this.entityTabsAnchor.viewContainerRef; this.entityTabsComponentRef = viewContainerRef.createComponent(componentTabsFactory);
viewContainerRef.clear(); this.entityTabsComponent = this.entityTabsComponentRef.instance;
const componentTabsRef = viewContainerRef.createComponent(componentTabsFactory);
this.entityTabsComponent = componentTabsRef.instance;
this.entityTabsComponent.isEdit = this.isEdit; this.entityTabsComponent.isEdit = this.isEdit;
this.entityTabsComponent.entitiesTableConfig = this.entitiesTableConfig; this.entityTabsComponent.entitiesTableConfig = this.entitiesTableConfig;
this.entityTabsComponent.detailsForm = this.detailsForm; this.entityTabsComponent.detailsForm = this.detailsForm;
this.subscriptions.push(this.entityTabsComponent.entityTabsChanged.subscribe(
(entityTabs) => {
if (entityTabs) {
if (this.viewInited) {
this.matTabGroup._tabs.reset([...this.inclusiveTabs.toArray(), ...entityTabs]);
this.matTabGroup._tabs.notifyOnChanges();
} else {
this.pendingTabs = entityTabs;
}
}
}
));
} }
} }
@ -254,15 +292,11 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit
} }
ngAfterViewInit(): void { ngAfterViewInit(): void {
if (this.entityTabsComponent) { this.viewInited = true;
this.entityTabsComponent.entityTabsChanged.subscribe( if (this.pendingTabs) {
(entityTabs) => { this.matTabGroup._tabs.reset([...this.inclusiveTabs.toArray(), ...this.pendingTabs]);
if (entityTabs) { this.matTabGroup._tabs.notifyOnChanges();
this.matTabGroup._tabs.reset([...this.inclusiveTabs.toArray(), ...entityTabs]); this.pendingTabs = null;
this.matTabGroup._tabs.notifyOnChanges();
}
}
);
} }
} }

4
ui-ngx/src/app/modules/home/models/datasource/entity-datasource.ts

@ -35,6 +35,8 @@ export class EntitiesDataSource<T extends BaseData<HasId>, P extends PageLink =
public currentEntity: T = null; public currentEntity: T = null;
public dataLoading = true;
constructor(private fetchFunction: EntitiesFetchFunction<T, P>, constructor(private fetchFunction: EntitiesFetchFunction<T, P>,
protected selectionEnabledFunction: EntityBooleanFunction<T>, protected selectionEnabledFunction: EntityBooleanFunction<T>,
protected dataLoadedFunction: (col?: number, row?: number) => void) {} protected dataLoadedFunction: (col?: number, row?: number) => void) {}
@ -56,6 +58,7 @@ export class EntitiesDataSource<T extends BaseData<HasId>, P extends PageLink =
} }
loadEntities(pageLink: P): Observable<PageData<T>> { loadEntities(pageLink: P): Observable<PageData<T>> {
this.dataLoading = true;
const result = new ReplaySubject<PageData<T>>(); const result = new ReplaySubject<PageData<T>>();
this.fetchFunction(pageLink).pipe( this.fetchFunction(pageLink).pipe(
tap(() => { tap(() => {
@ -68,6 +71,7 @@ export class EntitiesDataSource<T extends BaseData<HasId>, P extends PageLink =
this.pageDataSubject.next(pageData); this.pageDataSubject.next(pageData);
result.next(pageData); result.next(pageData);
this.dataLoadedFunction(); this.dataLoadedFunction();
this.dataLoading = false;
} }
); );
return result; return result;

5
ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts

@ -33,6 +33,8 @@ import { EntityTabsComponent } from '../../components/entity/entity-tabs.compone
export type EntityBooleanFunction<T extends BaseData<HasId>> = (entity: T) => boolean; export type EntityBooleanFunction<T extends BaseData<HasId>> = (entity: T) => boolean;
export type EntityStringFunction<T extends BaseData<HasId>> = (entity: T) => string; export type EntityStringFunction<T extends BaseData<HasId>> = (entity: T) => string;
export type EntityVoidFunction<T extends BaseData<HasId>> = (entity: T) => void;
export type EntityIdsVoidFunction<T extends BaseData<HasId>> = (ids: HasUUID[]) => void;
export type EntityCountStringFunction = (count: number) => string; export type EntityCountStringFunction = (count: number) => string;
export type EntityTwoWayOperation<T extends BaseData<HasId>> = (entity: T) => Observable<T>; export type EntityTwoWayOperation<T extends BaseData<HasId>> = (entity: T) => Observable<T>;
export type EntityByIdOperation<T extends BaseData<HasId>> = (id: HasUUID) => Observable<T>; export type EntityByIdOperation<T extends BaseData<HasId>> = (id: HasUUID) => Observable<T>;
@ -175,6 +177,9 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P
onEntityAction: EntityActionFunction<T> = () => false; onEntityAction: EntityActionFunction<T> = () => false;
handleRowClick: EntityRowClickFunction<L> = () => false; handleRowClick: EntityRowClickFunction<L> = () => false;
entityTitle: EntityStringFunction<T> = (entity) => entity?.name; entityTitle: EntityStringFunction<T> = (entity) => entity?.name;
entityAdded: EntityVoidFunction<T> = () => {};
entityUpdated: EntityVoidFunction<T> = () => {};
entitiesDeleted: EntityIdsVoidFunction<T> = () => {};
} }
export function checkBoxCell(value: boolean): string { export function checkBoxCell(value: boolean): string {

13
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts

@ -32,7 +32,7 @@ import * as RxJs from 'rxjs';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import * as RxJsOperators from 'rxjs/operators'; import * as RxJsOperators from 'rxjs/operators';
import { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb'; import { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb';
import { ResolvedRuleChainMetaData, RuleChain, RuleChainImport } from '@shared/models/rule-chain.models'; import { ResolvedRuleChainMetaData, RuleChain } from '@shared/models/rule-chain.models';
import { RuleChainService } from '@core/http/rule-chain.service'; import { RuleChainService } from '@core/http/rule-chain.service';
import { RuleChainPageComponent } from '@home/pages/rulechain/rulechain-page.component'; import { RuleChainPageComponent } from '@home/pages/rulechain/rulechain-page.component';
import { RuleNodeComponentDescriptor } from '@shared/models/rule-node.models'; import { RuleNodeComponentDescriptor } from '@shared/models/rule-node.models';
@ -40,15 +40,18 @@ import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import * as AngularCommon from '@angular/common'; import * as AngularCommon from '@angular/common';
import * as AngularForms from '@angular/forms'; import * as AngularForms from '@angular/forms';
import * as AngularRouter from '@angular/router';
import * as AngularCdkCoercion from '@angular/cdk/coercion'; import * as AngularCdkCoercion from '@angular/cdk/coercion';
import * as AngularCdkKeycodes from '@angular/cdk/keycodes'; import * as AngularCdkKeycodes from '@angular/cdk/keycodes';
import * as AngularMaterialChips from '@angular/material/chips'; import * as AngularMaterialChips from '@angular/material/chips';
import * as AngularMaterialAutocomplete from '@angular/material/autocomplete'; import * as AngularMaterialAutocomplete from '@angular/material/autocomplete';
import * as AngularMaterialDialog from '@angular/material/dialog';
import * as NgrxStore from '@ngrx/store'; import * as NgrxStore from '@ngrx/store';
import * as TranslateCore from '@ngx-translate/core'; import * as TranslateCore from '@ngx-translate/core';
import * as TbCore from '@core/public-api'; import * as TbCore from '@core/public-api';
import * as TbShared from '@shared/public-api'; import * as TbShared from '@shared/public-api';
import * as TbHomeComponents from '@home/components/public-api'; import * as TbHomeComponents from '@home/components/public-api';
import * as _moment from 'moment';
import { ItemBufferService } from '@core/public-api'; import { ItemBufferService } from '@core/public-api';
declare const SystemJS; declare const SystemJS;
@ -57,22 +60,22 @@ const ruleNodeConfigResourcesModulesMap = {
'@angular/core': SystemJS.newModule(AngularCore), '@angular/core': SystemJS.newModule(AngularCore),
'@angular/common': SystemJS.newModule(AngularCommon), '@angular/common': SystemJS.newModule(AngularCommon),
'@angular/forms': SystemJS.newModule(AngularForms), '@angular/forms': SystemJS.newModule(AngularForms),
'@angular/router': SystemJS.newModule(AngularRouter),
'@angular/cdk/keycodes': SystemJS.newModule(AngularCdkKeycodes), '@angular/cdk/keycodes': SystemJS.newModule(AngularCdkKeycodes),
'@angular/cdk/coercion': SystemJS.newModule(AngularCdkCoercion), '@angular/cdk/coercion': SystemJS.newModule(AngularCdkCoercion),
'@angular/material/chips': SystemJS.newModule(AngularMaterialChips), '@angular/material/chips': SystemJS.newModule(AngularMaterialChips),
'@angular/material/autocomplete': SystemJS.newModule(AngularMaterialAutocomplete), '@angular/material/autocomplete': SystemJS.newModule(AngularMaterialAutocomplete),
'@angular/material/dialog': SystemJS.newModule(AngularMaterialDialog),
'@ngrx/store': SystemJS.newModule(NgrxStore), '@ngrx/store': SystemJS.newModule(NgrxStore),
rxjs: SystemJS.newModule(RxJs), rxjs: SystemJS.newModule(RxJs),
'rxjs/operators': SystemJS.newModule(RxJsOperators), 'rxjs/operators': SystemJS.newModule(RxJsOperators),
'@ngx-translate/core': SystemJS.newModule(TranslateCore), '@ngx-translate/core': SystemJS.newModule(TranslateCore),
'@core/public-api': SystemJS.newModule(TbCore), '@core/public-api': SystemJS.newModule(TbCore),
'@shared/public-api': SystemJS.newModule(TbShared), '@shared/public-api': SystemJS.newModule(TbShared),
'@home/components/public-api': SystemJS.newModule(TbHomeComponents) '@home/components/public-api': SystemJS.newModule(TbHomeComponents),
moment: SystemJS.newModule(_moment)
}; };
const t = SystemJS.newModule(AngularCore);
@Injectable() @Injectable()
export class RuleChainResolver implements Resolve<RuleChain> { export class RuleChainResolver implements Resolve<RuleChain> {

4
ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
--> -->
<div class="tb-details-buttons"> <div class="tb-details-buttons" fxLayout.xs="column">
<button mat-raised-button color="primary" <button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'manageTenantAdmins')" (click)="onEntityAction($event, 'manageTenantAdmins')"
@ -28,7 +28,7 @@
[fxShow]="!hideDelete() && !isEdit"> [fxShow]="!hideDelete() && !isEdit">
{{'tenant.delete' | translate }} {{'tenant.delete' | translate }}
</button> </button>
<div fxLayout="row"> <div fxLayout="row" fxLayout.xs="column">
<button mat-raised-button <button mat-raised-button
ngxClipboard ngxClipboard
(cbOnSuccess)="onTenantIdCopied($event)" (cbOnSuccess)="onTenantIdCopied($event)"

9
ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts

@ -18,11 +18,10 @@ import { Component, Inject } from '@angular/core';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Customer } from '@shared/models/customer.model'; import { Tenant } from '@app/shared/models/tenant.model';
import {Tenant} from '@app/shared/models/tenant.model'; import { ActionNotificationShow } from '@app/core/notification/notification.actions';
import {ActionNotificationShow} from '@app/core/notification/notification.actions'; import { TranslateService } from '@ngx-translate/core';
import {TranslateService} from '@ngx-translate/core'; import { ContactBasedComponent } from '../../components/entity/contact-based.component';
import {ContactBasedComponent} from '../../components/entity/contact-based.component';
import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models';
@Component({ @Component({

6
ui-ngx/src/app/modules/home/pages/tenant/tenant.module.ts

@ -17,9 +17,9 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { SharedModule } from '@shared/shared.module'; import { SharedModule } from '@shared/shared.module';
import {TenantComponent} from '@modules/home/pages/tenant/tenant.component'; import { TenantComponent } from '@modules/home/pages/tenant/tenant.component';
import {TenantRoutingModule} from '@modules/home/pages/tenant/tenant-routing.module'; import { TenantRoutingModule } from '@modules/home/pages/tenant/tenant-routing.module';
import {HomeComponentsModule} from '@modules/home/components/home-components.module'; import { HomeComponentsModule } from '@modules/home/components/home-components.module';
import { TenantTabsComponent } from '@home/pages/tenant/tenant-tabs.component'; import { TenantTabsComponent } from '@home/pages/tenant/tenant-tabs.component';
@NgModule({ @NgModule({

7
ui-ngx/src/app/modules/home/pages/tenant/tenants-table-config.resolver.ts

@ -27,14 +27,9 @@ import {
import { TenantService } from '@core/http/tenant.service'; import { TenantService } from '@core/http/tenant.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models';
EntityType,
entityTypeResources,
entityTypeTranslations
} from '@shared/models/entity-type.models';
import { TenantComponent } from '@modules/home/pages/tenant/tenant.component'; import { TenantComponent } from '@modules/home/pages/tenant/tenant.component';
import { EntityAction } from '@home/models/entity/entity-component.models'; import { EntityAction } from '@home/models/entity/entity-component.models';
import { User } from '@shared/models/user.model';
import { TenantTabsComponent } from '@home/pages/tenant/tenant-tabs.component'; import { TenantTabsComponent } from '@home/pages/tenant/tenant-tabs.component';
@Injectable() @Injectable()

17
ui-ngx/src/app/shared/components/breadcrumb.component.ts

@ -61,8 +61,19 @@ export class BreadcrumbComponent implements OnInit, OnDestroy {
} }
} }
private lastChild(route: ActivatedRouteSnapshot) {
let child = route;
while (child.firstChild !== null) {
child = child.firstChild;
}
return child;
}
buildBreadCrumbs(route: ActivatedRouteSnapshot, breadcrumbs: Array<BreadCrumb> = []): Array<BreadCrumb> { buildBreadCrumbs(route: ActivatedRouteSnapshot, breadcrumbs: Array<BreadCrumb> = [],
lastChild?: ActivatedRouteSnapshot): Array<BreadCrumb> {
if (!lastChild) {
lastChild = this.lastChild(route);
}
let newBreadcrumbs = breadcrumbs; let newBreadcrumbs = breadcrumbs;
if (route.routeConfig && route.routeConfig.data) { if (route.routeConfig && route.routeConfig.data) {
const breadcrumbConfig = route.routeConfig.data.breadcrumb as BreadCrumbConfig<any>; const breadcrumbConfig = route.routeConfig.data.breadcrumb as BreadCrumbConfig<any>;
@ -72,7 +83,7 @@ export class BreadcrumbComponent implements OnInit, OnDestroy {
let ignoreTranslate; let ignoreTranslate;
if (breadcrumbConfig.labelFunction) { if (breadcrumbConfig.labelFunction) {
labelFunction = () => { labelFunction = () => {
return breadcrumbConfig.labelFunction(route, this.translate, this.activeComponentValue); return breadcrumbConfig.labelFunction(route, this.translate, this.activeComponentValue, lastChild.data);
}; };
ignoreTranslate = true; ignoreTranslate = true;
} else { } else {
@ -96,7 +107,7 @@ export class BreadcrumbComponent implements OnInit, OnDestroy {
} }
} }
if (route.firstChild) { if (route.firstChild) {
return this.buildBreadCrumbs(route.firstChild, newBreadcrumbs); return this.buildBreadCrumbs(route.firstChild, newBreadcrumbs, lastChild);
} }
return newBreadcrumbs; return newBreadcrumbs;
} }

2
ui-ngx/src/app/shared/components/breadcrumb.ts

@ -27,7 +27,7 @@ export interface BreadCrumb {
queryParams: Params; queryParams: Params;
} }
export type BreadCrumbLabelFunction<C> = (route: ActivatedRouteSnapshot, translate: TranslateService, component: C) => string; export type BreadCrumbLabelFunction<C> = (route: ActivatedRouteSnapshot, translate: TranslateService, component: C, data?: any) => string;
export interface BreadCrumbConfig<C> { export interface BreadCrumbConfig<C> {
labelFunction: BreadCrumbLabelFunction<C>; labelFunction: BreadCrumbLabelFunction<C>;

21
ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.scss

@ -0,0 +1,21 @@
/**
* Copyright © 2016-2020 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-form-field-infix {
width: auto;
min-width: 100px;
}
}

20
ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts

@ -30,7 +30,7 @@ import { coerceBooleanProperty } from '@angular/cdk/coercion';
@Component({ @Component({
selector: 'tb-entity-autocomplete', selector: 'tb-entity-autocomplete',
templateUrl: './entity-autocomplete.component.html', templateUrl: './entity-autocomplete.component.html',
styleUrls: [], styleUrls: ['./entity-autocomplete.component.scss'],
providers: [{ providers: [{
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => EntityAutocompleteComponent), useExisting: forwardRef(() => EntityAutocompleteComponent),
@ -228,18 +228,32 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit
if (value != null) { if (value != null) {
if (typeof value === 'string') { if (typeof value === 'string') {
const targetEntityType = this.checkEntityType(this.entityTypeValue); const targetEntityType = this.checkEntityType(this.entityTypeValue);
this.entityService.getEntity(targetEntityType, value, {ignoreLoading: true}).subscribe( this.entityService.getEntity(targetEntityType, value, {ignoreLoading: true, ignoreErrors: true}).subscribe(
(entity) => { (entity) => {
this.modelValue = entity.id.id; this.modelValue = entity.id.id;
this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: false}); this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: false});
},
() => {
this.modelValue = null;
this.selectEntityFormGroup.get('entity').patchValue('', {emitEvent: false});
if (value !== null) {
this.propagateChange(this.modelValue);
}
} }
); );
} else { } else {
const targetEntityType = this.checkEntityType(value.entityType); const targetEntityType = this.checkEntityType(value.entityType);
this.entityService.getEntity(targetEntityType, value.id, {ignoreLoading: true}).subscribe( this.entityService.getEntity(targetEntityType, value.id, {ignoreLoading: true, ignoreErrors: true}).subscribe(
(entity) => { (entity) => {
this.modelValue = entity.id.id; this.modelValue = entity.id.id;
this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: false}); this.selectEntityFormGroup.get('entity').patchValue(entity, {emitEvent: false});
},
() => {
this.modelValue = null;
this.selectEntityFormGroup.get('entity').patchValue('', {emitEvent: false});
if (value !== null) {
this.propagateChange(this.modelValue);
}
} }
); );
} }

16
ui-ngx/src/app/shared/components/entity/entity-type-select.component.ts

@ -14,14 +14,14 @@
/// limitations under the License. /// limitations under the License.
/// ///
import {AfterViewInit, Component, forwardRef, Input, OnInit} from '@angular/core'; import { AfterViewInit, Component, forwardRef, Input, OnInit } from '@angular/core';
import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR} from '@angular/forms'; import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import {Store} from '@ngrx/store'; import { Store } from '@ngrx/store';
import {AppState} from '@app/core/core.state'; import { AppState } from '@app/core/core.state';
import {TranslateService} from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import {AliasEntityType, EntityType, entityTypeTranslations} from '@app/shared/models/entity-type.models'; import { AliasEntityType, EntityType, entityTypeTranslations } from '@app/shared/models/entity-type.models';
import {EntityService} from '@core/http/entity.service'; import { EntityService } from '@core/http/entity.service';
import {coerceBooleanProperty} from '@angular/cdk/coercion'; import { coerceBooleanProperty } from '@angular/cdk/coercion';
@Component({ @Component({
selector: 'tb-entity-type-select', selector: 'tb-entity-type-select',

3
ui-ngx/src/app/shared/components/json-form/react/json-form-ace-editor.tsx

@ -1,5 +1,5 @@
/* /*
* Copyright © 2016-2019 The Thingsboard Authors * Copyright © 2016-2020 The Thingsboard Authors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import * as React from 'react'; import * as React from 'react';
import ThingsboardBaseComponent from './json-form-base-component'; import ThingsboardBaseComponent from './json-form-base-component';
import reactCSS from 'reactcss'; import reactCSS from 'reactcss';

2
ui-ngx/src/app/shared/components/json-form/react/json-form-array.tsx

@ -1,5 +1,5 @@
/* /*
* Copyright © 2016-2019 The Thingsboard Authors * Copyright © 2016-2020 The Thingsboard Authors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

2
ui-ngx/src/app/shared/components/json-form/react/json-form-base-component.tsx

@ -1,5 +1,5 @@
/* /*
* Copyright © 2016-2019 The Thingsboard Authors * Copyright © 2016-2020 The Thingsboard Authors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

2
ui-ngx/src/app/shared/components/json-form/react/json-form-checkbox.tsx

@ -1,5 +1,5 @@
/* /*
* Copyright © 2016-2019 The Thingsboard Authors * Copyright © 2016-2020 The Thingsboard Authors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

2
ui-ngx/src/app/shared/components/json-form/react/json-form-color.tsx

@ -1,5 +1,5 @@
/* /*
* Copyright © 2016-2019 The Thingsboard Authors * Copyright © 2016-2020 The Thingsboard Authors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

2
ui-ngx/src/app/shared/components/json-form/react/json-form-css.tsx

@ -1,5 +1,5 @@
/* /*
* Copyright © 2016-2019 The Thingsboard Authors * Copyright © 2016-2020 The Thingsboard Authors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

2
ui-ngx/src/app/shared/components/json-form/react/json-form-date.tsx

@ -1,5 +1,5 @@
/* /*
* Copyright © 2016-2019 The Thingsboard Authors * Copyright © 2016-2020 The Thingsboard Authors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

2
ui-ngx/src/app/shared/components/json-form/react/json-form-fieldset.tsx

@ -1,5 +1,5 @@
/* /*
* Copyright © 2016-2019 The Thingsboard Authors * Copyright © 2016-2020 The Thingsboard Authors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

15
ui-ngx/src/app/shared/components/json-form/react/json-form-help.tsx

@ -1,3 +1,18 @@
/*
* Copyright © 2016-2020 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 * as React from 'react'; import * as React from 'react';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';

2
ui-ngx/src/app/shared/components/json-form/react/json-form-html.tsx

@ -1,5 +1,5 @@
/* /*
* Copyright © 2016-2019 The Thingsboard Authors * Copyright © 2016-2020 The Thingsboard Authors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

2
ui-ngx/src/app/shared/components/json-form/react/json-form-image.tsx

@ -1,5 +1,5 @@
/* /*
* Copyright © 2016-2019 The Thingsboard Authors * Copyright © 2016-2020 The Thingsboard Authors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

2
ui-ngx/src/app/shared/components/json-form/react/json-form-javascript.tsx

@ -1,5 +1,5 @@
/* /*
* Copyright © 2016-2019 The Thingsboard Authors * Copyright © 2016-2020 The Thingsboard Authors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

2
ui-ngx/src/app/shared/components/json-form/react/json-form-json.tsx

@ -1,5 +1,5 @@
/* /*
* Copyright © 2016-2019 The Thingsboard Authors * Copyright © 2016-2020 The Thingsboard Authors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

2
ui-ngx/src/app/shared/components/json-form/react/json-form-number.tsx

@ -1,5 +1,5 @@
/* /*
* Copyright © 2016-2019 The Thingsboard Authors * Copyright © 2016-2020 The Thingsboard Authors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

15
ui-ngx/src/app/shared/components/json-form/react/json-form-radios.tsx

@ -1,3 +1,18 @@
/*
* Copyright © 2016-2020 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 * as React from 'react'; import * as React from 'react';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models'; import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
import FormControlLabel from '@material-ui/core/FormControlLabel'; import FormControlLabel from '@material-ui/core/FormControlLabel';

3
ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx

@ -1,5 +1,5 @@
/* /*
* Copyright © 2016-2019 The Thingsboard Authors * Copyright © 2016-2020 The Thingsboard Authors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import * as React from 'react'; import * as React from 'react';
import ThingsboardBaseComponent from './json-form-base-component'; import ThingsboardBaseComponent from './json-form-base-component';
import Select, {Option} from 'rc-select'; import Select, {Option} from 'rc-select';

2
ui-ngx/src/app/shared/components/json-form/react/json-form-react.tsx

@ -1,5 +1,5 @@
/* /*
* Copyright © 2016-2019 The Thingsboard Authors * Copyright © 2016-2020 The Thingsboard Authors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

2
ui-ngx/src/app/shared/components/json-form/react/json-form-schema-form.tsx

@ -1,5 +1,5 @@
/* /*
* Copyright © 2016-2019 The Thingsboard Authors * Copyright © 2016-2020 The Thingsboard Authors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

15
ui-ngx/src/app/shared/components/json-form/react/json-form-select.tsx

@ -1,3 +1,18 @@
/*
* Copyright © 2016-2020 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 * as React from 'react'; import * as React from 'react';
import { import {
JsonFormFieldProps, JsonFormFieldProps,

14
ui-ngx/src/app/shared/components/json-form/react/json-form-text.tsx

@ -1,5 +1,5 @@
/* /*
* Copyright © 2016-2019 The Thingsboard Authors * Copyright © 2016-2020 The Thingsboard Authors
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -56,8 +56,14 @@ class ThingsboardText extends React.Component<JsonFormFieldProps, ThingsboardTex
} }
const multiline = this.props.form.type === 'textarea'; const multiline = this.props.form.type === 'textarea';
const rows = multiline ? this.props.form.rows : 1; let rows = 1;
const rowsMax = multiline ? this.props.form.rowsMax : 1; let rowsMax = 1;
let minHeight = 48;
if (multiline) {
rows = this.props.form.rows || 2;
rowsMax = this.props.form.rowsMax;
minHeight = 19 * rows + 48;
}
return ( return (
<div> <div>
@ -77,7 +83,7 @@ class ThingsboardText extends React.Component<JsonFormFieldProps, ThingsboardTex
rowsMax={rowsMax} rowsMax={rowsMax}
onFocus={this.onFocus} onFocus={this.onFocus}
onBlur={this.onBlur} onBlur={this.onBlur}
style={this.props.form.style || {width: '100%'}}/> style={this.props.form.style || {width: '100%', minHeight: minHeight + 'px'}}/>
</div> </div>
); );
} }

9
ui-ngx/src/app/shared/components/json-form/react/json-form.scss

@ -58,6 +58,15 @@ $previewSize: 100px !default;
.tb-field { .tb-field {
padding-bottom: 18px; padding-bottom: 18px;
.MuiInputBase-multiline {
flex: 1;
flex-direction: column;
.MuiInputBase-inputMultiline {
flex: 1;
}
}
&.tb-required { &.tb-required {
label::after { label::after {
font-size: 13px; font-size: 13px;

2
ui-ngx/src/app/shared/components/nav-tree.component.ts

@ -43,7 +43,7 @@ export interface NavTreeEditCallbacks {
nodeIsLoaded?: (id: string) => boolean; nodeIsLoaded?: (id: string) => boolean;
refreshNode?: (id: string) => void; refreshNode?: (id: string) => void;
updateNode?: (id: string, newName: string) => void; updateNode?: (id: string, newName: string) => void;
createNode?: (parentId: string, node: NavTreeNode, pos: number) => void; createNode?: (parentId: string, node: NavTreeNode, pos: number | string) => void;
deleteNode?: (id: string) => void; deleteNode?: (id: string) => void;
disableNode?: (id: string) => void; disableNode?: (id: string) => void;
enableNode?: (id: string) => void; enableNode?: (id: string) => void;

1
ui-ngx/src/app/shared/components/public-api.ts

@ -17,4 +17,5 @@
export * from './dialog/color-picker-dialog.component'; export * from './dialog/color-picker-dialog.component';
export * from './dialog/material-icons-dialog.component'; export * from './dialog/material-icons-dialog.component';
export * from './page.component'; export * from './page.component';
export * from './dialog.component';
export * from './js-func.component'; export * from './js-func.component';

4
ui-ngx/src/app/shared/components/time/datetime-period.component.html

@ -16,7 +16,7 @@
--> -->
<section fxLayout="column" fxLayoutAlign="start start"> <section fxLayout="column" fxLayoutAlign="start start">
<section fxLayout="row" fxLayoutAlign="start start" fxLayoutGap="16px"> <section fxLayout="row" fxLayout.xs="row wrap" fxLayoutAlign="start start" fxLayoutGap="16px">
<mat-form-field> <mat-form-field>
<mat-placeholder translate>datetime.date-from</mat-placeholder> <mat-placeholder translate>datetime.date-from</mat-placeholder>
<mat-datetimepicker-toggle [for]="startDatePicker" matPrefix></mat-datetimepicker-toggle> <mat-datetimepicker-toggle [for]="startDatePicker" matPrefix></mat-datetimepicker-toggle>
@ -30,7 +30,7 @@
<input matInput [disabled]="disabled" [(ngModel)]="startDate" [matDatetimepicker]="startTimePicker" (ngModelChange)="onStartDateChange()"> <input matInput [disabled]="disabled" [(ngModel)]="startDate" [matDatetimepicker]="startTimePicker" (ngModelChange)="onStartDateChange()">
</mat-form-field> </mat-form-field>
</section> </section>
<section fxLayout="row" fxLayoutAlign="start start" fxLayoutGap="16px"> <section fxLayout="row" fxLayout.xs="row wrap" fxLayoutAlign="start start" fxLayoutGap="16px">
<mat-form-field> <mat-form-field>
<mat-placeholder translate>datetime.date-to</mat-placeholder> <mat-placeholder translate>datetime.date-to</mat-placeholder>
<mat-datetimepicker-toggle [for]="endDatePicker" matPrefix></mat-datetimepicker-toggle> <mat-datetimepicker-toggle [for]="endDatePicker" matPrefix></mat-datetimepicker-toggle>

6
ui-ngx/src/app/shared/components/time/datetime-period.component.scss

@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
@import '../../../../scss/constants';
:host ::ng-deep { :host ::ng-deep {
.mat-form-field-wrapper { .mat-form-field-wrapper {
padding-bottom: 8px; padding-bottom: 8px;
@ -22,5 +24,9 @@
} }
.mat-form-field-infix { .mat-form-field-infix {
width: 150px; width: 150px;
@media #{$mat-xs} {
width: 100%;
}
} }
} }

3
ui-ngx/src/app/shared/components/time/datetime.component.scss

@ -21,6 +21,7 @@
bottom: 8px; bottom: 8px;
} }
.mat-form-field-infix { .mat-form-field-infix {
width: 150px; width: auto;
min-width: 100px;
} }
} }

6
ui-ngx/src/app/shared/components/time/datetime.component.ts

@ -14,9 +14,9 @@
/// limitations under the License. /// limitations under the License.
/// ///
import {Component, forwardRef, Input, OnInit} from '@angular/core'; import { Component, forwardRef, Input, OnInit } from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import {coerceBooleanProperty} from '@angular/cdk/coercion'; import { coerceBooleanProperty } from '@angular/cdk/coercion';
@Component({ @Component({
selector: 'tb-datetime', selector: 'tb-datetime',

4
ui-ngx/src/app/shared/components/time/timeinterval.component.html

@ -22,7 +22,7 @@
</section> </section>
<section class="interval-section" fxLayout="column" fxFlex [fxShow]="advanced && (isEdit || !hideFlag)"> <section class="interval-section" fxLayout="column" fxFlex [fxShow]="advanced && (isEdit || !hideFlag)">
<label class="tb-small interval-label" translate>{{ predefinedName }}</label> <label class="tb-small interval-label" translate>{{ predefinedName }}</label>
<section fxLayout="row" fxLayoutAlign="start start" fxFlex fxLayoutGap="6px"> <section fxLayout="row wrap" fxLayoutAlign="start start" fxFlex fxLayoutGap="6px">
<mat-form-field class="number-input"> <mat-form-field class="number-input">
<mat-label translate>timeinterval.days</mat-label> <mat-label translate>timeinterval.days</mat-label>
<input matInput [disabled]="hideFlag || disabled" type="number" step="1" min="0" [(ngModel)]="days" (ngModelChange)="onTimeInputChange('days')"/> <input matInput [disabled]="hideFlag || disabled" type="number" step="1" min="0" [(ngModel)]="days" (ngModelChange)="onTimeInputChange('days')"/>
@ -44,7 +44,7 @@
<section class="interval-section" fxLayout="row" fxFlex [fxShow]="!advanced && (isEdit || !hideFlag)"> <section class="interval-section" fxLayout="row" fxFlex [fxShow]="!advanced && (isEdit || !hideFlag)">
<mat-form-field fxFlex> <mat-form-field fxFlex>
<mat-label translate>{{ predefinedName }}</mat-label> <mat-label translate>{{ predefinedName }}</mat-label>
<mat-select matInput [disabled]="hideFlag || disabled" [(ngModel)]="intervalMs" (ngModelChange)="onIntervalMsChange()" style="min-width: 150px;"> <mat-select [disabled]="hideFlag || disabled" [(ngModel)]="intervalMs" (ngModelChange)="onIntervalMsChange()" style="min-width: 150px;">
<mat-option *ngFor="let interval of intervals" [value]="interval.value"> <mat-option *ngFor="let interval of intervals" [value]="interval.value">
{{ interval.name | translate:interval.translateParams }} {{ interval.name | translate:interval.translateParams }}
</mat-option> </mat-option>

6
ui-ngx/src/app/shared/components/time/timeinterval.component.scss

@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
@import '../../../../scss/constants';
:host { :host {
min-width: 355px; min-width: 355px;
@ -37,6 +39,10 @@
} }
} }
@media #{$mat-xs} {
min-width: 0;
width: 100%;
}
} }
:host ::ng-deep { :host ::ng-deep {

11
ui-ngx/src/app/shared/components/time/timeinterval.component.ts

@ -14,15 +14,8 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { ChangeDetectorRef, Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core';
import { import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
ControlValueAccessor,
FormControl,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
Validator
} from '@angular/forms';
import { Timewindow } from '@shared/models/time/time.models';
import { TimeInterval, TimeService } from '@core/services/time.service'; import { TimeInterval, TimeService } from '@core/services/time.service';
import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { coerceBooleanProperty } from '@angular/cdk/coercion';

8
ui-ngx/src/app/shared/components/time/timewindow-panel.component.html

@ -39,11 +39,12 @@
<mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideInterval" <mat-checkbox [ngModelOptions]="{standalone: true}" [(ngModel)]="timewindow.hideInterval"
(ngModelChange)="onHideIntervalChanged()"></mat-checkbox> (ngModelChange)="onHideIntervalChanged()"></mat-checkbox>
</section> </section>
<section fxLayout="column" [fxShow]="isEdit || !timewindow.hideInterval"> <section fxLayout="column" fxFlex [fxShow]="isEdit || !timewindow.hideInterval">
<div formGroupName="history" class="mat-content mat-padding" style="padding-top: 8px;"> <div formGroupName="history" class="mat-content mat-padding" style="padding-top: 8px;">
<mat-radio-group formControlName="historyType"> <mat-radio-group formControlName="historyType">
<mat-radio-button [value]="historyTypes.LAST_INTERVAL" color="primary"> <mat-radio-button [value]="historyTypes.LAST_INTERVAL" color="primary">
<section fxLayout="column"> <section fxLayout="column">
<span translate>timewindow.last</span>
<tb-timeinterval <tb-timeinterval
formControlName="timewindowMs" formControlName="timewindowMs"
predefinedName="timewindow.last" predefinedName="timewindow.last"
@ -80,7 +81,7 @@
<section fxFlex fxLayout="column" [fxShow]="isEdit || !timewindow.hideAggregation"> <section fxFlex fxLayout="column" [fxShow]="isEdit || !timewindow.hideAggregation">
<mat-form-field> <mat-form-field>
<mat-label translate>aggregation.function</mat-label> <mat-label translate>aggregation.function</mat-label>
<mat-select matInput formControlName="type" style="min-width: 150px;"> <mat-select formControlName="type" style="min-width: 150px;">
<mat-option *ngFor="let aggregation of aggregations" [value]="aggregation"> <mat-option *ngFor="let aggregation of aggregations" [value]="aggregation">
{{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }} {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }}
</mat-option> </mat-option>
@ -138,8 +139,7 @@
predefinedName="aggregation.group-interval"> predefinedName="aggregation.group-interval">
</tb-timeinterval> </tb-timeinterval>
</div> </div>
<div fxLayout="row" class="tb-panel-actions"> <div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center">
<span fxFlex></span>
<button type="submit" <button type="submit"
mat-raised-button mat-raised-button
color="primary" color="primary"

4
ui-ngx/src/app/shared/components/time/timewindow-panel.component.scss

@ -35,6 +35,10 @@
margin-right: 5px; margin-right: 5px;
} }
tb-timeinterval[ng-reflect-fx-show="true"] {
margin-bottom: -16px;
}
.limit-slider-container { .limit-slider-container {
>:first-child { >:first-child {
margin-right: 16px; margin-right: 16px;

20
ui-ngx/src/app/shared/components/time/timewindow-panel.component.ts

@ -14,28 +14,16 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component, Inject, InjectionToken, OnInit, ViewContainerRef } from '@angular/core';
import { import {
Component,
Inject,
InjectionToken,
OnInit,
ViewChild,
ViewContainerRef
} from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe';
import {
Aggregation,
aggregationTranslations, aggregationTranslations,
AggregationType, DAY, AggregationType,
HistoryWindow, DAY,
HistoryWindowType, HistoryWindowType,
IntervalWindow,
Timewindow, Timewindow,
TimewindowType TimewindowType
} from '@shared/models/time/time.models'; } from '@shared/models/time/time.models';
import { DatePipe } from '@angular/common'; import { OverlayRef } from '@angular/cdk/overlay';
import { Overlay, OverlayRef } from '@angular/cdk/overlay';
import { PageComponent } from '@shared/components/page.component'; import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';

4
ui-ngx/src/app/shared/components/time/timewindow.component.html

@ -23,7 +23,7 @@
</button> </button>
<section *ngIf="!asButton" cdkOverlayOrigin #timewindowPanelOrigin="cdkOverlayOrigin" <section *ngIf="!asButton" cdkOverlayOrigin #timewindowPanelOrigin="cdkOverlayOrigin"
class="tb-timewindow" fxLayout="row" fxLayoutAlign="start center"> class="tb-timewindow" fxLayout="row" fxLayoutAlign="start center">
<button *ngIf="direction === 'left'" [disabled]="timewindowDisabled" mat-button mat-icon-button class="tb-mat-32" <button *ngIf="direction === 'left'" [disabled]="timewindowDisabled" mat-icon-button class="tb-mat-32"
type="button" type="button"
(click)="openEditMode()" (click)="openEditMode()"
matTooltip="{{ 'timewindow.edit' | translate }}" matTooltip="{{ 'timewindow.edit' | translate }}"
@ -36,7 +36,7 @@
[matTooltipPosition]="tooltipPosition"> [matTooltipPosition]="tooltipPosition">
{{innerValue?.displayValue}} {{innerValue?.displayValue}}
</span> </span>
<button *ngIf="direction === 'right'" [disabled]="timewindowDisabled" mat-button mat-icon-button class="tb-mat-32" <button *ngIf="direction === 'right'" [disabled]="timewindowDisabled" mat-icon-button class="tb-mat-32"
type="button" type="button"
(click)="openEditMode()" (click)="openEditMode()"
matTooltip="{{ 'timewindow.edit' | translate }}" matTooltip="{{ 'timewindow.edit' | translate }}"

1
ui-ngx/src/app/shared/components/time/timewindow.component.scss

@ -15,6 +15,7 @@
*/ */
:host { :host {
min-width: 52px; min-width: 52px;
margin: 8px 0;
section.tb-timewindow { section.tb-timewindow {
min-height: 32px; min-height: 32px;
padding: 0 6px; padding: 0 6px;

32
ui-ngx/src/app/shared/components/time/timewindow.component.ts

@ -14,32 +14,19 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { import { Component, forwardRef, Inject, Input, OnDestroy, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
ChangeDetectionStrategy,
Component,
forwardRef, Inject,
Input,
OnDestroy,
OnInit,
ViewChild,
ViewContainerRef
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe'; import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe';
import { import {
cloneSelectedTimewindow,
HistoryWindowType, HistoryWindowType,
initModelFromDefaultTimewindow,
Timewindow, Timewindow,
TimewindowType, TimewindowType
initModelFromDefaultTimewindow, cloneSelectedTimewindow
} from '@shared/models/time/time.models'; } from '@shared/models/time/time.models';
import { DatePipe } from '@angular/common'; import { DatePipe, DOCUMENT } from '@angular/common';
import { import { CdkOverlayOrigin, ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
Overlay,
CdkOverlayOrigin,
OverlayConfig,
OverlayPositionBuilder, ConnectedPosition, PositionStrategy, OverlayRef
} from '@angular/cdk/overlay';
import { import {
TIMEWINDOW_PANEL_DATA, TIMEWINDOW_PANEL_DATA,
TimewindowPanelComponent, TimewindowPanelComponent,
@ -48,7 +35,6 @@ import {
import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; import { ComponentPortal, PortalInjector } from '@angular/cdk/portal';
import { MediaBreakpoints } from '@shared/models/constants'; import { MediaBreakpoints } from '@shared/models/constants';
import { BreakpointObserver } from '@angular/cdk/layout'; import { BreakpointObserver } from '@angular/cdk/layout';
import { DOCUMENT } from '@angular/common';
import { WINDOW } from '@core/services/window.service'; import { WINDOW } from '@core/services/window.service';
import { TimeService } from '@core/services/time.service'; import { TimeService } from '@core/services/time.service';
import { TooltipPosition } from '@angular/material/tooltip'; import { TooltipPosition } from '@angular/material/tooltip';
@ -163,14 +149,14 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces
if (this.timewindowDisabled) { if (this.timewindowDisabled) {
return; return;
} }
const isGtSm = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']); const isGtXs = this.breakpointObserver.isMatched(MediaBreakpoints['gt-xs']);
const position = this.overlay.position(); const position = this.overlay.position();
const config = new OverlayConfig({ const config = new OverlayConfig({
panelClass: 'tb-timewindow-panel', panelClass: 'tb-timewindow-panel',
backdropClass: 'cdk-overlay-transparent-backdrop', backdropClass: 'cdk-overlay-transparent-backdrop',
hasBackdrop: isGtSm, hasBackdrop: isGtXs,
}); });
if (isGtSm) { if (isGtXs) {
config.minWidth = '417px'; config.minWidth = '417px';
config.maxHeight = '440px'; config.maxHeight = '440px';
const panelHeight = 375; const panelHeight = 375;

46
ui-ngx/src/app/shared/models/page/page-link.ts

@ -21,25 +21,43 @@ import { SortDirection } from '@angular/material/sort';
export const MAX_SAFE_PAGE_SIZE = 2147483647; export const MAX_SAFE_PAGE_SIZE = 2147483647;
export type PageLinkSearchFunction<T> = (entity: T, textSearch: string) => boolean; export type PageLinkSearchFunction<T> = (entity: T, textSearch: string, searchProperty?: string) => boolean;
const defaultPageLinkSearchFunction: PageLinkSearchFunction<any> = export function defaultPageLinkSearchFunction(searchProperty?: string): PageLinkSearchFunction<any> {
(entity: any, textSearch: string) => { return (entity, textSearch) => defaultPageLinkSearch(entity, textSearch, searchProperty);
}
const defaultPageLinkSearch: PageLinkSearchFunction<any> =
(entity: any, textSearch: string, searchProperty?: string) => {
if (textSearch === null || !textSearch.length) { if (textSearch === null || !textSearch.length) {
return true; return true;
} }
const expected = ('' + textSearch).toLowerCase(); const expected = ('' + textSearch).toLowerCase();
for (const key of Object.keys(entity)) { if (searchProperty && searchProperty.length) {
const val = entity[key]; if (Object.prototype.hasOwnProperty.call(entity, searchProperty)) {
if (val !== null) { const val = entity[searchProperty];
if (val !== Object(val)) { if (val !== null) {
const actual = ('' + val).toLowerCase(); if (val !== Object(val)) {
if (actual.indexOf(expected) !== -1) { const actual = ('' + val).toLowerCase();
return true; if (actual.indexOf(expected) !== -1) {
return true;
}
} }
} else if (isObject(val)) { }
if (defaultPageLinkSearchFunction(val, textSearch)) { }
return true; } else {
for (const key of Object.keys(entity)) {
const val = entity[key];
if (val !== null) {
if (val !== Object(val)) {
const actual = ('' + val).toLowerCase();
if (actual.indexOf(expected) !== -1) {
return true;
}
} else if (isObject(val)) {
if (defaultPageLinkSearch(val, textSearch)) {
return true;
}
} }
} }
} }
@ -103,7 +121,7 @@ export class PageLink {
} }
public filterData<T>(data: Array<T>, public filterData<T>(data: Array<T>,
searchFunction: PageLinkSearchFunction<T> = defaultPageLinkSearchFunction): PageData<T> { searchFunction: PageLinkSearchFunction<T> = defaultPageLinkSearchFunction()): PageData<T> {
const pageData = emptyPageData<T>(); const pageData = emptyPageData<T>();
pageData.data = [...data]; pageData.data = [...data];
if (this.textSearch && this.textSearch.length) { if (this.textSearch && this.textSearch.length) {

4
ui-ngx/src/app/shared/models/rule-node.models.ts

@ -126,7 +126,7 @@ export abstract class RuleNodeConfigurationComponent extends PageComponent imple
control = control.get(part); control = control.get(part);
} }
control.valueChanges.subscribe(() => { control.valueChanges.subscribe(() => {
this.updateValidators(true); this.updateValidators(true, trigger);
}); });
} }
this.configForm().valueChanges.subscribe((updated: RuleNodeConfiguration) => { this.configForm().valueChanges.subscribe((updated: RuleNodeConfiguration) => {
@ -139,7 +139,7 @@ export abstract class RuleNodeConfigurationComponent extends PageComponent imple
this.updateValidators(false); this.updateValidators(false);
} }
protected updateValidators(emitEvent: boolean) { protected updateValidators(emitEvent: boolean, trigger?: string) {
} }
protected validatorTriggers(): string[] { protected validatorTriggers(): string[] {

0
ui-ngx/src/assets/.gitkeep

3
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -380,7 +380,8 @@
"enter-username": "Enter username", "enter-username": "Enter username",
"enter-password": "Enter password", "enter-password": "Enter password",
"enter-search": "Enter search", "enter-search": "Enter search",
"created-time": "Created time" "created-time": "Created time",
"loading": "Loading..."
}, },
"content-type": { "content-type": {
"json": "Json", "json": "Json",

35
ui/src/app/widget/lib/web-camera-input-widget.js

@ -52,6 +52,11 @@ function WebCameraWidgetController($element, $scope, $window, types, utils, attr
let canvas = null; let canvas = null;
let photoCamera = null; let photoCamera = null;
let dataKeyType = ""; let dataKeyType = "";
let width = 640;
let height = 480;
const DEFAULT_IMAGE_TYPE = 'image/jpeg';
const DEFAULT_IMAGE_QUALITY = 0.92;
vm.getStream = getStream; vm.getStream = getStream;
vm.createPhoto = createPhoto; vm.createPhoto = createPhoto;
@ -79,6 +84,8 @@ function WebCameraWidgetController($element, $scope, $window, types, utils, attr
vm.isEntityDetected = true; vm.isEntityDetected = true;
} }
} }
width = vm.ctx.settings.maxWidth ? vm.ctx.settings.maxWidth : 640;
height = vm.ctx.settings.maxHeight ? vm.ctx.settings.maxWidth : 480;
if (datasource.dataKeys.length) { if (datasource.dataKeys.length) {
$scope.currentKey = datasource.dataKeys[0].name; $scope.currentKey = datasource.dataKeys[0].name;
dataKeyType = datasource.dataKeys[0].type; dataKeyType = datasource.dataKeys[0].type;
@ -93,6 +100,24 @@ function WebCameraWidgetController($element, $scope, $window, types, utils, attr
} }
}); });
function getVideoAspectRatio() {
if (videoElement.videoWidth && videoElement.videoWidth > 0 &&
videoElement.videoHeight && videoElement.videoHeight > 0) {
return videoElement.videoWidth / videoElement.videoHeight;
}
return width / height;
}
vm.videoWidth = function() {
const videoRatio = getVideoAspectRatio();
return Math.min(width, height * videoRatio);
}
vm.videoHeight = function() {
const videoRatio = getVideoAspectRatio();
return Math.min(height, width / videoRatio);
}
function hasGetUserMedia() { function hasGetUserMedia() {
return !!($window.navigator.mediaDevices && $window.navigator.mediaDevices.getUserMedia); return !!($window.navigator.mediaDevices && $window.navigator.mediaDevices.getUserMedia);
} }
@ -157,10 +182,12 @@ function WebCameraWidgetController($element, $scope, $window, types, utils, attr
} }
function createPhoto() { function createPhoto() {
canvas.width = videoElement.videoWidth; canvas.width = vm.videoWidth();
canvas.height = videoElement.videoHeight; canvas.height = vm.videoHeight();
canvas.getContext('2d').drawImage(videoElement, 0, 0); canvas.getContext('2d').drawImage(videoElement, 0, 0, vm.videoWidth(), vm.videoHeight());
vm.previewPhoto = canvas.toDataURL('image/png'); const mimeType = vm.ctx.settings.imageFormat ? vm.ctx.settings.imageFormat : DEFAULT_IMAGE_TYPE;
const quality = vm.ctx.settings.imageQuality ? vm.ctx.settings.imageQuality : DEFAULT_IMAGE_QUALITY;
vm.previewPhoto = canvas.toDataURL(mimeType, quality);
vm.isPreviewPhoto = true; vm.isPreviewPhoto = true;
} }

Loading…
Cancel
Save