Browse Source

UI: Rule chain selector

pull/8638/head
Artem Dzhereleiko 3 years ago
parent
commit
3443319c61
  1. 2
      ui-ngx/src/app/modules/common/modules-map.ts
  2. 61
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.html
  3. 47
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss
  4. 8
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts
  5. 26
      ui-ngx/src/app/shared/components/rule-chain/rule-chain-select.component.html
  6. 38
      ui-ngx/src/app/shared/components/rule-chain/rule-chain-select.component.scss
  7. 117
      ui-ngx/src/app/shared/components/rule-chain/rule-chain-select.component.ts
  8. 7
      ui-ngx/src/app/shared/shared.module.ts
  9. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json

2
ui-ngx/src/app/modules/common/modules-map.ts

@ -300,6 +300,7 @@ import * as QueueFormComponent from '@home/components/queue/queue-form.component
import * as AssetProfileComponent from '@home/components/profile/asset-profile.component';
import * as AssetProfileDialogComponent from '@home/components/profile/asset-profile-dialog.component';
import * as AssetProfileAutocompleteComponent from '@home/components/profile/asset-profile-autocomplete.component';
import * as RuleChainSelectComponent from '@shared/components/rule-chain/rule-chain-select.component';
import { IModulesMap } from '@modules/common/modules-map.models';
@ -418,6 +419,7 @@ class ModulesMap implements IModulesMap {
'@shared/components/time/quick-time-interval.component': QuickTimeIntervalComponent,
'@shared/components/dashboard-select.component': DashboardSelectComponent,
'@shared/components/dashboard-select-panel.component': DashboardSelectPanelComponent,
'@shared/components/rule-chain/rule-chain-select.component': RuleChainSelectComponent,
'@shared/components/time/datetime-period.component': DatetimePeriodComponent,
'@shared/components/time/datetime.component': DatetimeComponent,
'@shared/components/time/timezone-select.component': TimezoneSelectComponent,

61
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.html

@ -21,49 +21,42 @@
<tb-hotkeys-cheatsheet #cheatSheetComponent></tb-hotkeys-cheatsheet>
<section class="tb-rulechain-container" fxFlex fxLayout="column">
<div class="tb-rulechain-layout" fxFlex fxLayout="row">
<section fxLayout="row"
class="tb-header-buttons tb-library-open" [fxShow]="!isLibraryOpen">
<button color="primary"
mat-mini-fab
class="tb-btn-header tb-btn-open-library"
(click)="isLibraryOpen = true"
matTooltip="{{ 'rulenode.open-node-library' | translate }}"
matTooltipPosition="above">
<mat-icon>menu</mat-icon>
</button>
</section>
<mat-drawer-container style="width: 100%; height: 100%;">
<mat-drawer class="tb-rulechain-library mat-elevation-z4"
disableClose="true"
mode="side"
[opened]="isLibraryOpen"
#drawer
[opened]="true"
position="start"
fxLayout="column">
<mat-toolbar color="primary" class="tb-dark">
<tb-rule-chain-select
fxFlex
*ngIf="!isImport"
[disabled]="isDirtyValue"
[(ngModel)]="ruleChain.id.id"
(ngModelChange)="currentRuleChainIdChanged(ruleChain.id?.id)">
</tb-rule-chain-select>
</mat-toolbar>
<mat-toolbar>
<div class="mat-toolbar-tools">
<button mat-icon-button class="tb-small"
matTooltip="{{'rulenode.search' | translate}}"
matTooltipPosition="above">
<mat-icon>search</mat-icon>
</button>
<mat-form-field fxFlex class="tb-appearance-transparent">
<button mat-icon-button matPrefix class="tb-small"
matTooltip="{{'rulenode.search' | translate}}"
matTooltipPosition="above">
<mat-icon>search</mat-icon>
</button>
<input #ruleNodeSearchInput matInput
[(ngModel)]="ruleNodeTypeSearch"
placeholder="{{'rulenode.search' | translate}}"/>
<button mat-icon-button matSuffix class="tb-small"
[fxShow]="ruleNodeTypeSearch !== ''"
(click)="ruleNodeTypeSearch = ''; updateRuleChainLibrary()"
matTooltip="{{'action.clear-search' | translate}}"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
</button>
</mat-form-field>
<button mat-icon-button class="tb-small"
[fxShow]="ruleNodeTypeSearch !== ''"
(click)="ruleNodeTypeSearch = ''; updateRuleChainLibrary()"
matTooltip="{{'action.clear-search' | translate}}"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
</button>
<button mat-icon-button class="tb-small"
(click)="isLibraryOpen = false"
matTooltip="{{'action.close' | translate}}"
matTooltipPosition="above">
<mat-icon>chevron_left</mat-icon>
</button>
</div>
</mat-toolbar>
<div class="tb-rulechain-library-panel-group">
@ -164,6 +157,14 @@
</tb-details-panel>
</mat-drawer>
<mat-drawer-content class="tb-rulechain-graph-content">
<button color="primary"
mat-mini-fab
class="tb-library-node-btn"
(click)="drawer.toggle();"
matTooltip="{{ (drawer.opened ? 'rulenode.close-node-library' : 'rulenode.open-node-library') | translate }}"
matTooltipPosition="above">
<mat-icon style="transition: transform 0.4s" [ngClass]="{'tb-library-node-btn-toggled' : !drawer.opened}">chevron_right</mat-icon>
</button>
<button #versionControlButton
*ngIf="!isImport"
color="primary"

47
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.scss

@ -54,20 +54,40 @@
z-index: 2;
}
section.tb-header-buttons.tb-library-open {
.tb-library-node-btn {
width: 20px;
height: 90px;
position: absolute;
top: 0;
left: 0;
z-index: 2;
pointer-events: none;
.mdc-fab.tb-btn-open-library {
top: 0;
top: 50%;
z-index: 1000;
opacity: 0.5;
border-radius: 0 15px 15px 0 !important;
transform: translateY(-50%);
&-toggled {
transform: rotateZ(180deg);
}
&:before {
width: 20px;
height: 30px;
top: -28px;
left: 0;
content: "";
pointer-events: none;
border-radius: 0 0 0 15px;
box-shadow: -10px 0px 0 0px #305680;
border: none;
}
&:after {
position: absolute;
width: 20px;
height: 30px;
bottom: -28px;
left: 0;
width: 36px;
height: 36px;
margin: 4px 0 0 4px;
line-height: 36px;
opacity: .5;
content: "";
pointer-events: none;
border-top-left-radius: 15px;
box-shadow: -10px 0px 0px 0px #305680;
border: none;
}
}
@ -77,8 +97,7 @@
min-width: 250px;
.mat-toolbar {
height: 48px;
min-height: 48px;
height: min-content;
padding: 0;
.mat-toolbar-tools {

8
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts

@ -159,7 +159,6 @@ export class RuleChainPageComponent extends PageComponent
hotKeys: Hotkey[] = [];
enableHotKeys = true;
isLibraryOpen = true;
ruleNodeSearch = '';
ruleNodeTypeSearch = '';
@ -299,6 +298,13 @@ export class RuleChainPageComponent extends PageComponent
this.rxSubscription.unsubscribe();
}
currentRuleChainIdChanged(ruleChainId: string) {
if (this.isEditingRuleNode) {
return;
}
this.router.navigateByUrl(`ruleChains/${ruleChainId}`);
}
onSearchTextUpdated(searchText: string) {
this.ruleNodeSearch = searchText;
this.updateRuleNodesHighlight();

26
ui-ngx/src/app/shared/components/rule-chain/rule-chain-select.component.html

@ -0,0 +1,26 @@
<!--
Copyright © 2016-2023 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.
-->
<mat-select fxFlex
[required]="required"
[disabled]="disabled"
[(ngModel)]="ruleChainId"
(ngModelChange)="ruleChainIdChanged()">
<mat-option *ngFor="let ruleChain of ruleChains$ | async" [value]="ruleChain.id.id">
{{ruleChain.name}}
</mat-option>
</mat-select>

38
ui-ngx/src/app/shared/components/rule-chain/rule-chain-select.component.scss

@ -0,0 +1,38 @@
/**
* Copyright © 2016-2023 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 {
min-width: 52px;
width: 100%;
padding: 0 6px;
.mat-mdc-select {
display: flex;
max-width: 300px;
height: 48px;
min-height: 100%;
pointer-events: all;
}
}
:host ::ng-deep {
.mat-mdc-select {
.mat-mdc-select-value {
max-width: 282px;
}
.mat-mdc-select-arrow {
width: 24px;
}
}
}

117
ui-ngx/src/app/shared/components/rule-chain/rule-chain-select.component.ts

@ -0,0 +1,117 @@
///
/// Copyright © 2016-2023 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, Inject, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Observable } from 'rxjs';
import { PageLink } from '@shared/models/page/page-link';
import { map, share } from 'rxjs/operators';
import { PageData } from '@shared/models/page/page-data';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { TooltipPosition } from '@angular/material/tooltip';
import { CdkOverlayOrigin, Overlay } from '@angular/cdk/overlay';
import { BreakpointObserver } from '@angular/cdk/layout';
import { DOCUMENT } from '@angular/common';
import { WINDOW } from '@core/services/window.service';
import { RuleChain, RuleChainType } from '@shared/models/rule-chain.models';
import { RuleChainService } from '@core/http/rule-chain.service';
// @dynamic
@Component({
selector: 'tb-rule-chain-select',
templateUrl: './rule-chain-select.component.html',
styleUrls: ['./rule-chain-select.component.scss'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => RuleChainSelectComponent),
multi: true
}]
})
export class RuleChainSelectComponent implements ControlValueAccessor, OnInit {
@Input()
tooltipPosition: TooltipPosition = 'above';
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
}
@Input()
disabled: boolean;
ruleChains$: Observable<Array<RuleChain>>;
ruleChainId: string | null;
@ViewChild('ruleChainSelectPanelOrigin') ruleChainSelectPanelOrigin: CdkOverlayOrigin;
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
private ruleChainService: RuleChainService,
private overlay: Overlay,
private breakpointObserver: BreakpointObserver,
private viewContainerRef: ViewContainerRef,
private nativeElement: ElementRef,
@Inject(DOCUMENT) private document: Document,
@Inject(WINDOW) private window: Window) {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
ngOnInit() {
const pageLink = new PageLink(100);
this.ruleChains$ = this.getRuleChains(pageLink).pipe(
map((pageData) => pageData.data),
share()
);
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
writeValue(value: string | null): void {
this.ruleChainId = value;
}
ruleChainIdChanged() {
this.updateView();
}
private updateView() {
this.propagateChange(this.ruleChainId);
}
private getRuleChains(pageLink: PageLink): Observable<PageData<RuleChain>> {
return this.ruleChainService.getRuleChains(pageLink, RuleChainType.CORE, {ignoreLoading: true});
}
}

7
ui-ngx/src/app/shared/shared.module.ts

@ -189,6 +189,7 @@ import {
} from '@shared/layout/layout.directives';
import { ColorPickerComponent } from '@shared/components/color-picker/color-picker.component';
import { ShortNumberPipe } from '@shared/pipe/short-number.pipe';
import { RuleChainSelectComponent } from '@shared/components/rule-chain/rule-chain-select.component';
export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) {
return markedOptionsService;
@ -356,7 +357,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
GtMdLgLayoutAlignDirective,
GtMdLgLayoutGapDirective,
GtMdLgShowHideDirective,
ColorPickerComponent
ColorPickerComponent,
RuleChainSelectComponent
],
imports: [
CommonModule,
@ -580,7 +582,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
GtMdLgLayoutAlignDirective,
GtMdLgLayoutGapDirective,
GtMdLgShowHideDirective,
ColorPickerComponent
ColorPickerComponent,
RuleChainSelectComponent
]
})
export class SharedModule { }

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

@ -3325,6 +3325,7 @@
"events": "Events",
"search": "Search nodes",
"open-node-library": "Open node library",
"close-node-library": "Close node library",
"add": "Add rule node",
"name": "Name",
"name-required": "Name is required.",

Loading…
Cancel
Save