Browse Source

Widget Editor: Save and save as actions.

pull/2150/head
Igor Kulikov 7 years ago
parent
commit
2eb93dac7e
  1. 1
      ui-ngx/src/app/core/api/widget-subscription.ts
  2. 149
      ui-ngx/src/app/core/http/widget.service.ts
  3. 2
      ui-ngx/src/app/core/services/utils.service.ts
  4. 8
      ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts
  5. 63
      ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts
  6. 3
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  7. 2
      ui-ngx/src/app/modules/home/models/dashboard-component.models.ts
  8. 24
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  9. 28
      ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts
  10. 3
      ui-ngx/src/app/modules/home/pages/dashboard/layout/dashboard-layout.component.ts
  11. 1
      ui-ngx/src/app/modules/home/pages/dashboard/layout/layout.models.ts
  12. 65
      ui-ngx/src/app/modules/home/pages/widget/save-widget-type-as-dialog.component.html
  13. 81
      ui-ngx/src/app/modules/home/pages/widget/save-widget-type-as-dialog.component.ts
  14. 22
      ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html
  15. 82
      ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts
  16. 2
      ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts
  17. 5
      ui-ngx/src/app/modules/home/pages/widget/widget-library.component.html
  18. 39
      ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts
  19. 7
      ui-ngx/src/app/modules/home/pages/widget/widget-library.module.ts
  20. 3
      ui-ngx/src/app/shared/components/dashboard-select.component.ts
  21. 34
      ui-ngx/src/app/shared/components/widgets-bundle-select.component.html
  22. 91
      ui-ngx/src/app/shared/components/widgets-bundle-select.component.scss
  23. 156
      ui-ngx/src/app/shared/components/widgets-bundle-select.component.ts
  24. 3
      ui-ngx/src/app/shared/shared.module.ts

1
ui-ngx/src/app/core/api/widget-subscription.ts

@ -505,6 +505,7 @@ export class WidgetSubscription implements IWidgetSubscription {
private alarmsSubscribe() {
// TODO:
this.notifyDataLoaded();
}

149
ui-ngx/src/app/core/http/widget.service.ts

@ -16,7 +16,7 @@
import { Injectable } from '@angular/core';
import { defaultHttpOptions } from './http-utils';
import { Observable } from 'rxjs/index';
import { Observable, Subject, of, ReplaySubject } from 'rxjs/index';
import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link';
import { PageData } from '@shared/models/page/page-data';
@ -25,20 +25,57 @@ import { WidgetType, widgetType, WidgetTypeData, widgetTypesData } from '@shared
import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core';
import { ResourcesService } from '../services/resources.service';
import { toWidgetInfo, WidgetInfo } from '@app/modules/home/models/widget-component.models';
import { map } from 'rxjs/operators';
import { toWidgetInfo, WidgetInfo, toWidgetType } from '@app/modules/home/models/widget-component.models';
import { map, tap, mergeMap, filter } from 'rxjs/operators';
import { WidgetTypeId } from '@shared/models/id/widget-type-id';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import { ActivationEnd, Router } from '@angular/router';
@Injectable({
providedIn: 'root'
})
export class WidgetService {
private widgetTypeUpdatedSubject = new Subject<WidgetType>();
private widgetsBundleDeletedSubject = new Subject<WidgetsBundle>();
private allWidgetsBundles: Array<WidgetsBundle>;
private systemWidgetsBundles: Array<WidgetsBundle>;
private tenantWidgetsBundles: Array<WidgetsBundle>;
constructor(
private http: HttpClient,
private utils: UtilsService,
private resources: ResourcesService,
private translate: TranslateService
private translate: TranslateService,
private router: Router
) {
this.router.events.pipe(filter(event => event instanceof ActivationEnd)).subscribe(
() => {
this.invalidateWidgetsBundleCache();
}
);
}
public getAllWidgetsBundles(ignoreErrors: boolean = false,
ignoreLoading: boolean = false): Observable<Array<WidgetsBundle>> {
return this.loadWidgetsBundleCache(ignoreErrors, ignoreLoading).pipe(
map(() => this.allWidgetsBundles)
);
}
public getSystemWidgetsBundles(ignoreErrors: boolean = false,
ignoreLoading: boolean = false): Observable<Array<WidgetsBundle>> {
return this.loadWidgetsBundleCache(ignoreErrors, ignoreLoading).pipe(
map(() => this.systemWidgetsBundles)
);
}
public getTenantWidgetsBundles(ignoreErrors: boolean = false,
ignoreLoading: boolean = false): Observable<Array<WidgetsBundle>> {
return this.loadWidgetsBundleCache(ignoreErrors, ignoreLoading).pipe(
map(() => this.tenantWidgetsBundles)
);
}
public getWidgetBundles(pageLink: PageLink, ignoreErrors: boolean = false,
@ -54,11 +91,26 @@ export class WidgetService {
public saveWidgetsBundle(widgetsBundle: WidgetsBundle,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetsBundle> {
return this.http.post<WidgetsBundle>('/api/widgetsBundle', widgetsBundle, defaultHttpOptions(ignoreLoading, ignoreErrors));
return this.http.post<WidgetsBundle>('/api/widgetsBundle', widgetsBundle,
defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe(
tap(() => {
this.invalidateWidgetsBundleCache();
})
);
}
public deleteWidgetsBundle(widgetsBundleId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) {
return this.http.delete(`/api/widgetsBundle/${widgetsBundleId}`, defaultHttpOptions(ignoreLoading, ignoreErrors));
return this.getWidgetsBundle(widgetsBundleId, ignoreErrors, ignoreLoading).pipe(
mergeMap((widgetsBundle) => {
return this.http.delete(`/api/widgetsBundle/${widgetsBundleId}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe(
tap(() => {
this.invalidateWidgetsBundleCache();
this.widgetsBundleDeletedSubject.next(widgetsBundle);
})
);
}
));
}
public getBundleWidgetTypes(bundleAlias: string, isSystem: boolean,
@ -73,6 +125,41 @@ export class WidgetService {
defaultHttpOptions(ignoreLoading, ignoreErrors));
}
public saveWidgetType(widgetInfo: WidgetInfo,
id: WidgetTypeId,
bundleAlias: string,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetType> {
const widgetTypeInstance = toWidgetType(widgetInfo, id, undefined, bundleAlias);
return this.http.post<WidgetType>('/api/widgetType', widgetTypeInstance,
defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe(
tap((savedWidgetType) => {
this.widgetTypeUpdatedSubject.next(savedWidgetType);
}));
}
public saveImportedWidgetType(widgetTypeInstance: WidgetType,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetType> {
return this.http.post<WidgetType>('/api/widgetType', widgetTypeInstance,
defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe(
tap((savedWidgetType) => {
this.widgetTypeUpdatedSubject.next(savedWidgetType);
}));
}
public deleteWidgetType(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean,
ignoreErrors: boolean = false, ignoreLoading: boolean = false) {
return this.getWidgetType(bundleAlias, widgetTypeAlias, isSystem, ignoreErrors, ignoreLoading).pipe(
mergeMap((widgetTypeInstance) => {
return this.http.delete(`/api/widgetType/${widgetTypeInstance.id.id}`,
defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe(
tap(() => {
this.widgetTypeUpdatedSubject.next(widgetTypeInstance);
})
);
}
));
}
public getWidgetTypeById(widgetTypeId: string,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<WidgetType> {
return this.http.get<WidgetType>(`/api/widgetType/${widgetTypeId}`,
@ -90,5 +177,55 @@ export class WidgetService {
return widgetInfo;
})
);
}
public onWidgetTypeUpdated(): Observable<WidgetType> {
return this.widgetTypeUpdatedSubject.asObservable();
}
public onWidgetBundleDeleted(): Observable<WidgetsBundle> {
return this.widgetsBundleDeletedSubject.asObservable();
}
private loadWidgetsBundleCache(ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<any> {
if (!this.allWidgetsBundles) {
const loadWidgetsBundleCacheSubject = new ReplaySubject();
this.http.get<Array<WidgetsBundle>>('/api/widgetsBundles',
defaultHttpOptions(ignoreLoading, ignoreErrors)).subscribe(
(allWidgetsBundles) => {
this.allWidgetsBundles = allWidgetsBundles;
this.systemWidgetsBundles = new Array<WidgetsBundle>();
this.tenantWidgetsBundles = new Array<WidgetsBundle>();
this.allWidgetsBundles = this.allWidgetsBundles.sort((wb1, wb2) => {
let res = wb1.title.localeCompare(wb2.title);
if (res === 0) {
res = wb2.createdTime - wb1.createdTime;
}
return res;
});
this.allWidgetsBundles.forEach((widgetsBundle) => {
if (widgetsBundle.tenantId.id === NULL_UUID) {
this.systemWidgetsBundles.push(widgetsBundle);
} else {
this.tenantWidgetsBundles.push(widgetsBundle);
}
});
loadWidgetsBundleCacheSubject.next();
loadWidgetsBundleCacheSubject.complete();
},
() => {
loadWidgetsBundleCacheSubject.error(null);
});
return loadWidgetsBundleCacheSubject.asObservable();
} else {
return of(null);
}
}
private invalidateWidgetsBundleCache() {
this.allWidgetsBundles = undefined;
this.systemWidgetsBundles = undefined;
this.tenantWidgetsBundles = undefined;
}
}

2
ui-ngx/src/app/core/services/utils.service.ts

@ -84,7 +84,7 @@ export class UtilsService {
}
public processWidgetException(exception: any): ExceptionData {
const data = this.parseException(exception, -5);
const data = this.parseException(exception, -6);
if (this.widgetEditMode) {
const message: WindowMessage = {
type: 'widgetException',

8
ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts

@ -308,7 +308,13 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo
$event.stopPropagation();
}
if (this.isRemoveActionEnabled && this.callbacks && this.callbacks.onRemoveWidget) {
this.callbacks.onRemoveWidget($event, widget.widget);
this.callbacks.onRemoveWidget($event, widget.widget).subscribe(
(result) => {
if (result) {
this.dashboardWidgets.removeWidget(widget.widget);
}
}
);
}
}

63
ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts

@ -18,16 +18,19 @@ import { Inject, Injectable } from '@angular/core';
import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service';
import { WidgetService } from '@core/http/widget.service';
import { forkJoin, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs';
import { WidgetInfo, MissingWidgetType, toWidgetInfo, WidgetTypeInstance, ErrorWidgetType } from '@home/models/widget-component.models';
import {
ErrorWidgetType,
MissingWidgetType,
toWidgetInfo,
toWidgetType,
WidgetInfo,
WidgetTypeInstance
} from '@home/models/widget-component.models';
import cssjs from '@core/css/css';
import { UtilsService } from '@core/services/utils.service';
import { ResourcesService } from '@core/services/resources.service';
import {
widgetActionSources,
WidgetControllerDescriptor,
WidgetType
} from '@shared/models/widget.models';
import { catchError, switchMap, map, mergeMap } from 'rxjs/operators';
import { widgetActionSources, WidgetControllerDescriptor, WidgetType } from '@shared/models/widget.models';
import { catchError, map, mergeMap, switchMap } from 'rxjs/operators';
import { isFunction, isUndefined } from '@core/utils';
import { TranslateService } from '@ngx-translate/core';
import { DynamicWidgetComponent } from '@home/components/widget/dynamic-widget.component';
@ -37,6 +40,9 @@ import { WINDOW } from '@core/services/window.service';
import * as tinycolor from 'tinycolor2';
import { TbFlot } from './lib/flot-widget';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import { WidgetTypeId } from '@app/shared/models/id/widget-type-id';
import { TenantId } from '@app/shared/models/id/tenant-id';
// declare var jQuery: any;
@ -53,6 +59,7 @@ export class WidgetComponentService {
private missingWidgetType: WidgetInfo;
private errorWidgetType: WidgetInfo;
private editingWidgetType: WidgetType;
constructor(@Inject(WINDOW) private window: Window,
private dynamicComponentFactoryService: DynamicComponentFactoryService,
@ -68,6 +75,15 @@ export class WidgetComponentService {
this.window.TbFlot = TbFlot;
this.cssParser.testMode = false;
this.widgetService.onWidgetTypeUpdated().subscribe((widgetType) => {
this.deleteWidgetInfoFromCache(widgetType.bundleAlias, widgetType.alias, widgetType.tenantId.id === NULL_UUID);
});
this.widgetService.onWidgetBundleDeleted().subscribe((widgetsBundle) => {
this.deleteWidgetsBundleFromCache(widgetsBundle.alias, widgetsBundle.tenantId.id === NULL_UUID);
});
this.init();
}
@ -77,6 +93,24 @@ export class WidgetComponentService {
} else {
this.missingWidgetType = {...MissingWidgetType};
this.errorWidgetType = {...ErrorWidgetType};
if (this.utils.widgetEditMode) {
this.editingWidgetType = toWidgetType(
{
widgetName: this.utils.editWidgetInfo.widgetName,
alias: 'customWidget',
type: this.utils.editWidgetInfo.type,
sizeX: this.utils.editWidgetInfo.sizeX,
sizeY: this.utils.editWidgetInfo.sizeY,
resources: this.utils.editWidgetInfo.resources,
templateHtml: this.utils.editWidgetInfo.templateHtml,
templateCss: this.utils.editWidgetInfo.templateCss,
controllerScript: this.utils.editWidgetInfo.controllerScript,
settingsSchema: this.utils.editWidgetInfo.settingsSchema,
dataKeySettingsSchema: this.utils.editWidgetInfo.dataKeySettingsSchema,
defaultConfig: this.utils.editWidgetInfo.defaultConfig
}, new WidgetTypeId('1'), new TenantId( NULL_UUID ), 'customWidgetBundle'
);
}
const initSubject = new ReplaySubject();
this.init$ = initSubject.asObservable();
const loadDefaultWidgetInfoTasks = [
@ -110,7 +144,7 @@ export class WidgetComponentService {
widgetInfoSubject.complete();
} else {
if (this.utils.widgetEditMode) {
// TODO:
this.loadWidget(this.editingWidgetType, bundleAlias, isSystem, widgetInfoSubject);
} else {
const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);
let fetchQueue = this.widgetsInfoFetchQueue.get(key);
@ -377,4 +411,17 @@ export class WidgetComponentService {
this.widgetsInfoInMemoryCache.set(key, widgetInfo);
}
private deleteWidgetInfoFromCache(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean) {
const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);
this.widgetsInfoInMemoryCache.delete(key);
}
private deleteWidgetsBundleFromCache(bundleAlias: string, isSystem: boolean) {
const key = (isSystem ? 'sys_' : '') + bundleAlias;
this.widgetsInfoInMemoryCache.forEach((widgetInfo, cacheKey) => {
if (cacheKey.startsWith(key)) {
this.widgetsInfoInMemoryCache.delete(cacheKey);
}
});
}
}

3
ui-ngx/src/app/modules/home/components/widget/widget.component.ts

@ -847,13 +847,16 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
createSubscriptionSubject.error(null);
}
);
this.cd.detectChanges();
} else if (this.widget.type === widgetType.static) {
this.loadingData = false;
createSubscriptionSubject.next();
createSubscriptionSubject.complete();
this.cd.detectChanges();
} else {
createSubscriptionSubject.next();
createSubscriptionSubject.complete();
this.cd.detectChanges();
}
return createSubscriptionSubject.asObservable();
}

2
ui-ngx/src/app/modules/home/models/dashboard-component.models.ts

@ -33,7 +33,7 @@ export interface WidgetsData {
export interface DashboardCallbacks {
onEditWidget?: ($event: Event, widget: Widget) => void;
onExportWidget?: ($event: Event, widget: Widget) => void;
onRemoveWidget?: ($event: Event, widget: Widget) => void;
onRemoveWidget?: ($event: Event, widget: Widget) => Observable<boolean>;
onWidgetMouseDown?: ($event: Event, widget: Widget) => void;
onWidgetClicked?: ($event: Event, widget: Widget) => void;
prepareDashboardContextMenu?: ($event: Event) => void;

24
ui-ngx/src/app/modules/home/models/widget-component.models.ts

@ -43,6 +43,8 @@ import {
import { ComponentFactory } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { RafService } from '@core/services/raf.service';
import { WidgetTypeId } from '@shared/models/id/widget-type-id';
import { TenantId } from '@shared/models/id/tenant-id';
export interface IWidgetAction {
name: string;
@ -192,3 +194,25 @@ export function toWidgetInfo(widgetTypeEntity: WidgetType): WidgetInfo {
};
}
export function toWidgetType(widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId: TenantId, bundleAlias: string): WidgetType {
const descriptor: WidgetTypeDescriptor = {
type: widgetInfo.type,
sizeX: widgetInfo.sizeX,
sizeY: widgetInfo.sizeY,
resources: widgetInfo.resources,
templateHtml: widgetInfo.templateHtml,
templateCss: widgetInfo.templateCss,
controllerScript: widgetInfo.controllerScript,
settingsSchema: widgetInfo.settingsSchema,
dataKeySettingsSchema: widgetInfo.dataKeySettingsSchema,
defaultConfig: widgetInfo.defaultConfig
};
return {
id,
tenantId,
bundleAlias,
alias: widgetInfo.alias,
name: widgetInfo.widgetName,
descriptor
};
}

28
ui-ngx/src/app/modules/home/pages/dashboard/dashboard-page.component.ts

@ -51,6 +51,7 @@ import { Subscription } from 'rxjs';
import { FooterFabButtons } from '@shared/components/footer-fab-buttons.component';
import { IStateController } from '@core/api/widget-api.models';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
import { DashboardService } from '@core/http/dashboard.service';
@Component({
selector: 'tb-dashboard-page',
@ -173,7 +174,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
private dashboardUtils: DashboardUtilsService,
private authService: AuthService,
private entityService: EntityService,
private dialogService: DialogService) {
private dialogService: DialogService,
private dashboardService: DashboardService) {
super(store);
this.rxSubscriptions.push(this.route.data.subscribe(
@ -460,6 +462,11 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
this.setEditMode(!this.isEdit, true);
}
public saveDashboard() {
this.setEditMode(false, false);
this.notifyDashboardUpdated();
}
public openDashboardState(state: string, openRightLayout: boolean) {
const layoutsData = this.dashboardUtils.getStateLayoutsData(this.dashboard, state);
if (layoutsData) {
@ -514,8 +521,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
private setEditMode(isEdit: boolean, revert: boolean) {
this.isEdit = isEdit;
if (this.isEdit) {
// TODO:
// this.dashboardCtx.stateController.preserveState();
this.dashboardCtx.stateController.preserveState();
this.prevDashboard = deepClone(this.dashboard);
} else {
if (this.widgetEditMode) {
@ -549,4 +555,20 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
private entityAliasesUpdated() {
this.dashboardCtx.aliasController.updateEntityAliases(this.dashboard.configuration.entityAliases);
}
private notifyDashboardUpdated() {
if (this.widgetEditMode) {
const widget = this.layouts.main.layoutCtx.widgets[0];
const layout = this.layouts.main.layoutCtx.widgetLayouts[widget.id];
widget.sizeX = layout.sizeX;
widget.sizeY = layout.sizeY;
const message: WindowMessage = {
type: 'widgetEditUpdated',
data: widget
};
this.window.parent.postMessage(JSON.stringify(message), '*');
} else {
this.dashboardService.saveDashboard(this.dashboard);
}
}
}

3
ui-ngx/src/app/modules/home/pages/dashboard/layout/dashboard-layout.component.ts

@ -77,4 +77,7 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo
setResizing(layoutVisibilityChanged: boolean) {
}
resetHighlight() {
}
}

1
ui-ngx/src/app/modules/home/pages/dashboard/layout/layout.models.ts

@ -17,4 +17,5 @@
export interface ILayoutController {
reload();
setResizing(layoutVisibilityChanged: boolean);
resetHighlight();
}

65
ui-ngx/src/app/modules/home/pages/widget/save-widget-type-as-dialog.component.html

@ -0,0 +1,65 @@
<!--
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 #saveWidgetTypeAsForm="ngForm"
[formGroup]="saveWidgetTypeAsFormGroup"(ngSubmit)="saveAs()">
<mat-toolbar fxLayout="row" color="primary">
<h2 translate>widget.save-widget-type-as</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 style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content>
<fieldset>
<span translate>widget.save-widget-type-as-text</span>
<mat-form-field class="mat-block">
<mat-label translate>widget.title</mat-label>
<input matInput formControlName="title" required>
<mat-error *ngIf="saveWidgetTypeAsFormGroup.get('title').hasError('required')">
{{ 'widget.title-required' | translate }}
</mat-error>
</mat-form-field>
<tb-widgets-bundle-select fxFlex
formControlName="widgetsBundle"
required
bundlesScope="{{bundlesScope}}">
</tb-widgets-bundle-select>
</fieldset>
</div>
<div mat-dialog-actions fxLayout="row">
<span fxFlex></span>
<button mat-button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || saveWidgetTypeAsForm.invalid
|| !saveWidgetTypeAsForm.dirty">
{{ 'action.saveAs' | 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>

81
ui-ngx/src/app/modules/home/pages/widget/save-widget-type-as-dialog.component.ts

@ -0,0 +1,81 @@
///
/// Copyright © 2016-2019 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, OnInit } from '@angular/core';
import { MatDialogRef } from '@angular/material';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router';
import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import { Authority } from '@shared/models/authority.enum';
export interface SaveWidgetTypeAsDialogResult {
widgetName: string;
bundleId: string;
bundleAlias: string;
}
@Component({
selector: 'tb-save-widget-type-as-dialog',
templateUrl: './save-widget-type-as-dialog.component.html',
styleUrls: []
})
export class SaveWidgetTypeAsDialogComponent extends
DialogComponent<SaveWidgetTypeAsDialogComponent, SaveWidgetTypeAsDialogResult> implements OnInit {
saveWidgetTypeAsFormGroup: FormGroup;
bundlesScope: string;
constructor(protected store: Store<AppState>,
protected router: Router,
public dialogRef: MatDialogRef<SaveWidgetTypeAsDialogComponent, SaveWidgetTypeAsDialogResult>,
public fb: FormBuilder) {
super(store, router, dialogRef);
const authUser = getCurrentAuthUser(store);
if (authUser.authority === Authority.TENANT_ADMIN) {
this.bundlesScope = 'tenant';
} else {
this.bundlesScope = 'system';
}
}
ngOnInit(): void {
this.saveWidgetTypeAsFormGroup = this.fb.group({
title: [null, [Validators.required]],
widgetsBundle: [null, [Validators.required]]
});
}
cancel(): void {
this.dialogRef.close(null);
}
saveAs(): void {
const widgetName: string = this.saveWidgetTypeAsFormGroup.get('title').value;
const widgetsBundle: WidgetsBundle = this.saveWidgetTypeAsFormGroup.get('widgetsBundle').value;
const result: SaveWidgetTypeAsDialogResult = {
widgetName,
bundleId: widgetsBundle.id.id,
bundleAlias: widgetsBundle.alias
};
this.dialogRef.close(result);
}
}

22
ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html

@ -19,9 +19,10 @@
<div fxFlex fxLayout="column">
<div fxFlex fxLayout="column" tb-fullscreen [fullscreen]="fullscreen">
<mat-toolbar class="mat-elevation-z1 tb-edit-toolbar mat-hue-3" fxLayoutGap="16px">
<mat-form-field floatLabel="always" class="tb-widget-title">
<mat-form-field floatLabel="always" hideRequiredMarker class="tb-widget-title">
<mat-label></mat-label>
<input [disabled]="isReadOnly" matInput [(ngModel)]="widget.widgetName" (ngModelChange)="isDirty = true"
<input [disabled]="isReadOnly" matInput required
[(ngModel)]="widget.widgetName" (ngModelChange)="isDirty = true"
placeholder="{{ 'widget.title' | translate }}"/>
</mat-form-field>
<mat-form-field>
@ -238,14 +239,15 @@
</div>
<div tb-fullscreen [fullscreen]="iFrameFullscreen" style="width: 100%; height: 100%;">
<iframe #widgetIFrame frameborder="0" height="100%" width="100%"></iframe>
<button mat-button mat-icon-button
class="tb-fullscreen-button-style"
style="position: absolute; top: 10px; left: 10px; bottom: initial;"
(click)="iFrameFullscreen = !iFrameFullscreen"
matTooltip="{{(iFrameFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}"
matTooltipPosition="above">
<mat-icon>{{ iFrameFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>
</button>
<div style="position: absolute; top: 10px; left: 10px; bottom: initial;">
<button mat-button mat-icon-button
class="tb-fullscreen-button-style"
(click)="iFrameFullscreen = !iFrameFullscreen"
matTooltip="{{(iFrameFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}"
matTooltipPosition="above">
<mat-icon>{{ iFrameFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>
</button>
</div>
</div>
</div>
</div>

82
ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts

@ -20,9 +20,9 @@ import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { WidgetService } from '@core/http/widget.service';
import { WidgetInfo } from '@home/models/widget-component.models';
import { toWidgetInfo, WidgetInfo } from '@home/models/widget-component.models';
import { WidgetConfig, widgetType, WidgetType, widgetTypesData, Widget } from '@shared/models/widget.models';
import { ActivatedRoute } from '@angular/router';
import { ActivatedRoute, Router } from '@angular/router';
import { deepClone } from '@core/utils';
import { HasDirtyFlag } from '@core/guards/confirm-on-exit.guard';
import { AuthUser } from '@shared/models/user.model';
@ -40,6 +40,13 @@ import { WindowMessage } from '@shared/models/window-message.model';
import { ExceptionData } from '@shared/models/error.models';
import Timeout = NodeJS.Timeout;
import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions';
import { MatDialog } from '@angular/material/dialog';
import { SelectWidgetTypeDialogComponent } from '@home/pages/widget/select-widget-type-dialog.component';
import {
SaveWidgetTypeAsDialogComponent,
SaveWidgetTypeAsDialogResult
} from '@home/pages/widget/save-widget-type-as-dialog.component';
import { Subscription } from 'rxjs';
@Component({
selector: 'tb-widget-editor',
@ -131,25 +138,37 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
saveWidgetTimeout: Timeout;
private rxSubscriptions = new Array<Subscription>();
constructor(protected store: Store<AppState>,
@Inject(WINDOW) private window: Window,
private route: ActivatedRoute,
private router: Router,
private widgetService: WidgetService,
private hotkeysService: HotkeysService,
private translate: TranslateService,
private raf: RafService) {
private raf: RafService,
private dialog: MatDialog) {
super(store);
this.authUser = getCurrentAuthUser(store);
this.widgetsBundle = this.route.snapshot.data.widgetsBundle;
this.rxSubscriptions.push(this.route.data.subscribe(
(data) => {
this.init(data);
}
));
}
private init(data: any) {
this.widgetsBundle = data.widgetsBundle;
if (this.authUser.authority === Authority.TENANT_ADMIN) {
this.isReadOnly = !this.widgetsBundle || this.widgetsBundle.tenantId.id === NULL_UUID;
} else {
this.isReadOnly = this.authUser.authority !== Authority.SYS_ADMIN;
}
this.widgetType = this.route.snapshot.data.widgetEditorData.widgetType;
this.widget = this.route.snapshot.data.widgetEditorData.widget;
this.widgetType = data.widgetEditorData.widgetType;
this.widget = data.widgetEditorData.widget;
if (this.widgetType) {
const config = JSON.parse(this.widget.defaultConfig);
this.widget.defaultConfig = JSON.stringify(config);
@ -176,6 +195,10 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
// @ts-ignore
removeResizeListener(resizeListener.element, resizeListener.resizeListener);
});
this.rxSubscriptions.forEach((subscription) => {
subscription.unsubscribe();
});
this.rxSubscriptions.length = 0;
}
private initHotKeys(): void {
@ -448,13 +471,52 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
}
private commitSaveWidget() {
// TODO:
this.saveWidgetPending = false;
const id = (this.widgetType && this.widgetType.id) ? this.widgetType.id : undefined;
this.widgetService.saveWidgetType(this.widget, id, this.widgetsBundle.alias).subscribe(
(widgetTypeInstance) => {
this.setWidgetType(widgetTypeInstance);
this.saveWidgetPending = false;
this.store.dispatch(new ActionNotificationShow(
{message: this.translate.instant('widget.widget-saved'), type: 'success', duration: 500}));
},
() => {
this.saveWidgetPending = false;
}
);
}
private commitSaveWidgetAs() {
// TODO:
this.saveWidgetAsPending = false;
this.dialog.open<SaveWidgetTypeAsDialogComponent, any,
SaveWidgetTypeAsDialogResult>(SaveWidgetTypeAsDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog']
}).afterClosed().subscribe(
(saveWidgetAsData) => {
if (saveWidgetAsData) {
this.widget.widgetName = saveWidgetAsData.widgetName;
this.widget.alias = undefined;
const config = JSON.parse(this.widget.defaultConfig);
config.title = this.widget.widgetName;
this.widget.defaultConfig = JSON.stringify(config);
this.isDirty = false;
this.widgetService.saveWidgetType(this.widget, undefined, saveWidgetAsData.bundleAlias).subscribe(
(widgetTypeInstance) => {
this.router.navigateByUrl(`/widgets-bundles/${saveWidgetAsData.bundleId}/widgetTypes/${widgetTypeInstance.id.id}`);
}
);
}
this.saveWidgetAsPending = false;
}
);
}
private setWidgetType(widgetTypeInstance: WidgetType) {
this.widgetType = widgetTypeInstance;
this.widget = toWidgetInfo(this.widgetType);
const config = JSON.parse(this.widget.defaultConfig);
this.widget.defaultConfig = JSON.stringify(config);
this.origWidget = deepClone(this.widget);
this.isDirty = false;
}
applyWidgetScript(): void {

2
ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts

@ -73,7 +73,7 @@ export class WidgetsTypesDataResolver implements Resolve<WidgetsData> {
}
return result;
});
const widgetTypes = new Array<Widget>(types.length);
const widgetTypes = new Array<Widget>();
let top = 0;
const lastTop = [0, 0, 0];
let col = 0;

5
ui-ngx/src/app/modules/home/pages/widget/widget-library.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<section [fxShow]="!(isLoading$ | async) && (widgetTypes$ | async)?.length === 0" fxLayoutAlign="center center"
<section [fxShow]="!(isLoading$ | async) && widgetsData.widgets.length === 0" fxLayoutAlign="center center"
style="text-transform: uppercase; display: flex; z-index: 1;"
class="tb-absolute-fill">
<button mat-button *ngIf="!isReadOnly" class="tb-add-new-widget" (click)="addWidgetType($event)">
@ -27,7 +27,8 @@
style="text-transform: uppercase; display: flex;"
class="mat-headline tb-absolute-fill">widgets-bundle.empty</span>
</section>
<tb-dashboard [aliasController]="aliasController"
<tb-dashboard #dashboard
[aliasController]="aliasController"
[widgets]="widgetsData.widgets"
[widgetLayouts]="widgetsData.widgetLayouts"
[isEdit]="false"

39
ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, ViewChild } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { PageComponent } from '@shared/components/page.component';
@ -24,13 +24,13 @@ import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
import { ActivatedRoute, Router } from '@angular/router';
import { Authority } from '@shared/models/authority.enum';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import { Observable } from 'rxjs';
import { Observable, of } from 'rxjs';
import { Widget, widgetType } from '@app/shared/models/widget.models';
import { WidgetService } from '@core/http/widget.service';
import { map, share } from 'rxjs/operators';
import { map, mergeMap, share } from 'rxjs/operators';
import { DialogService } from '@core/services/dialog.service';
import { FooterFabButtons } from '@app/shared/components/footer-fab-buttons.component';
import { DashboardCallbacks, WidgetsData } from '@home/models/dashboard-component.models';
import { DashboardCallbacks, IDashboardComponent, WidgetsData } from '@home/models/dashboard-component.models';
import { IAliasController } from '@app/core/api/widget-api.models';
import { toWidgetInfo } from '@home/models/widget-component.models';
import { DummyAliasController } from '@core/api/alias-controller';
@ -41,6 +41,7 @@ import {
import { DeviceCredentials } from '@shared/models/device.models';
import { MatDialog } from '@angular/material/dialog';
import { SelectWidgetTypeDialogComponent } from '@home/pages/widget/select-widget-type-dialog.component';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'tb-widget-library',
@ -86,12 +87,15 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit {
aliasController: IAliasController = new DummyAliasController();
@ViewChild('dashboard', {static: true}) dashboard: IDashboardComponent;
constructor(protected store: Store<AppState>,
private route: ActivatedRoute,
private router: Router,
private widgetService: WidgetService,
private dialogService: DialogService,
private dialog: MatDialog) {
private dialog: MatDialog,
private translate: TranslateService) {
super(store);
this.authUser = getCurrentAuthUser(store);
@ -146,11 +150,32 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit {
this.dialogService.todo();
}
removeWidgetType($event: Event, widget: Widget): void {
removeWidgetType($event: Event, widget: Widget): Observable<boolean> {
if ($event) {
$event.stopPropagation();
}
this.dialogService.todo();
return this.dialogService.confirm(
this.translate.instant('widget.remove-widget-type-title', {widgetName: widget.config.title}),
this.translate.instant('widget.remove-widget-type-text'),
this.translate.instant('action.no'),
this.translate.instant('action.yes'),
).pipe(
mergeMap((result) => {
if (result) {
return this.widgetService.deleteWidgetType(widget.bundleAlias, widget.typeAlias, widget.isSystemType);
} else {
return of(false);
}
}),
map((result) => {
if (result !== false) {
this.widgetsData.widgets.splice(this.widgetsData.widgets.indexOf(widget), 1);
return true;
} else {
return false;
}
}
));
}
}

7
ui-ngx/src/app/modules/home/pages/widget/widget-library.module.ts

@ -23,17 +23,20 @@ import {HomeComponentsModule} from '@modules/home/components/home-components.mod
import { WidgetLibraryComponent } from './widget-library.component';
import { WidgetEditorComponent } from '@home/pages/widget/widget-editor.component';
import { SelectWidgetTypeDialogComponent } from '@home/pages/widget/select-widget-type-dialog.component';
import { SaveWidgetTypeAsDialogComponent } from './save-widget-type-as-dialog.component';
@NgModule({
entryComponents: [
WidgetsBundleComponent,
SelectWidgetTypeDialogComponent
SelectWidgetTypeDialogComponent,
SaveWidgetTypeAsDialogComponent
],
declarations: [
WidgetsBundleComponent,
WidgetLibraryComponent,
WidgetEditorComponent,
SelectWidgetTypeDialogComponent
SelectWidgetTypeDialogComponent,
SaveWidgetTypeAsDialogComponent
],
imports: [
CommonModule,

3
ui-ngx/src/app/shared/components/dashboard-select.component.ts

@ -38,6 +38,7 @@ import {
DashboardSelectPanelComponent,
DashboardSelectPanelData
} from './dashboard-select-panel.component';
import { NULL_UUID } from '@shared/models/id/has-uuid';
@Component({
selector: 'tb-dashboard-select',
@ -200,7 +201,7 @@ export class DashboardSelectComponent implements ControlValueAccessor, OnInit {
let dashboardsObservable: Observable<PageData<DashboardInfo>>;
const authUser = getCurrentAuthUser(this.store);
if (this.dashboardsScope === 'customer' || authUser.authority === Authority.CUSTOMER_USER) {
if (this.customerId) {
if (this.customerId && this.customerId !== NULL_UUID) {
dashboardsObservable = this.dashboardService.getCustomerDashboards(this.customerId, pageLink, false, true);
} else {
dashboardsObservable = of(emptyPageData());

34
ui-ngx/src/app/shared/components/widgets-bundle-select.component.html

@ -0,0 +1,34 @@
<!--
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.
-->
<mat-form-field floatLabel="always" hideRequiredMarker class="mat-block">
<mat-label></mat-label>
<mat-select [required]="required"
[disabled]="disabled"
[(ngModel)]="widgetsBundle"
matInput
panelClass="tb-widgets-bundle-select"
placeholder="{{ 'widget.select-widgets-bundle' | translate }}"
(ngModelChange)="widgetsBundleChanged()">
<mat-option *ngFor="let widgetsBundle of widgetsBundles$ | async" [value]="widgetsBundle">
<div class="tb-bundle-item">
<span>{{widgetsBundle.title}}</span>
<span translate class="tb-bundle-system" *ngIf="isSystem(item)">widgets-bundle.system</span>
</div>
</mat-option>
</mat-select>
</mat-form-field>

91
ui-ngx/src/app/shared/components/widgets-bundle-select.component.scss

@ -0,0 +1,91 @@
/**
* 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.
*/
tb-widgets-bundle-select {
mat-select {
margin: 0;
}
.tb-bundle-item {
height: 24px;
line-height: 24px;
}
}
.tb-widgets-bundle-select {
.tb-bundle-item {
height: 48px;
line-height: 48px;
}
}
tb-widgets-bundle-select,
.tb-widgets-bundle-select {
.mat-select-value-text {
display: block;
width: 100%;
}
.tb-bundle-item {
display: inline-block;
width: 100%;
span {
display: inline-block;
vertical-align: middle;
}
.tb-bundle-system {
float: right;
font-size: .8rem;
opacity: .8;
}
}
mat-option {
height: auto !important;
white-space: normal !important;
}
}
mat-toolbar {
tb-widgets-bundle-select {
mat-select {
background: rgba(255, 255, 255, .2);
padding: 5px 20px;
.mat-select-value-text {
font-size: 1.2rem;
color: #fff;
span:first-child::after {
color: #fff;
}
}
.mat-select-value.mat-select-placeholder {
color: #fff;
opacity: .8;
}
}
mat-select.ng-invalid.ng-touched {
.mat-select-value-text {
color: #fff !important;
}
}
}
}

156
ui-ngx/src/app/shared/components/widgets-bundle-select.component.ts

@ -0,0 +1,156 @@
///
/// 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, forwardRef, Input, OnChanges, OnInit, ViewEncapsulation, SimpleChanges } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Observable } from 'rxjs';
import { share, tap } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
import { WidgetService } from '@core/http/widget.service';
import { isDefined } from '@core/utils';
import { NULL_UUID } from '@shared/models/id/has-uuid';
@Component({
selector: 'tb-widgets-bundle-select',
templateUrl: './widgets-bundle-select.component.html',
styleUrls: ['./widgets-bundle-select.component.scss'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => WidgetsBundleSelectComponent),
multi: true
}],
encapsulation: ViewEncapsulation.None
})
export class WidgetsBundleSelectComponent implements ControlValueAccessor, OnInit, OnChanges {
@Input()
bundlesScope: 'system' | 'tenant';
@Input()
selectFirstBundle: boolean;
@Input()
selectBundleAlias: string;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
}
@Input()
disabled: boolean;
widgetsBundles$: Observable<Array<WidgetsBundle>>;
widgetsBundles: Array<WidgetsBundle>;
widgetsBundle: WidgetsBundle | null;
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
private widgetService: WidgetService) {
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
ngOnInit() {
this.widgetsBundles$ = this.getWidgetsBundles().pipe(
tap((widgetsBundles) => {
this.widgetsBundles = widgetsBundles;
if (this.selectFirstBundle) {
if (widgetsBundles.length > 0) {
if (this.widgetsBundle !== widgetsBundles[0]) {
this.widgetsBundle = widgetsBundles[0];
this.updateView();
} else if (isDefined(this.selectBundleAlias)) {
this.selectWidgetsBundleByAlias(this.selectBundleAlias);
}
}
}
}),
share()
);
}
ngOnChanges(changes: SimpleChanges): void {
for (const propName of Object.keys(changes)) {
const change = changes[propName];
if (!change.firstChange && change.currentValue !== change.previousValue) {
if (propName === 'selectBundleAlias') {
this.selectWidgetsBundleByAlias(this.selectBundleAlias);
}
}
}
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
writeValue(value: WidgetsBundle | null): void {
this.widgetsBundle = value;
}
widgetsBundleChanged() {
this.updateView();
}
isSystem(item: WidgetsBundle) {
return item && item.tenantId.id === NULL_UUID;
}
private selectWidgetsBundleByAlias(alias: string) {
if (this.widgetsBundles && alias) {
const found = this.widgetsBundles.find((widgetsBundle) => widgetsBundle.alias === alias);
if (found && this.widgetsBundle !== found) {
this.widgetsBundle = found;
this.updateView();
}
}
}
private updateView() {
this.propagateChange(this.widgetsBundle);
}
private getWidgetsBundles(): Observable<Array<WidgetsBundle>> {
let widgetsBundlesObservable: Observable<Array<WidgetsBundle>>;
if (this.bundlesScope) {
if (this.bundlesScope === 'system') {
widgetsBundlesObservable = this.widgetService.getSystemWidgetsBundles();
} else if (this.bundlesScope === 'tenant') {
widgetsBundlesObservable = this.widgetService.getTenantWidgetsBundles();
}
} else {
widgetsBundlesObservable = this.widgetService.getAllWidgetsBundles();
}
return widgetsBundlesObservable;
}
}

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

@ -94,6 +94,7 @@ import { MatSpinner } from '@angular/material/progress-spinner';
import { FabToolbarComponent, FabActionsDirective, FabTriggerDirective } from './components/fab-toolbar.component';
import { DashboardSelectPanelComponent } from '@shared/components/dashboard-select-panel.component';
import { DashboardSelectComponent } from '@shared/components/dashboard-select.component';
import { WidgetsBundleSelectComponent } from './components/widgets-bundle-select.component';
@NgModule({
providers: [
@ -145,6 +146,7 @@ import { DashboardSelectComponent } from '@shared/components/dashboard-select.co
FabTriggerDirective,
FabActionsDirective,
FabToolbarComponent,
WidgetsBundleSelectComponent,
NospacePipe,
MillisecondsToTimeStringPipe,
EnumToArrayPipe,
@ -226,6 +228,7 @@ import { DashboardSelectComponent } from '@shared/components/dashboard-select.co
FabTriggerDirective,
FabActionsDirective,
FabToolbarComponent,
WidgetsBundleSelectComponent,
ValueInputComponent,
MatButtonModule,
MatCheckboxModule,

Loading…
Cancel
Save