26 changed files with 1077 additions and 195 deletions
@ -0,0 +1,69 @@ |
|||
<!-- |
|||
|
|||
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 #stateForm="ngForm" [formGroup]="stateFormGroup" (ngSubmit)="save()" style="min-width: 600px;"> |
|||
<mat-toolbar fxLayout="row" color="primary"> |
|||
<h2 translate>{{ isAdd ? 'dashboard.add-state' : 'dashboard.edit-state' }}</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"> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label translate>dashboard.state-name</mat-label> |
|||
<input required matInput formControlName="name"> |
|||
<mat-error *ngIf="stateFormGroup.get('name').hasError('required')"> |
|||
{{ 'dashboard.state-name-required' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<mat-form-field class="mat-block"> |
|||
<mat-label translate>dashboard.state-id</mat-label> |
|||
<input required matInput formControlName="id"> |
|||
<mat-error *ngIf="stateFormGroup.get('id').hasError('required')"> |
|||
{{ 'dashboard.state-id-required' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="stateFormGroup.get('id').hasError('stateExists')"> |
|||
{{ 'dashboard.state-id-exists' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<mat-checkbox formControlName="root"> |
|||
{{ 'dashboard.is-root-state' | translate }} |
|||
</mat-checkbox> |
|||
</fieldset> |
|||
</div> |
|||
<div mat-dialog-actions fxLayout="row"> |
|||
<span fxFlex></span> |
|||
<button mat-button mat-raised-button color="primary" |
|||
type="submit" |
|||
[disabled]="(isLoading$ | async) || stateFormGroup.invalid || !stateFormGroup.dirty"> |
|||
{{ (isAdd ? 'action.add' : '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,145 @@ |
|||
///
|
|||
/// 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, |
|||
ValidatorFn, |
|||
Validators |
|||
} from '@angular/forms'; |
|||
import { Router } from '@angular/router'; |
|||
import { DialogComponent } from '@app/shared/components/dialog.component'; |
|||
import { DashboardState } from '@app/shared/models/dashboard.models'; |
|||
import { MatDialog } from '@angular/material/dialog'; |
|||
import { DashboardStateInfo } from '@home/pages/dashboard/states/manage-dashboard-states-dialog.component.models'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; |
|||
|
|||
export interface DashboardStateDialogData { |
|||
states: {[id: string]: DashboardState }; |
|||
state: DashboardStateInfo; |
|||
isAdd: boolean; |
|||
} |
|||
|
|||
@Component({ |
|||
selector: 'tb-dashboard-state-dialog', |
|||
templateUrl: './dashboard-state-dialog.component.html', |
|||
providers: [{provide: ErrorStateMatcher, useExisting: DashboardStateDialogComponent}], |
|||
styleUrls: [] |
|||
}) |
|||
export class DashboardStateDialogComponent extends |
|||
DialogComponent<DashboardStateDialogComponent, DashboardStateInfo> |
|||
implements OnInit, ErrorStateMatcher { |
|||
|
|||
stateFormGroup: FormGroup; |
|||
|
|||
states: {[id: string]: DashboardState }; |
|||
state: DashboardStateInfo; |
|||
prevStateId: string; |
|||
|
|||
stateIdTouched: boolean; |
|||
|
|||
isAdd: boolean; |
|||
|
|||
submitted = false; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
protected router: Router, |
|||
@Inject(MAT_DIALOG_DATA) public data: DashboardStateDialogData, |
|||
@SkipSelf() private errorStateMatcher: ErrorStateMatcher, |
|||
public dialogRef: MatDialogRef<DashboardStateDialogComponent, DashboardStateInfo>, |
|||
private fb: FormBuilder, |
|||
private translate: TranslateService, |
|||
private dashboardUtils: DashboardUtilsService, |
|||
private dialog: MatDialog) { |
|||
super(store, router, dialogRef); |
|||
|
|||
this.states = this.data.states; |
|||
this.isAdd = this.data.isAdd; |
|||
if (this.isAdd) { |
|||
this.state = {id: '', ...this.dashboardUtils.createDefaultState('', false)}; |
|||
this.prevStateId = ''; |
|||
} else { |
|||
this.state = this.data.state; |
|||
this.prevStateId = this.state.id; |
|||
} |
|||
|
|||
this.stateFormGroup = this.fb.group({ |
|||
name: [this.state.name, [Validators.required]], |
|||
id: [this.state.id, [Validators.required, this.validateDuplicateStateId()]], |
|||
root: [this.state.root, []], |
|||
}); |
|||
|
|||
this.stateFormGroup.get('name').valueChanges.subscribe((name: string) => { |
|||
this.checkStateName(name); |
|||
}); |
|||
|
|||
this.stateFormGroup.get('id').valueChanges.subscribe((id: string) => { |
|||
this.stateIdTouched = true; |
|||
}); |
|||
} |
|||
|
|||
private checkStateName(name: string) { |
|||
if (name && !this.stateIdTouched && this.isAdd) { |
|||
this.stateFormGroup.get('id').setValue( |
|||
name.toLowerCase().replace(/\W/g, '_'), |
|||
{ emitEvent: false } |
|||
); |
|||
} |
|||
} |
|||
|
|||
private validateDuplicateStateId(): ValidatorFn { |
|||
return (c: FormControl) => { |
|||
const newStateId: string = c.value; |
|||
if (newStateId) { |
|||
const existing = this.states[newStateId]; |
|||
if (existing && newStateId !== this.prevStateId) { |
|||
return { |
|||
stateExists: true |
|||
}; |
|||
} |
|||
} |
|||
return null; |
|||
}; |
|||
} |
|||
|
|||
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; |
|||
this.state = {...this.state, ...this.stateFormGroup.value}; |
|||
this.state.id = this.state.id.trim(); |
|||
this.dialogRef.close(this.state); |
|||
} |
|||
} |
|||
@ -0,0 +1,153 @@ |
|||
<!-- |
|||
|
|||
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 #statesForm="ngForm" [formGroup]="statesFormGroup" (ngSubmit)="save()" style="min-width: 600px;"> |
|||
<mat-toolbar fxLayout="row" color="primary"> |
|||
<h2 translate>dashboard.manage-states</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"> |
|||
<div class="manage-dashboard-states" fxLayout="column"> |
|||
<div class="tb-entity-table"> |
|||
<div fxLayout="column" class="tb-entity-table-content"> |
|||
<mat-toolbar class="mat-table-toolbar" [fxShow]="!textSearchMode"> |
|||
<div class="mat-toolbar-tools"> |
|||
<span class="tb-entity-table-title" translate>dashboard.states</span> |
|||
<span fxFlex></span> |
|||
<button mat-button mat-icon-button [disabled]="isLoading$ | async" |
|||
type="button" |
|||
(click)="addState($event)" |
|||
matTooltip="{{ 'dashboard.add-state' | translate }}" |
|||
matTooltipPosition="above"> |
|||
<mat-icon>add</mat-icon> |
|||
</button> |
|||
<button mat-button mat-icon-button [disabled]="isLoading$ | async" (click)="enterFilterMode()" |
|||
type="button" |
|||
matTooltip="{{ 'action.search' | translate }}" |
|||
matTooltipPosition="above"> |
|||
<mat-icon>search</mat-icon> |
|||
</button> |
|||
</div> |
|||
</mat-toolbar> |
|||
<mat-toolbar class="mat-table-toolbar" [fxShow]="textSearchMode"> |
|||
<div class="mat-toolbar-tools"> |
|||
<button mat-button mat-icon-button |
|||
type="button" |
|||
matTooltip="{{ 'dashboard.search-states' | translate }}" |
|||
matTooltipPosition="above"> |
|||
<mat-icon>search</mat-icon> |
|||
</button> |
|||
<mat-form-field fxFlex> |
|||
<mat-label> </mat-label> |
|||
<input #searchInput matInput |
|||
[(ngModel)]="pageLink.textSearch" |
|||
[ngModelOptions]="{standalone: true}" |
|||
placeholder="{{ 'dashboard.search-states' | translate }}"/> |
|||
</mat-form-field> |
|||
<button mat-button mat-icon-button (click)="exitFilterMode()" |
|||
type="button" |
|||
matTooltip="{{ 'action.close' | translate }}" |
|||
matTooltipPosition="above"> |
|||
<mat-icon>close</mat-icon> |
|||
</button> |
|||
</div> |
|||
</mat-toolbar> |
|||
<div class="table-container"> |
|||
<mat-table [dataSource]="dataSource" |
|||
matSort [matSortActive]="pageLink.sortOrder.property" [matSortDirection]="(pageLink.sortOrder.direction + '').toLowerCase()" matSortDisableClear> |
|||
<ng-container matColumnDef="name"> |
|||
<mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'dashboard.state-name' | translate }} </mat-header-cell> |
|||
<mat-cell *matCellDef="let state"> |
|||
{{ state.name }} |
|||
</mat-cell> |
|||
</ng-container> |
|||
<ng-container matColumnDef="id"> |
|||
<mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'dashboard.state-id' | translate }} </mat-header-cell> |
|||
<mat-cell *matCellDef="let state"> |
|||
{{ state.id }} |
|||
</mat-cell> |
|||
</ng-container> |
|||
<ng-container matColumnDef="root"> |
|||
<mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'dashboard.is-root-state' | translate }} </mat-header-cell> |
|||
<mat-cell *matCellDef="let state"> |
|||
<mat-icon class="material-icons mat-icon">{{state.root ? 'check_box' : 'check_box_outline_blank'}}</mat-icon> |
|||
</mat-cell> |
|||
</ng-container> |
|||
<ng-container matColumnDef="actions" stickyEnd> |
|||
<mat-header-cell *matHeaderCellDef [ngStyle]="{ minWidth: '80px', maxWidth: '80px' }"> |
|||
</mat-header-cell> |
|||
<mat-cell *matCellDef="let state" [ngStyle]="{ minWidth: '80px', maxWidth: '80px' }"> |
|||
<div fxFlex fxLayout="row"> |
|||
<button mat-button mat-icon-button [disabled]="isLoading$ | async" |
|||
type="button" |
|||
matTooltip="{{ 'dashboard.edit-state' | translate }}" |
|||
matTooltipPosition="above" |
|||
(click)="editState($event, state)"> |
|||
<mat-icon>edit</mat-icon> |
|||
</button> |
|||
<button [fxShow]="!state.root" mat-button mat-icon-button [disabled]="isLoading$ | async" |
|||
type="button" |
|||
matTooltip="{{ 'dashboard.delete-state' | translate }}" |
|||
matTooltipPosition="above" |
|||
(click)="deleteState($event, state)"> |
|||
<mat-icon>delete</mat-icon> |
|||
</button> |
|||
</div> |
|||
</mat-cell> |
|||
</ng-container> |
|||
<mat-header-row class="mat-row-select" *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row> |
|||
<mat-row class="mat-row-select" |
|||
*matRowDef="let state; columns: displayedColumns;"></mat-row> |
|||
</mat-table> |
|||
<span [fxShow]="dataSource.isEmpty() | async" |
|||
fxLayoutAlign="center center" |
|||
class="no-data-found" translate>{{ 'dashboard.no-states-text' }}</span> |
|||
</div> |
|||
<mat-divider></mat-divider> |
|||
<mat-paginator [length]="dataSource.total() | async" |
|||
[pageIndex]="pageLink.page" |
|||
[pageSize]="pageLink.pageSize" |
|||
[pageSizeOptions]="[5, 10, 15]"></mat-paginator> |
|||
</div> |
|||
</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) || statesFormGroup.invalid || !statesFormGroup.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,100 @@ |
|||
///
|
|||
/// 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 { DashboardState } from '@shared/models/dashboard.models'; |
|||
import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections'; |
|||
import { WidgetActionDescriptorInfo } from '@home/components/widget/action/manage-widget-actions.component.models'; |
|||
import { BehaviorSubject, Observable, of, ReplaySubject } from 'rxjs'; |
|||
import { emptyPageData, PageData } from '@shared/models/page/page-data'; |
|||
import { PageLink } from '@shared/models/page/page-link'; |
|||
import { catchError, map, publishReplay, refCount } from 'rxjs/operators'; |
|||
|
|||
export interface DashboardStateInfo extends DashboardState { |
|||
id: string; |
|||
} |
|||
|
|||
export class DashboardStatesDatasource implements DataSource<DashboardStateInfo> { |
|||
|
|||
private statesSubject = new BehaviorSubject<DashboardStateInfo[]>([]); |
|||
private pageDataSubject = new BehaviorSubject<PageData<DashboardStateInfo>>(emptyPageData<DashboardStateInfo>()); |
|||
|
|||
public pageData$ = this.pageDataSubject.asObservable(); |
|||
|
|||
private allStates: Observable<Array<DashboardStateInfo>>; |
|||
|
|||
constructor(private states: {[id: string]: DashboardState }) { |
|||
} |
|||
|
|||
connect(collectionViewer: CollectionViewer): Observable<DashboardStateInfo[] | ReadonlyArray<DashboardStateInfo>> { |
|||
return this.statesSubject.asObservable(); |
|||
} |
|||
|
|||
disconnect(collectionViewer: CollectionViewer): void { |
|||
this.statesSubject.complete(); |
|||
this.pageDataSubject.complete(); |
|||
} |
|||
|
|||
loadStates(pageLink: PageLink, reload: boolean = false): Observable<PageData<DashboardStateInfo>> { |
|||
if (reload) { |
|||
this.allStates = null; |
|||
} |
|||
const result = new ReplaySubject<PageData<DashboardStateInfo>>(); |
|||
this.fetchStates(pageLink).pipe( |
|||
catchError(() => of(emptyPageData<DashboardStateInfo>())), |
|||
).subscribe( |
|||
(pageData) => { |
|||
this.statesSubject.next(pageData.data); |
|||
this.pageDataSubject.next(pageData); |
|||
result.next(pageData); |
|||
} |
|||
); |
|||
return result; |
|||
} |
|||
|
|||
fetchStates(pageLink: PageLink): Observable<PageData<DashboardStateInfo>> { |
|||
return this.getAllStates().pipe( |
|||
map((data) => pageLink.filterData(data)) |
|||
); |
|||
} |
|||
|
|||
getAllStates(): Observable<Array<DashboardStateInfo>> { |
|||
if (!this.allStates) { |
|||
const states: DashboardStateInfo[] = []; |
|||
for (const id of Object.keys(this.states)) { |
|||
const state = this.states[id]; |
|||
states.push({id, ...state}); |
|||
} |
|||
this.allStates = of(states).pipe( |
|||
publishReplay(1), |
|||
refCount() |
|||
); |
|||
} |
|||
return this.allStates; |
|||
} |
|||
|
|||
isEmpty(): Observable<boolean> { |
|||
return this.statesSubject.pipe( |
|||
map((states) => !states.length) |
|||
); |
|||
} |
|||
|
|||
total(): Observable<number> { |
|||
return this.pageDataSubject.pipe( |
|||
map((pageData) => pageData.totalElements) |
|||
); |
|||
} |
|||
|
|||
} |
|||
@ -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. |
|||
*/ |
|||
:host { |
|||
.manage-dashboard-states { |
|||
.tb-entity-table { |
|||
.tb-entity-table-content { |
|||
width: 100%; |
|||
height: 100%; |
|||
background: #fff; |
|||
|
|||
.tb-entity-table-title { |
|||
padding-right: 20px; |
|||
white-space: nowrap; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
} |
|||
|
|||
.table-container { |
|||
overflow: auto; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
:host ::ng-deep { |
|||
.manage-dashboard-states { |
|||
.mat-sort-header-sorted .mat-sort-header-arrow { |
|||
opacity: 1 !important; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,244 @@ |
|||
///
|
|||
/// 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 { AfterViewInit, Component, ElementRef, Inject, OnInit, SkipSelf, ViewChild } 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 { DashboardState } from '@app/shared/models/dashboard.models'; |
|||
import { MatDialog } from '@angular/material/dialog'; |
|||
import { PageLink } from '@shared/models/page/page-link'; |
|||
import { |
|||
WidgetActionDescriptorInfo, |
|||
WidgetActionsDatasource |
|||
} from '@home/components/widget/action/manage-widget-actions.component.models'; |
|||
import { |
|||
DashboardStateInfo, |
|||
DashboardStatesDatasource |
|||
} from '@home/pages/dashboard/states/manage-dashboard-states-dialog.component.models'; |
|||
import { Direction, SortOrder } from '@shared/models/page/sort-order'; |
|||
import { MatPaginator } from '@angular/material/paginator'; |
|||
import { MatSort } from '@angular/material/sort'; |
|||
import { fromEvent, merge } from 'rxjs'; |
|||
import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { DialogService } from '@core/services/dialog.service'; |
|||
import { |
|||
WidgetActionDialogComponent, |
|||
WidgetActionDialogData |
|||
} from '@home/components/widget/action/widget-action-dialog.component'; |
|||
import { deepClone } from '@core/utils'; |
|||
import { |
|||
DashboardStateDialogComponent, |
|||
DashboardStateDialogData |
|||
} from '@home/pages/dashboard/states/dashboard-state-dialog.component'; |
|||
|
|||
export interface ManageDashboardStatesDialogData { |
|||
states: {[id: string]: DashboardState }; |
|||
} |
|||
|
|||
@Component({ |
|||
selector: 'tb-manage-dashboard-states-dialog', |
|||
templateUrl: './manage-dashboard-states-dialog.component.html', |
|||
providers: [{provide: ErrorStateMatcher, useExisting: ManageDashboardStatesDialogComponent}], |
|||
styleUrls: ['./manage-dashboard-states-dialog.component.scss'] |
|||
}) |
|||
export class ManageDashboardStatesDialogComponent extends |
|||
DialogComponent<ManageDashboardStatesDialogComponent, {[id: string]: DashboardState }> |
|||
implements OnInit, ErrorStateMatcher, AfterViewInit { |
|||
|
|||
statesFormGroup: FormGroup; |
|||
|
|||
states: {[id: string]: DashboardState }; |
|||
|
|||
displayedColumns: string[]; |
|||
pageLink: PageLink; |
|||
textSearchMode = false; |
|||
dataSource: DashboardStatesDatasource; |
|||
|
|||
submitted = false; |
|||
|
|||
@ViewChild('searchInput', {static: false}) searchInputField: ElementRef; |
|||
|
|||
@ViewChild(MatPaginator, {static: false}) paginator: MatPaginator; |
|||
@ViewChild(MatSort, {static: false}) sort: MatSort; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
protected router: Router, |
|||
@Inject(MAT_DIALOG_DATA) public data: ManageDashboardStatesDialogData, |
|||
@SkipSelf() private errorStateMatcher: ErrorStateMatcher, |
|||
public dialogRef: MatDialogRef<ManageDashboardStatesDialogComponent, {[id: string]: DashboardState }>, |
|||
private fb: FormBuilder, |
|||
private translate: TranslateService, |
|||
private dialogs: DialogService, |
|||
private dialog: MatDialog) { |
|||
super(store, router, dialogRef); |
|||
|
|||
this.states = this.data.states; |
|||
this.statesFormGroup = this.fb.group({}); |
|||
|
|||
const sortOrder: SortOrder = { property: 'name', direction: Direction.ASC }; |
|||
this.pageLink = new PageLink(5, 0, null, sortOrder); |
|||
this.displayedColumns = ['name', 'id', 'root', 'actions']; |
|||
this.dataSource = new DashboardStatesDatasource(this.states); |
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
this.dataSource.loadStates(this.pageLink); |
|||
} |
|||
|
|||
ngAfterViewInit() { |
|||
fromEvent(this.searchInputField.nativeElement, 'keyup') |
|||
.pipe( |
|||
debounceTime(150), |
|||
distinctUntilChanged(), |
|||
tap(() => { |
|||
this.paginator.pageIndex = 0; |
|||
this.updateData(); |
|||
}) |
|||
) |
|||
.subscribe(); |
|||
|
|||
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); |
|||
|
|||
merge(this.sort.sortChange, this.paginator.page) |
|||
.pipe( |
|||
tap(() => this.updateData()) |
|||
) |
|||
.subscribe(); |
|||
} |
|||
|
|||
updateData(reload: boolean = false) { |
|||
this.pageLink.page = this.paginator.pageIndex; |
|||
this.pageLink.pageSize = this.paginator.pageSize; |
|||
this.pageLink.sortOrder.property = this.sort.active; |
|||
this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()]; |
|||
this.dataSource.loadStates(this.pageLink, reload); |
|||
} |
|||
|
|||
addState($event: Event) { |
|||
this.openStateDialog($event); |
|||
} |
|||
|
|||
editState($event: Event, state: DashboardStateInfo) { |
|||
this.openStateDialog($event, state); |
|||
} |
|||
|
|||
deleteState($event: Event, state: DashboardStateInfo) { |
|||
if ($event) { |
|||
$event.stopPropagation(); |
|||
} |
|||
const title = this.translate.instant('dashboard.delete-state-title'); |
|||
const content = this.translate.instant('dashboard.delete-state-text', {stateName: state.name}); |
|||
this.dialogs.confirm(title, content, this.translate.instant('action.no'), |
|||
this.translate.instant('action.yes')).subscribe( |
|||
(res) => { |
|||
if (res) { |
|||
delete this.states[state.id]; |
|||
this.onStatesUpdated(); |
|||
} |
|||
} |
|||
); |
|||
} |
|||
|
|||
enterFilterMode() { |
|||
this.textSearchMode = true; |
|||
this.pageLink.textSearch = ''; |
|||
setTimeout(() => { |
|||
this.searchInputField.nativeElement.focus(); |
|||
this.searchInputField.nativeElement.setSelectionRange(0, 0); |
|||
}, 10); |
|||
} |
|||
|
|||
exitFilterMode() { |
|||
this.textSearchMode = false; |
|||
this.pageLink.textSearch = null; |
|||
this.paginator.pageIndex = 0; |
|||
this.updateData(); |
|||
} |
|||
|
|||
openStateDialog($event: Event, state: DashboardStateInfo = null) { |
|||
if ($event) { |
|||
$event.stopPropagation(); |
|||
} |
|||
const isAdd = state === null; |
|||
let prevStateId = null; |
|||
if (!isAdd) { |
|||
prevStateId = state.id; |
|||
} |
|||
this.dialog.open<DashboardStateDialogComponent, DashboardStateDialogData, |
|||
DashboardStateInfo>(DashboardStateDialogComponent, { |
|||
disableClose: true, |
|||
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
|||
data: { |
|||
isAdd, |
|||
states: this.states, |
|||
state: deepClone(state) |
|||
} |
|||
}).afterClosed().subscribe( |
|||
(res) => { |
|||
if (res) { |
|||
this.saveState(res, prevStateId); |
|||
} |
|||
} |
|||
); |
|||
} |
|||
|
|||
saveState(state: DashboardStateInfo, prevStateId: string) { |
|||
const newState: DashboardState = { |
|||
name: state.name, |
|||
root: state.root, |
|||
layouts: state.layouts |
|||
}; |
|||
if (prevStateId) { |
|||
this.states[prevStateId] = newState; |
|||
} else { |
|||
this.states[state.id] = newState; |
|||
} |
|||
if (state.root) { |
|||
for (const id of Object.keys(this.states)) { |
|||
const otherState = this.states[id]; |
|||
if (id !== state.id) { |
|||
otherState.root = false; |
|||
} |
|||
} |
|||
} |
|||
this.onStatesUpdated(); |
|||
} |
|||
|
|||
private onStatesUpdated() { |
|||
this.statesFormGroup.markAsDirty(); |
|||
this.updateData(true); |
|||
} |
|||
|
|||
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; |
|||
this.dialogRef.close(this.states); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue