74 changed files with 4959 additions and 184 deletions
@ -0,0 +1,348 @@ |
|||||
|
///
|
||||
|
/// 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 { Injectable } from '@angular/core'; |
||||
|
import { UtilsService } from '@core/services/utils.service'; |
||||
|
import { TimeService } from '@core/services/time.service'; |
||||
|
import { |
||||
|
Dashboard, |
||||
|
DashboardLayout, |
||||
|
DashboardStateLayouts, |
||||
|
DashboardState, |
||||
|
DashboardConfiguration |
||||
|
} from '@shared/models/dashboard.models'; |
||||
|
import { isUndefined, isDefined, isString } from '@core/utils'; |
||||
|
import { DatasourceType, Widget, Datasource } from '@app/shared/models/widget.models'; |
||||
|
import { EntityType } from '@shared/models/entity-type.models'; |
||||
|
import { EntityAlias, AliasFilterType } from '@app/shared/models/alias.models'; |
||||
|
|
||||
|
@Injectable({ |
||||
|
providedIn: 'root' |
||||
|
}) |
||||
|
export class DashboardUtilsService { |
||||
|
|
||||
|
constructor(private utils: UtilsService, |
||||
|
private timeService: TimeService) { |
||||
|
} |
||||
|
|
||||
|
public validateAndUpdateDashboard(dashboard: Dashboard): Dashboard { |
||||
|
if (!dashboard.configuration) { |
||||
|
dashboard.configuration = {}; |
||||
|
} |
||||
|
if (isUndefined(dashboard.configuration.widgets)) { |
||||
|
dashboard.configuration.widgets = {}; |
||||
|
} else if (Array.isArray(dashboard.configuration.widgets)) { |
||||
|
const widgetsMap: {[id: string]: Widget} = {}; |
||||
|
dashboard.configuration.widgets.forEach((widget) => { |
||||
|
if (!widget.id) { |
||||
|
widget.id = this.utils.guid(); |
||||
|
} |
||||
|
widgetsMap[widget.id] = widget; |
||||
|
}); |
||||
|
dashboard.configuration.widgets = widgetsMap; |
||||
|
} |
||||
|
for (const id of Object.keys(dashboard.configuration.widgets)) { |
||||
|
const widget = dashboard.configuration.widgets[id]; |
||||
|
dashboard.configuration.widgets[id] = this.validateAndUpdateWidget(widget); |
||||
|
} |
||||
|
if (isUndefined(dashboard.configuration.states)) { |
||||
|
dashboard.configuration.states = { |
||||
|
default: this.createDefaultState(dashboard.title, true) |
||||
|
}; |
||||
|
|
||||
|
const mainLayout = dashboard.configuration.states.default.layouts.main; |
||||
|
for (const id of Object.keys(dashboard.configuration.widgets)) { |
||||
|
const widget = dashboard.configuration.widgets[id]; |
||||
|
mainLayout.widgets[id] = { |
||||
|
sizeX: widget.sizeX, |
||||
|
sizeY: widget.sizeY, |
||||
|
row: widget.row, |
||||
|
col: widget.col |
||||
|
}; |
||||
|
if (isDefined(widget.config.mobileHeight)) { |
||||
|
mainLayout.widgets[id].mobileHeight = widget.config.mobileHeight; |
||||
|
} |
||||
|
if (isDefined(widget.config.mobileOrder)) { |
||||
|
mainLayout.widgets[id].mobileOrder = widget.config.mobileOrder; |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
const states = dashboard.configuration.states; |
||||
|
let rootFound = false; |
||||
|
for (const stateId of Object.keys(states)) { |
||||
|
const state = states[stateId]; |
||||
|
if (isUndefined(state.root)) { |
||||
|
state.root = false; |
||||
|
} else if (state.root) { |
||||
|
rootFound = true; |
||||
|
} |
||||
|
} |
||||
|
if (!rootFound) { |
||||
|
const firstStateId = Object.keys(states)[0]; |
||||
|
states[firstStateId].root = true; |
||||
|
} |
||||
|
} |
||||
|
const datasourcesByAliasId: {[aliasId: string]: Array<Datasource>} = {}; |
||||
|
const targetDevicesByAliasId: {[aliasId: string]: Array<Array<string>>} = {}; |
||||
|
for (const widgetId of Object.keys(dashboard.configuration.widgets)) { |
||||
|
const widget = dashboard.configuration.widgets[widgetId]; |
||||
|
widget.config.datasources.forEach((datasource) => { |
||||
|
if (datasource.entityAliasId) { |
||||
|
const aliasId = datasource.entityAliasId; |
||||
|
let aliasDatasources = datasourcesByAliasId[aliasId]; |
||||
|
if (!aliasDatasources) { |
||||
|
aliasDatasources = []; |
||||
|
datasourcesByAliasId[aliasId] = aliasDatasources; |
||||
|
} |
||||
|
aliasDatasources.push(datasource); |
||||
|
} |
||||
|
}); |
||||
|
if (widget.config.targetDeviceAliasIds && widget.config.targetDeviceAliasIds.length) { |
||||
|
const aliasId = widget.config.targetDeviceAliasIds[0]; |
||||
|
let targetDeviceAliasIdsList = targetDevicesByAliasId[aliasId]; |
||||
|
if (!targetDeviceAliasIdsList) { |
||||
|
targetDeviceAliasIdsList = []; |
||||
|
targetDevicesByAliasId[aliasId] = targetDeviceAliasIdsList; |
||||
|
} |
||||
|
targetDeviceAliasIdsList.push(widget.config.targetDeviceAliasIds); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
dashboard.configuration = this.validateAndUpdateEntityAliases(dashboard.configuration, datasourcesByAliasId, targetDevicesByAliasId); |
||||
|
|
||||
|
if (isUndefined(dashboard.configuration.timewindow)) { |
||||
|
dashboard.configuration.timewindow = this.timeService.defaultTimewindow(); |
||||
|
} |
||||
|
if (isUndefined(dashboard.configuration.settings)) { |
||||
|
dashboard.configuration.settings = {}; |
||||
|
dashboard.configuration.settings.stateControllerId = 'entity'; |
||||
|
dashboard.configuration.settings.showTitle = false; |
||||
|
dashboard.configuration.settings.showDashboardsSelect = true; |
||||
|
dashboard.configuration.settings.showEntitiesSelect = true; |
||||
|
dashboard.configuration.settings.showDashboardTimewindow = true; |
||||
|
dashboard.configuration.settings.showDashboardExport = true; |
||||
|
dashboard.configuration.settings.toolbarAlwaysOpen = true; |
||||
|
} else { |
||||
|
if (isUndefined(dashboard.configuration.settings.stateControllerId)) { |
||||
|
dashboard.configuration.settings.stateControllerId = 'entity'; |
||||
|
} |
||||
|
} |
||||
|
if (isDefined(dashboard.configuration.gridSettings)) { |
||||
|
const gridSettings = dashboard.configuration.gridSettings; |
||||
|
if (isDefined(gridSettings.showTitle)) { |
||||
|
dashboard.configuration.settings.showTitle = gridSettings.showTitle; |
||||
|
delete gridSettings.showTitle; |
||||
|
} |
||||
|
if (isDefined(gridSettings.titleColor)) { |
||||
|
dashboard.configuration.settings.titleColor = gridSettings.titleColor; |
||||
|
delete gridSettings.titleColor; |
||||
|
} |
||||
|
if (isDefined(gridSettings.showDevicesSelect)) { |
||||
|
dashboard.configuration.settings.showEntitiesSelect = gridSettings.showDevicesSelect; |
||||
|
delete gridSettings.showDevicesSelect; |
||||
|
} |
||||
|
if (isDefined(gridSettings.showEntitiesSelect)) { |
||||
|
dashboard.configuration.settings.showEntitiesSelect = gridSettings.showEntitiesSelect; |
||||
|
delete gridSettings.showEntitiesSelect; |
||||
|
} |
||||
|
if (isDefined(gridSettings.showDashboardTimewindow)) { |
||||
|
dashboard.configuration.settings.showDashboardTimewindow = gridSettings.showDashboardTimewindow; |
||||
|
delete gridSettings.showDashboardTimewindow; |
||||
|
} |
||||
|
if (isDefined(gridSettings.showDashboardExport)) { |
||||
|
dashboard.configuration.settings.showDashboardExport = gridSettings.showDashboardExport; |
||||
|
delete gridSettings.showDashboardExport; |
||||
|
} |
||||
|
dashboard.configuration.states.default.layouts.main.gridSettings = gridSettings; |
||||
|
delete dashboard.configuration.gridSettings; |
||||
|
} |
||||
|
return dashboard; |
||||
|
} |
||||
|
|
||||
|
public createSingleWidgetDashboard(widget: Widget): Dashboard { |
||||
|
if (!widget.id) { |
||||
|
widget.id = this.utils.guid(); |
||||
|
} |
||||
|
let dashboard: Dashboard = {}; |
||||
|
dashboard = this.validateAndUpdateDashboard(dashboard); |
||||
|
dashboard.configuration.widgets[widget.id] = widget; |
||||
|
dashboard.configuration.states.default.layouts.main.widgets[widget.id] = { |
||||
|
sizeX: widget.sizeX, |
||||
|
sizeY: widget.sizeY, |
||||
|
row: widget.row, |
||||
|
col: widget.col, |
||||
|
}; |
||||
|
return dashboard; |
||||
|
} |
||||
|
|
||||
|
public validateAndUpdateWidget(widget: Widget): Widget { |
||||
|
if (!widget.config) { |
||||
|
widget.config = {}; |
||||
|
} |
||||
|
if (!widget.config.datasources) { |
||||
|
widget.config.datasources = []; |
||||
|
} |
||||
|
widget.config.datasources.forEach((datasource) => { |
||||
|
if (datasource.type === 'device') { |
||||
|
datasource.type = DatasourceType.entity; |
||||
|
} |
||||
|
if (datasource.deviceAliasId) { |
||||
|
datasource.entityAliasId = datasource.deviceAliasId; |
||||
|
delete datasource.deviceAliasId; |
||||
|
} |
||||
|
}); |
||||
|
// TODO: Temp workaround
|
||||
|
if (widget.isSystemType && widget.bundleAlias === 'charts' && widget.typeAlias === 'timeseries') { |
||||
|
widget.typeAlias = 'basic_timeseries'; |
||||
|
} |
||||
|
return widget; |
||||
|
} |
||||
|
|
||||
|
public createDefaultLayoutData(): DashboardLayout { |
||||
|
return { |
||||
|
widgets: {}, |
||||
|
gridSettings: { |
||||
|
backgroundColor: '#eeeeee', |
||||
|
color: 'rgba(0,0,0,0.870588)', |
||||
|
columns: 24, |
||||
|
margins: [10, 10], |
||||
|
backgroundSizeMode: '100%' |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public createDefaultLayouts(): DashboardStateLayouts { |
||||
|
return { |
||||
|
main: this.createDefaultLayoutData() |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public createDefaultState(name: string, root: boolean): DashboardState { |
||||
|
return { |
||||
|
name, |
||||
|
root, |
||||
|
layouts: this.createDefaultLayouts() |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private validateAndUpdateEntityAliases(configuration: DashboardConfiguration, |
||||
|
datasourcesByAliasId: {[aliasId: string]: Array<Datasource>}, |
||||
|
targetDevicesByAliasId: {[aliasId: string]: Array<Array<string>>}): DashboardConfiguration { |
||||
|
let entityAlias: EntityAlias; |
||||
|
if (isUndefined(configuration.entityAliases)) { |
||||
|
configuration.entityAliases = {}; |
||||
|
if (configuration.deviceAliases) { |
||||
|
const deviceAliases = configuration.deviceAliases; |
||||
|
for (const aliasId of Object.keys(deviceAliases)) { |
||||
|
const deviceAlias = deviceAliases[aliasId]; |
||||
|
entityAlias = this.validateAndUpdateDeviceAlias(aliasId, deviceAlias, datasourcesByAliasId, targetDevicesByAliasId); |
||||
|
configuration.entityAliases[entityAlias.id] = entityAlias; |
||||
|
} |
||||
|
delete configuration.deviceAliases; |
||||
|
} |
||||
|
} else { |
||||
|
const entityAliases = configuration.entityAliases; |
||||
|
for (const aliasId of Object.keys(entityAliases)) { |
||||
|
entityAlias = entityAliases[aliasId]; |
||||
|
entityAlias = this.validateAndUpdateEntityAlias(aliasId, entityAlias, datasourcesByAliasId, targetDevicesByAliasId); |
||||
|
if (aliasId !== entityAlias.id) { |
||||
|
delete entityAliases[aliasId]; |
||||
|
} |
||||
|
entityAliases[entityAlias.id] = entityAlias; |
||||
|
} |
||||
|
} |
||||
|
return configuration; |
||||
|
} |
||||
|
|
||||
|
private validateAndUpdateDeviceAlias(aliasId: string, |
||||
|
deviceAlias: any, |
||||
|
datasourcesByAliasId: {[aliasId: string]: Array<Datasource>}, |
||||
|
targetDevicesByAliasId: {[aliasId: string]: Array<Array<string>>}): EntityAlias { |
||||
|
aliasId = this.validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId); |
||||
|
const alias = deviceAlias.alias; |
||||
|
const entityAlias: EntityAlias = { |
||||
|
id: aliasId, |
||||
|
alias, |
||||
|
filter: { |
||||
|
type: null, |
||||
|
entityType: EntityType.DEVICE, |
||||
|
resolveMultiple: false |
||||
|
}, |
||||
|
}; |
||||
|
if (deviceAlias.deviceFilter) { |
||||
|
entityAlias.filter.type = |
||||
|
deviceAlias.deviceFilter.useFilter ? AliasFilterType.entityName : AliasFilterType.entityList; |
||||
|
if (entityAlias.filter.type === AliasFilterType.entityList) { |
||||
|
entityAlias.filter.entityList = deviceAlias.deviceFilter.deviceList; |
||||
|
} else { |
||||
|
entityAlias.filter.entityNameFilter = deviceAlias.deviceFilter.deviceNameFilter; |
||||
|
} |
||||
|
} else { |
||||
|
entityAlias.filter.type = AliasFilterType.entityList; |
||||
|
entityAlias.filter.entityList = [deviceAlias.deviceId]; |
||||
|
} |
||||
|
return entityAlias; |
||||
|
} |
||||
|
|
||||
|
private validateAndUpdateEntityAlias(aliasId: string, entityAlias: EntityAlias, |
||||
|
datasourcesByAliasId: {[aliasId: string]: Array<Datasource>}, |
||||
|
targetDevicesByAliasId: {[aliasId: string]: Array<Array<string>>}): EntityAlias { |
||||
|
entityAlias.id = this.validateAliasId(aliasId, datasourcesByAliasId, targetDevicesByAliasId); |
||||
|
if (!entityAlias.filter) { |
||||
|
entityAlias.filter = { |
||||
|
type: entityAlias.entityFilter.useFilter ? AliasFilterType.entityName : AliasFilterType.entityList, |
||||
|
entityType: entityAlias.entityType, |
||||
|
resolveMultiple: false |
||||
|
}; |
||||
|
if (entityAlias.filter.type === AliasFilterType.entityList) { |
||||
|
entityAlias.filter.entityList = entityAlias.entityFilter.entityList; |
||||
|
} else { |
||||
|
entityAlias.filter.entityNameFilter = entityAlias.entityFilter.entityNameFilter; |
||||
|
} |
||||
|
delete entityAlias.entityType; |
||||
|
delete entityAlias.entityFilter; |
||||
|
} |
||||
|
return entityAlias; |
||||
|
} |
||||
|
|
||||
|
private validateAliasId(aliasId: string, |
||||
|
datasourcesByAliasId: {[aliasId: string]: Array<Datasource>}, |
||||
|
targetDevicesByAliasId: {[aliasId: string]: Array<Array<string>>}): string { |
||||
|
if (!aliasId || !isString(aliasId) || aliasId.length !== 36) { |
||||
|
const newAliasId = this.utils.guid(); |
||||
|
const aliasDatasources = datasourcesByAliasId[aliasId]; |
||||
|
if (aliasDatasources) { |
||||
|
aliasDatasources.forEach( |
||||
|
(datasource) => { |
||||
|
datasource.entityAliasId = newAliasId; |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
const targetDeviceAliasIdsList = targetDevicesByAliasId[aliasId]; |
||||
|
if (targetDeviceAliasIdsList) { |
||||
|
targetDeviceAliasIdsList.forEach( |
||||
|
(targetDeviceAliasIds) => { |
||||
|
targetDeviceAliasIds[0] = newAliasId; |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
return newAliasId; |
||||
|
} else { |
||||
|
return aliasId; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
///
|
||||
|
/// 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 { NgModule } from '@angular/core'; |
||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { SharedModule } from '@shared/shared.module'; |
||||
|
import { HomeComponentsModule } from '@modules/home/components/home-components.module'; |
||||
|
import { HomeDialogsModule } from '@app/modules/home/dialogs/home-dialogs.module'; |
||||
|
import { DashboardModule } from '@home/pages/dashboard/dashboard.module'; |
||||
|
import { DashboardPagesRoutingModule } from './dashboard-pages.routing.module'; |
||||
|
|
||||
|
@NgModule({ |
||||
|
entryComponents: [], |
||||
|
imports: [ |
||||
|
CommonModule, |
||||
|
SharedModule, |
||||
|
HomeComponentsModule, |
||||
|
HomeDialogsModule, |
||||
|
DashboardModule, |
||||
|
DashboardPagesRoutingModule |
||||
|
] |
||||
|
}) |
||||
|
export class DashboardPagesModule { } |
||||
@ -0,0 +1,104 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2019 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { Injectable, NgModule } from '@angular/core'; |
||||
|
import { ActivatedRouteSnapshot, Resolve, RouterModule, Routes } from '@angular/router'; |
||||
|
|
||||
|
import {Authority} from '@shared/models/authority.enum'; |
||||
|
import { DashboardPageComponent } from '@home/pages/dashboard/dashboard-page.component'; |
||||
|
import { BreadCrumbConfig, BreadCrumbLabelFunction } from '@shared/components/breadcrumb'; |
||||
|
import { widgetTypesBreadcumbLabelFunction } from '@home/pages/widget/widget-library-routing.module'; |
||||
|
import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; |
||||
|
import { WidgetService } from '@core/http/widget.service'; |
||||
|
import { Observable } from 'rxjs'; |
||||
|
import { Dashboard } from '@app/shared/models/dashboard.models'; |
||||
|
import { DashboardService } from '@core/http/dashboard.service'; |
||||
|
import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; |
||||
|
import { map } from 'rxjs/operators'; |
||||
|
import { dashboardBreadcumbLabelFunction, DashboardResolver } from '@app/modules/home/pages/dashboard/dashboard-routing.module'; |
||||
|
import { UtilsService } from '@core/services/utils.service'; |
||||
|
import { Widget } from '@app/shared/models/widget.models'; |
||||
|
|
||||
|
@Injectable() |
||||
|
export class WidgetEditorDashboardResolver implements Resolve<Dashboard> { |
||||
|
|
||||
|
constructor(private dashboardService: DashboardService, |
||||
|
private dashboardUtils: DashboardUtilsService, |
||||
|
private utils: UtilsService) { |
||||
|
} |
||||
|
|
||||
|
resolve(route: ActivatedRouteSnapshot): Dashboard { |
||||
|
const editWidgetInfo = this.utils.editWidgetInfo; |
||||
|
const widget: Widget = { |
||||
|
isSystemType: true, |
||||
|
bundleAlias: 'customWidgetBundle', |
||||
|
typeAlias: 'customWidget', |
||||
|
type: editWidgetInfo.type, |
||||
|
title: 'My widget', |
||||
|
sizeX: editWidgetInfo.sizeX * 2, |
||||
|
sizeY: editWidgetInfo.sizeY * 2, |
||||
|
row: 2, |
||||
|
col: 4, |
||||
|
config: JSON.parse(editWidgetInfo.defaultConfig) |
||||
|
}; |
||||
|
widget.config.title = widget.config.title || editWidgetInfo.widgetName; |
||||
|
return this.dashboardUtils.createSingleWidgetDashboard(widget); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const routes: Routes = [ |
||||
|
{ |
||||
|
path: 'dashboard/:dashboardId', |
||||
|
component: DashboardPageComponent, |
||||
|
data: { |
||||
|
breadcrumb: { |
||||
|
skip: true |
||||
|
}, |
||||
|
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], |
||||
|
title: 'dashboard.dashboard', |
||||
|
widgetEditMode: false, |
||||
|
singlePageMode: true |
||||
|
}, |
||||
|
resolve: { |
||||
|
dashboard: DashboardResolver |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
path: 'widget-editor', |
||||
|
component: DashboardPageComponent, |
||||
|
data: { |
||||
|
breadcrumb: { |
||||
|
skip: true |
||||
|
}, |
||||
|
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], |
||||
|
title: 'widget.editor', |
||||
|
widgetEditMode: true, |
||||
|
singlePageMode: true |
||||
|
}, |
||||
|
resolve: { |
||||
|
dashboard: WidgetEditorDashboardResolver |
||||
|
} |
||||
|
} |
||||
|
]; |
||||
|
|
||||
|
@NgModule({ |
||||
|
imports: [RouterModule.forChild(routes)], |
||||
|
exports: [RouterModule], |
||||
|
providers: [ |
||||
|
WidgetEditorDashboardResolver |
||||
|
] |
||||
|
}) |
||||
|
export class DashboardPagesRoutingModule { } |
||||
@ -0,0 +1,43 @@ |
|||||
|
///
|
||||
|
/// 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 { NgModule } from '@angular/core'; |
||||
|
import { Routes, RouterModule } from '@angular/router'; |
||||
|
|
||||
|
import { AuthGuard } from '@core/guards/auth.guard'; |
||||
|
import { StoreModule } from '@ngrx/store'; |
||||
|
|
||||
|
const routes: Routes = [ |
||||
|
{ path: '', |
||||
|
data: { |
||||
|
title: 'dashboard.dashboard', |
||||
|
breadcrumb: { |
||||
|
skip: true |
||||
|
} |
||||
|
}, |
||||
|
canActivate: [AuthGuard], |
||||
|
canActivateChild: [AuthGuard], |
||||
|
loadChildren: './dashboard-pages.module#DashboardPagesModule' |
||||
|
} |
||||
|
]; |
||||
|
|
||||
|
@NgModule({ |
||||
|
imports: [ |
||||
|
StoreModule, |
||||
|
RouterModule.forChild(routes)], |
||||
|
exports: [RouterModule] |
||||
|
}) |
||||
|
export class DashboardRoutingModule { } |
||||
@ -0,0 +1,30 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2019 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<div fxLayout="column" class="mat-content mat-padding"> |
||||
|
<div fxLayout="row" *ngFor="let alias of entityAliasesInfo | keyvalue"> |
||||
|
<mat-form-field> |
||||
|
<mat-label>{{alias.value.alias}}</mat-label> |
||||
|
<mat-select matInput [(ngModel)]="alias.value.selectedId" |
||||
|
(ngModelChange)="currentAliasEntityChanged(alias.key, alias.value.selectedId)"> |
||||
|
<mat-option *ngFor="let resolvedEntity of alias.value.resolvedEntities" [value]="resolvedEntity.id"> |
||||
|
{{resolvedEntity.name}} |
||||
|
</mat-option> |
||||
|
</mat-select> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,35 @@ |
|||||
|
/** |
||||
|
* 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 { |
||||
|
min-width: 300px; |
||||
|
max-height: 150px; |
||||
|
overflow-x: hidden; |
||||
|
overflow-y: auto; |
||||
|
background: #fff; |
||||
|
border-radius: 4px; |
||||
|
box-shadow: |
||||
|
0 7px 8px -4px rgba(0, 0, 0, .2), |
||||
|
0 13px 19px 2px rgba(0, 0, 0, .14), |
||||
|
0 5px 24px 4px rgba(0, 0, 0, .12); |
||||
|
|
||||
|
@media (min-height: 350px) { |
||||
|
max-height: 250px; |
||||
|
} |
||||
|
|
||||
|
.mat-content { |
||||
|
background-color: #fff; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,60 @@ |
|||||
|
///
|
||||
|
/// 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, InjectionToken, OnInit } from '@angular/core'; |
||||
|
import { Timewindow } from '@shared/models/time/time.models'; |
||||
|
import { AliasInfo, IAliasController } from '@core/api/widget-api.models'; |
||||
|
import { PageComponent } from '@shared/components/page.component'; |
||||
|
import { TIMEWINDOW_PANEL_DATA, TimewindowPanelData } from '@shared/components/time/timewindow-panel.component'; |
||||
|
import { deepClone } from '@core/utils'; |
||||
|
|
||||
|
export const ALIASES_ENTITY_SELECT_PANEL_DATA = new InjectionToken<any>('AliasesEntitySelectPanelData'); |
||||
|
|
||||
|
export interface AliasesEntitySelectPanelData { |
||||
|
aliasController: IAliasController; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-aliases-entity-select-panel', |
||||
|
templateUrl: './aliases-entity-select-panel.component.html', |
||||
|
styleUrls: ['./aliases-entity-select-panel.component.scss'] |
||||
|
}) |
||||
|
export class AliasesEntitySelectPanelComponent { |
||||
|
|
||||
|
entityAliasesInfo: {[aliasId: string]: AliasInfo} = {}; |
||||
|
|
||||
|
constructor(@Inject(ALIASES_ENTITY_SELECT_PANEL_DATA) public data: AliasesEntitySelectPanelData) { |
||||
|
const allEntityAliases = this.data.aliasController.getEntityAliases(); |
||||
|
for (const aliasId of Object.keys(allEntityAliases)) { |
||||
|
const aliasInfo = this.data.aliasController.getInstantAliasInfo(aliasId); |
||||
|
if (aliasInfo && !aliasInfo.resolveMultiple && aliasInfo.currentEntity |
||||
|
&& aliasInfo.resolvedEntities.length > 1) { |
||||
|
this.entityAliasesInfo[aliasId] = deepClone(aliasInfo); |
||||
|
this.entityAliasesInfo[aliasId].selectedId = aliasInfo.currentEntity.id; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public currentAliasEntityChanged(aliasId: string, selectedId: string) { |
||||
|
const resolvedEntities = this.entityAliasesInfo[aliasId].resolvedEntities; |
||||
|
const selected = resolvedEntities.find((entity) => entity.id === selectedId); |
||||
|
if (selected) { |
||||
|
this.data.aliasController.updateCurrentAliasEntity(aliasId, selected[0]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
<section class="tb-aliases-entity-select" fxLayout="row" fxLayoutAlign="start center"> |
||||
|
<button mat-button mat-icon-button |
||||
|
cdkOverlayOrigin #aliasEntitySelectPanelOrigin="cdkOverlayOrigin" |
||||
|
(click)="openEditMode()" |
||||
|
matTooltip="{{ 'entity.select-entities' | translate }}" |
||||
|
[matTooltipPosition]="tooltipPosition"> |
||||
|
<mat-icon class="material-icons">devices_other</mat-icon> |
||||
|
</button> |
||||
|
<span fxHide.xs fxHide.sm fxHide.md |
||||
|
(click)="openEditMode()" |
||||
|
matTooltip="{{ 'entity.select-entities' | translate }}" |
||||
|
[matTooltipPosition]="tooltipPosition"> |
||||
|
{{displayValue}} |
||||
|
</span> |
||||
|
</section> |
||||
@ -0,0 +1,38 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2019 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
@import "../../../../../scss/constants"; |
||||
|
|
||||
|
:host { |
||||
|
min-width: 52px; |
||||
|
|
||||
|
section.tb-aliases-entity-select { |
||||
|
min-height: 32px; |
||||
|
padding: 0 6px; |
||||
|
|
||||
|
@media #{$mat-lt-md} { |
||||
|
padding: 0; |
||||
|
} |
||||
|
|
||||
|
span { |
||||
|
max-width: 200px; |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
white-space: nowrap; |
||||
|
pointer-events: all; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,178 @@ |
|||||
|
///
|
||||
|
/// 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, Input, OnDestroy, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; |
||||
|
import { TooltipPosition } from '@angular/material'; |
||||
|
import { IAliasController } from '@core/api/widget-api.models'; |
||||
|
import { CdkOverlayOrigin, ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
import { Subscription } from 'rxjs'; |
||||
|
import { BreakpointObserver } from '@angular/cdk/layout'; |
||||
|
import { DOCUMENT } from '@angular/common'; |
||||
|
import { WINDOW } from '@core/services/window.service'; |
||||
|
import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; |
||||
|
import { |
||||
|
ALIASES_ENTITY_SELECT_PANEL_DATA, |
||||
|
AliasesEntitySelectPanelComponent, |
||||
|
AliasesEntitySelectPanelData |
||||
|
} from './aliases-entity-select-panel.component'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-aliases-entity-select', |
||||
|
templateUrl: './aliases-entity-select.component.html', |
||||
|
styleUrls: ['./aliases-entity-select.component.scss'] |
||||
|
}) |
||||
|
export class AliasesEntitySelectComponent implements OnInit, OnDestroy { |
||||
|
|
||||
|
@Input() |
||||
|
aliasController: IAliasController; |
||||
|
|
||||
|
@Input() |
||||
|
tooltipPosition: TooltipPosition = 'above'; |
||||
|
|
||||
|
@Input() disabled: boolean; |
||||
|
|
||||
|
@ViewChild('aliasEntitySelectPanelOrigin', {static: false}) aliasEntitySelectPanelOrigin: CdkOverlayOrigin; |
||||
|
|
||||
|
displayValue: string; |
||||
|
|
||||
|
private rxSubscriptions = new Array<Subscription>(); |
||||
|
|
||||
|
constructor(private translate: TranslateService, |
||||
|
private overlay: Overlay, |
||||
|
private breakpointObserver: BreakpointObserver, |
||||
|
private viewContainerRef: ViewContainerRef, |
||||
|
@Inject(DOCUMENT) private document: Document, |
||||
|
@Inject(WINDOW) private window: Window) { |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.rxSubscriptions.push(this.aliasController.entityAliasesChanged.subscribe( |
||||
|
() => { |
||||
|
this.updateDisplayValue(); |
||||
|
} |
||||
|
)); |
||||
|
this.rxSubscriptions.push(this.aliasController.entityAliasResolved.subscribe( |
||||
|
() => { |
||||
|
this.updateDisplayValue(); |
||||
|
} |
||||
|
)); |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy(): void { |
||||
|
this.rxSubscriptions.forEach((subscription) => { |
||||
|
subscription.unsubscribe(); |
||||
|
}); |
||||
|
this.rxSubscriptions.length = 0; |
||||
|
} |
||||
|
|
||||
|
openEditMode() { |
||||
|
if (this.disabled) { |
||||
|
return; |
||||
|
} |
||||
|
const panelHeight = this.breakpointObserver.isMatched('min-height: 350px') ? 250 : 150; |
||||
|
const panelWidth = 300; |
||||
|
const position = this.overlay.position(); |
||||
|
const config = new OverlayConfig({ |
||||
|
panelClass: 'tb-aliases-entity-select-panel', |
||||
|
backdropClass: 'cdk-overlay-transparent-backdrop', |
||||
|
hasBackdrop: true, |
||||
|
}); |
||||
|
const el = this.aliasEntitySelectPanelOrigin.elementRef.nativeElement; |
||||
|
const offset = el.getBoundingClientRect(); |
||||
|
const scrollTop = this.window.pageYOffset || this.document.documentElement.scrollTop || this.document.body.scrollTop || 0; |
||||
|
const scrollLeft = this.window.pageXOffset || this.document.documentElement.scrollLeft || this.document.body.scrollLeft || 0; |
||||
|
const bottomY = offset.bottom - scrollTop; |
||||
|
const leftX = offset.left - scrollLeft; |
||||
|
let originX; |
||||
|
let originY; |
||||
|
let overlayX; |
||||
|
let overlayY; |
||||
|
const wHeight = this.document.documentElement.clientHeight; |
||||
|
const wWidth = this.document.documentElement.clientWidth; |
||||
|
if (bottomY + panelHeight > wHeight) { |
||||
|
originY = 'top'; |
||||
|
overlayY = 'bottom'; |
||||
|
} else { |
||||
|
originY = 'bottom'; |
||||
|
overlayY = 'top'; |
||||
|
} |
||||
|
if (leftX + panelWidth > wWidth) { |
||||
|
originX = 'end'; |
||||
|
overlayX = 'end'; |
||||
|
} else { |
||||
|
originX = 'start'; |
||||
|
overlayX = 'start'; |
||||
|
} |
||||
|
const connectedPosition: ConnectedPosition = { |
||||
|
originX, |
||||
|
originY, |
||||
|
overlayX, |
||||
|
overlayY |
||||
|
}; |
||||
|
config.positionStrategy = position.flexibleConnectedTo(this.aliasEntitySelectPanelOrigin.elementRef) |
||||
|
.withPositions([connectedPosition]); |
||||
|
const overlayRef = this.overlay.create(config); |
||||
|
overlayRef.backdropClick().subscribe(() => { |
||||
|
overlayRef.dispose(); |
||||
|
}); |
||||
|
|
||||
|
const injector = this._createAliasesEntitySelectPanelInjector( |
||||
|
overlayRef, |
||||
|
{ |
||||
|
aliasController: this.aliasController |
||||
|
} |
||||
|
); |
||||
|
overlayRef.attach(new ComponentPortal(AliasesEntitySelectPanelComponent, this.viewContainerRef, injector)); |
||||
|
} |
||||
|
|
||||
|
private _createAliasesEntitySelectPanelInjector(overlayRef: OverlayRef, data: AliasesEntitySelectPanelData): PortalInjector { |
||||
|
const injectionTokens = new WeakMap<any, any>([ |
||||
|
[ALIASES_ENTITY_SELECT_PANEL_DATA, data], |
||||
|
[OverlayRef, overlayRef] |
||||
|
]); |
||||
|
return new PortalInjector(this.viewContainerRef.injector, injectionTokens); |
||||
|
} |
||||
|
|
||||
|
private updateDisplayValue() { |
||||
|
let displayValue; |
||||
|
let singleValue = true; |
||||
|
let currentAliasId; |
||||
|
const entityAliases = this.aliasController.getEntityAliases(); |
||||
|
for (const aliasId of Object.keys(entityAliases)) { |
||||
|
const entityAlias = entityAliases[aliasId]; |
||||
|
if (!entityAlias.filter.resolveMultiple) { |
||||
|
const resolvedAlias = this.aliasController.getInstantAliasInfo(aliasId); |
||||
|
if (resolvedAlias && resolvedAlias.currentEntity) { |
||||
|
if (!currentAliasId) { |
||||
|
currentAliasId = aliasId; |
||||
|
} else { |
||||
|
singleValue = false; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
if (singleValue && currentAliasId) { |
||||
|
const aliasInfo = this.aliasController.getInstantAliasInfo(currentAliasId); |
||||
|
displayValue = aliasInfo.currentEntity.name; |
||||
|
} else { |
||||
|
displayValue = this.translate.instant('entity.entities'); |
||||
|
} |
||||
|
this.displayValue = displayValue; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,157 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2019 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<div class="tb-dashboard-page mat-content" style="padding-top: 150px;" |
||||
|
fxFlex tb-fullscreen [fullscreen]="widgetEditMode || iframeMode || forceFullscreen || isFullscreen"> |
||||
|
<section class="tb-dashboard-toolbar" |
||||
|
[ngClass]="{ 'tb-dashboard-toolbar-opened': toolbarOpened, 'tb-dashboard-toolbar-closed': !toolbarOpened }"> |
||||
|
<tb-dashboard-toolbar [fxShow]="!widgetEditMode" [forceFullscreen]="forceFullscreen" |
||||
|
[toolbarOpened]="toolbarOpened" (triggerClick)="openToolbar()"> |
||||
|
<div class="tb-dashboard-action-panels" fxLayout="column-reverse" fxLayout.gt-sm="row-reverse" |
||||
|
fxLayoutAlign="center stretch" fxLayoutAlign.gt-sm="space-between center"> |
||||
|
<div class="tb-dashboard-action-panel" fxFlex.md="30" fxLayout="row-reverse" |
||||
|
fxLayoutAlign.gt-sm="start center" fxLayoutAlign="space-between center" fxLayoutGap="12px"> |
||||
|
<button [fxShow]="showCloseToolbar()" mat-button mat-icon-button class="close-action" |
||||
|
matTooltip="{{ 'dashboard.close-toolbar' | translate }}" |
||||
|
matTooltipPosition="below" |
||||
|
(click)="closeToolbar()"> |
||||
|
<mat-icon>arrow_forward</mat-icon> |
||||
|
</button> |
||||
|
<tb-user-menu *ngIf="isPublicUser() && forceFullscreen" fxHide.xs fxHide.sm displayUserInfo="true"> |
||||
|
</tb-user-menu> |
||||
|
<button [fxShow]="showRightLayoutSwitch()" mat-button mat-icon-button |
||||
|
matTooltip="{{ (isRightLayoutOpened ? 'dashboard.hide-details' : 'dashboard.show-details') | translate }}" |
||||
|
matTooltipPosition="below" |
||||
|
(click)="toggleLayouts()"> |
||||
|
<mat-icon>{{isRightLayoutOpened ? 'arrow_back' : 'menu'}}</mat-icon> |
||||
|
</button> |
||||
|
<button [fxShow]="!hideFullscreenButton()" mat-button mat-icon-button |
||||
|
matTooltip="{{(isFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}" |
||||
|
matTooltipPosition="below" |
||||
|
(click)="isFullscreen = !isFullscreen"> |
||||
|
<mat-icon>{{ isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> |
||||
|
</button> |
||||
|
<button [fxShow]="isEdit || displayExport()" mat-button mat-icon-button |
||||
|
matTooltip="{{'dashboard.export' | translate}}" |
||||
|
matTooltipPosition="below" |
||||
|
(click)="exportDashboard($event)"> |
||||
|
<mat-icon>file_download</mat-icon> |
||||
|
</button> |
||||
|
<tb-timewindow [fxShow]="isEdit || displayDashboardTimewindow()" |
||||
|
isToolbar="true" |
||||
|
direction="left" |
||||
|
tooltipPosition="below" |
||||
|
aggregation="true" |
||||
|
[(ngModel)]="dashboardCtx.dashboardTimewindow"> |
||||
|
</tb-timewindow> |
||||
|
<tb-aliases-entity-select [fxShow]="!isEdit && displayEntitiesSelect()" |
||||
|
tooltipPosition="below" |
||||
|
[aliasController]="dashboardCtx.aliasController"> |
||||
|
</tb-aliases-entity-select> |
||||
|
<button [fxShow]="isEdit" mat-button mat-icon-button |
||||
|
matTooltip="{{ 'entity.aliases' | translate }}" |
||||
|
matTooltipPosition="below" |
||||
|
(click)="openEntityAliases($event)"> |
||||
|
<mat-icon>devices_other</mat-icon> |
||||
|
</button> |
||||
|
<button [fxShow]="isEdit" mat-button mat-icon-button |
||||
|
matTooltip="{{ 'dashboard.settings' | translate }}" |
||||
|
matTooltipPosition="below" |
||||
|
(click)="openDashboardSettings($event)"> |
||||
|
<mat-icon>settings</mat-icon> |
||||
|
</button> |
||||
|
<tb-dashboard-select [fxShow]="!isEdit && !widgetEditMode && displayDashboardsSelect()" |
||||
|
[(ngModel)]="currentDashboardId" |
||||
|
(ngModelChange)="currentDashboardIdChanged(currentDashboardId)" |
||||
|
[customerId]="currentCustomerId" |
||||
|
[dashboardsScope]="currentDashboardScope"> |
||||
|
</tb-dashboard-select> |
||||
|
</div> |
||||
|
<div class="tb-dashboard-action-panel" fxFlex.md="70" fxLayout="row-reverse" |
||||
|
fxLayoutAlign.gt-sm="end center" fxLayoutAlign="space-between center" fxLayoutGap="12px"> |
||||
|
<tb-user-menu *ngIf="!isPublicUser() && forceFullscreen" fxHide.gt-sm displayUserInfo="true"> |
||||
|
</tb-user-menu> |
||||
|
<!-- TODO --> |
||||
|
</div> |
||||
|
</div> |
||||
|
</tb-dashboard-toolbar> |
||||
|
</section> |
||||
|
<section class="tb-dashboard-container tb-absolute-fill" |
||||
|
[ngClass]="{ 'is-fullscreen': forceFullscreen, |
||||
|
'tb-dashboard-toolbar-opened': toolbarOpened, |
||||
|
'tb-dashboard-toolbar-closed': !toolbarOpened }"> |
||||
|
<section *ngIf="!widgetEditMode" class="tb-dashboard-title" fxLayout="row" fxLayoutAlign="center center" |
||||
|
[ngStyle]="{'color': dashboard.configuration.settings.titleColor}"> |
||||
|
<h3 [fxShow]="!isEdit && displayTitle()">{{ dashboard.title }}</h3> |
||||
|
<mat-form-field [fxShow]="isEdit" class="mat-block" style="height: 30px;"> |
||||
|
<mat-label translate [ngStyle]="{'color': dashboard.configuration.settings.titleColor}">dashboard.title</mat-label> |
||||
|
<input matInput class="tb-dashboard-title" |
||||
|
[ngStyle]="{'color': dashboard.configuration.settings.titleColor}" |
||||
|
required name="title" [(ngModel)]="dashboard.title"> |
||||
|
</mat-form-field> |
||||
|
</section> |
||||
|
<div class="tb-absolute-fill tb-dashboard-layouts" fxLayout="{{forceDashboardMobileMode ? 'column' : 'row'}}" |
||||
|
[ngClass]="{ 'tb-padded' : !widgetEditMode && (isEdit || displayTitle()), 'tb-shrinked' : isEditingWidget }"> |
||||
|
<div [fxShow]="layouts.main.show" |
||||
|
id="tb-main-layout" |
||||
|
[ngStyle]="{width: mainLayoutWidth(), |
||||
|
height: mainLayoutHeight()}"> |
||||
|
TODO: MAIN LAYOUT tb-dashboard-layout |
||||
|
</div> |
||||
|
<mat-sidenav-container *ngIf="layouts.right.show" |
||||
|
id="tb-right-layout"> |
||||
|
<mat-sidenav |
||||
|
[ngStyle]="{minWidth: rightLayoutWidth(), |
||||
|
maxWidth: rightLayoutWidth(), |
||||
|
height: rightLayoutHeight(), |
||||
|
zIndex: 25}" |
||||
|
disableClose="true" |
||||
|
position="end" |
||||
|
[mode]="isMobile ? 'over' : 'side'" |
||||
|
[(opened)]="rightLayoutOpened"> |
||||
|
TODO: RIGHT LAYOUT tb-dashboard-layout |
||||
|
</mat-sidenav> |
||||
|
</mat-sidenav-container> |
||||
|
</div> |
||||
|
<!--tb-details-sidenav TODO --> |
||||
|
<!--tb-details-sidenav TODO --> |
||||
|
<section fxLayout="row" class="layout-wrap tb-footer-buttons" fxLayoutAlign="start end"> |
||||
|
<!--md-fab-speed-dial TODO --> |
||||
|
<button *ngIf="(isTenantAdmin() || isSystemAdmin()) && !forceFullscreen" |
||||
|
mat-fab color="accent" class="tb-btn-footer" |
||||
|
[ngClass]="{'tb-hide': !isEdit || isAddingWidget}" |
||||
|
[disabled]="isLoading$ | async" |
||||
|
(click)="saveDashboard()" |
||||
|
matTooltip="{{ 'action.apply-changes' | translate }}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon>done</mat-icon> |
||||
|
</button> |
||||
|
<button *ngIf="(isTenantAdmin() || isSystemAdmin()) && !forceFullscreen" |
||||
|
mat-fab color="accent" class="tb-btn-footer" |
||||
|
[ngClass]="{'tb-hide': isAddingWidget || (isLoading$ | async)}" |
||||
|
[disabled]="isLoading$ | async" |
||||
|
(click)="toggleDashboardEditMode()" |
||||
|
matTooltip="{{ (isEdit ? 'action.decline-changes': 'action.enter-edit-mode') | translate }}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon>{{ isEdit ? 'close' : 'edit' }}</mat-icon> |
||||
|
</button> |
||||
|
</section> |
||||
|
</section> |
||||
|
<section class="tb-powered-by-footer" [ngStyle]="{'color': dashboard.configuration.settings.titleColor}"> |
||||
|
<span>Powered by <a href="https://thingsboard.io" target="_blank">Thingsboard v.{{ thingsboardVersion }}</a></span> |
||||
|
</section> |
||||
|
</div> |
||||
@ -0,0 +1,137 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2019 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
|
||||
|
@import "../../../../../scss/constants"; |
||||
|
|
||||
|
$toolbar-height: 50px !default; |
||||
|
$fullscreen-toolbar-height: 64px !default; |
||||
|
$mobile-toolbar-height: 84px !default; |
||||
|
|
||||
|
tb-dashboard-page { |
||||
|
display: flex; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
div.tb-dashboard-page { |
||||
|
&.mat-content { |
||||
|
background-color: #eee; |
||||
|
} |
||||
|
section.tb-dashboard-title { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 20px; |
||||
|
mat-form-field { |
||||
|
.mat-form-field-infix { |
||||
|
width: 100%; |
||||
|
} |
||||
|
} |
||||
|
input.tb-dashboard-title { |
||||
|
height: 38px; |
||||
|
font-size: 2rem; |
||||
|
font-weight: 500; |
||||
|
letter-spacing: .005em; |
||||
|
} |
||||
|
} |
||||
|
div.tb-padded { |
||||
|
top: 60px; |
||||
|
} |
||||
|
|
||||
|
section.tb-padded { |
||||
|
top: 60px; |
||||
|
} |
||||
|
|
||||
|
div.tb-shrinked { |
||||
|
width: 40%; |
||||
|
} |
||||
|
|
||||
|
section.tb-dashboard-toolbar { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
z-index: 13; |
||||
|
pointer-events: none; |
||||
|
|
||||
|
&.tb-dashboard-toolbar-opened { |
||||
|
right: 0; |
||||
|
// transition: right .3s cubic-bezier(.55, 0, .55, .2); |
||||
|
} |
||||
|
|
||||
|
&.tb-dashboard-toolbar-closed { |
||||
|
right: 18px; |
||||
|
transition: right .3s cubic-bezier(.55, 0, .55, .2) .2s; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.tb-dashboard-container { |
||||
|
&.tb-dashboard-toolbar-opened { |
||||
|
&.is-fullscreen { |
||||
|
margin-top: $mobile-toolbar-height; |
||||
|
|
||||
|
@media #{$mat-gt-sm} { |
||||
|
margin-top: $fullscreen-toolbar-height; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&:not(.is-fullscreen) { |
||||
|
margin-top: $mobile-toolbar-height; |
||||
|
|
||||
|
@media #{$mat-gt-sm} { |
||||
|
margin-top: $toolbar-height; |
||||
|
} |
||||
|
|
||||
|
transition: margin-top .3s cubic-bezier(.55, 0, .55, .2); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&.tb-dashboard-toolbar-closed { |
||||
|
margin-top: 0; |
||||
|
|
||||
|
transition: margin-top .3s cubic-bezier(.55, 0, .55, .2) .2s; |
||||
|
} |
||||
|
|
||||
|
.tb-dashboard-layouts { |
||||
|
/*md-backdrop { |
||||
|
z-index: 1; |
||||
|
}*/ |
||||
|
#tb-right-layout { |
||||
|
mat-sidenav { |
||||
|
z-index: 1; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
section.tb-powered-by-footer { |
||||
|
position: absolute; |
||||
|
right: 25px; |
||||
|
bottom: 5px; |
||||
|
z-index: 30; |
||||
|
pointer-events: none; |
||||
|
|
||||
|
span { |
||||
|
font-size: 12px; |
||||
|
|
||||
|
a { |
||||
|
font-weight: 700; |
||||
|
text-decoration: none; |
||||
|
pointer-events: all; |
||||
|
border: none; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,438 @@ |
|||||
|
///
|
||||
|
/// 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, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; |
||||
|
import { PageComponent } from '@shared/components/page.component'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { ActivatedRoute, Router } from '@angular/router'; |
||||
|
import { UtilsService } from '@core/services/utils.service'; |
||||
|
import { AuthService } from '@core/auth/auth.service'; |
||||
|
import { Dashboard, DashboardConfiguration, WidgetLayout } from '@app/shared/models/dashboard.models'; |
||||
|
import { WINDOW } from '@core/services/window.service'; |
||||
|
import { WindowMessage } from '@shared/models/window-message.model'; |
||||
|
import { deepClone, isDefined } from '@app/core/utils'; |
||||
|
import { |
||||
|
DashboardContext, |
||||
|
DashboardPageLayoutContext, |
||||
|
DashboardPageLayouts, |
||||
|
DashboardPageScope |
||||
|
} from './dashboard-page.models'; |
||||
|
import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; |
||||
|
import { MediaBreakpoints } from '@shared/models/constants'; |
||||
|
import { AuthUser } from '@shared/models/user.model'; |
||||
|
import { getCurrentAuthUser } from '@core/auth/auth.selectors'; |
||||
|
import { Widget } from '@app/shared/models/widget.models'; |
||||
|
import { environment as env } from '@env/environment'; |
||||
|
import { Authority } from '@shared/models/authority.enum'; |
||||
|
import { DialogService } from '@core/services/dialog.service'; |
||||
|
import { EntityService } from '@core/http/entity.service'; |
||||
|
import { AliasController } from '@core/api/alias-controller'; |
||||
|
import { Subscription } from 'rxjs'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-dashboard-page', |
||||
|
templateUrl: './dashboard-page.component.html', |
||||
|
styleUrls: ['./dashboard-page.component.scss'], |
||||
|
encapsulation: ViewEncapsulation.None |
||||
|
}) |
||||
|
export class DashboardPageComponent extends PageComponent implements OnDestroy { |
||||
|
|
||||
|
authUser: AuthUser = getCurrentAuthUser(this.store); |
||||
|
|
||||
|
dashboard: Dashboard; |
||||
|
dashboardConfiguration: DashboardConfiguration; |
||||
|
|
||||
|
prevDashboard: Dashboard; |
||||
|
|
||||
|
iframeMode = this.utils.iframeMode; |
||||
|
widgetEditMode: boolean; |
||||
|
singlePageMode: boolean; |
||||
|
forceFullscreen = this.authService.forceFullscreen; |
||||
|
|
||||
|
isFullscreen = false; |
||||
|
isEdit = false; |
||||
|
isEditingWidget = false; |
||||
|
isMobile = !this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']); |
||||
|
forceDashboardMobileMode = false; |
||||
|
isAddingWidget = false; |
||||
|
|
||||
|
isToolbarOpened = false; |
||||
|
isRightLayoutOpened = false; |
||||
|
|
||||
|
editingWidget: Widget = null; |
||||
|
editingWidgetLayout: WidgetLayout = null; |
||||
|
editingWidgetOriginal: Widget = null; |
||||
|
editingWidgetLayoutOriginal: WidgetLayout = null; |
||||
|
editingWidgetSubtitle: string = null; |
||||
|
editingLayoutCtx: DashboardPageLayoutContext = null; |
||||
|
|
||||
|
thingsboardVersion: string = env.tbVersion; |
||||
|
|
||||
|
currentDashboardId: string; |
||||
|
currentCustomerId: string; |
||||
|
currentDashboardScope: DashboardPageScope; |
||||
|
|
||||
|
layouts: DashboardPageLayouts = { |
||||
|
main: { |
||||
|
show: false, |
||||
|
layoutCtx: { |
||||
|
id: 'main', |
||||
|
widgets: [], |
||||
|
widgetLayouts: {}, |
||||
|
gridSettings: {}, |
||||
|
ignoreLoading: false |
||||
|
} |
||||
|
}, |
||||
|
right: { |
||||
|
show: false, |
||||
|
layoutCtx: { |
||||
|
id: 'right', |
||||
|
widgets: [], |
||||
|
widgetLayouts: {}, |
||||
|
gridSettings: {}, |
||||
|
ignoreLoading: false |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
dashboardCtx: DashboardContext = { |
||||
|
dashboard: null, |
||||
|
dashboardTimewindow: null, |
||||
|
state: null, |
||||
|
stateController: { |
||||
|
openRightLayout: this.openRightLayout.bind(this) |
||||
|
}, |
||||
|
aliasController: null |
||||
|
}; |
||||
|
|
||||
|
private rxSubscriptions = new Array<Subscription>(); |
||||
|
|
||||
|
get toolbarOpened(): boolean { |
||||
|
return !this.widgetEditMode && |
||||
|
(this.toolbarAlwaysOpen() || this.isToolbarOpened || this.isEdit || this.showRightLayoutSwitch()); |
||||
|
} |
||||
|
set toolbarOpened(toolbarOpened: boolean) { |
||||
|
} |
||||
|
|
||||
|
get rightLayoutOpened(): boolean { |
||||
|
return !this.isMobile || this.isRightLayoutOpened; |
||||
|
} |
||||
|
set rightLayoutOpened(rightLayoutOpened: boolean) { |
||||
|
} |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
@Inject(WINDOW) private window: Window, |
||||
|
private breakpointObserver: BreakpointObserver, |
||||
|
private route: ActivatedRoute, |
||||
|
private router: Router, |
||||
|
private utils: UtilsService, |
||||
|
private authService: AuthService, |
||||
|
private entityService: EntityService, |
||||
|
private dialogService: DialogService) { |
||||
|
super(store); |
||||
|
|
||||
|
this.rxSubscriptions.push(this.route.data.subscribe( |
||||
|
(data) => { |
||||
|
this.init(data); |
||||
|
} |
||||
|
)); |
||||
|
|
||||
|
this.rxSubscriptions.push(this.breakpointObserver |
||||
|
.observe(MediaBreakpoints['gt-sm']) |
||||
|
.subscribe((state: BreakpointState) => { |
||||
|
this.isMobile = !state.matches; |
||||
|
} |
||||
|
)); |
||||
|
} |
||||
|
|
||||
|
private init(data: any) { |
||||
|
|
||||
|
this.reset(); |
||||
|
|
||||
|
this.currentDashboardId = this.route.snapshot.params.dashboardId; |
||||
|
|
||||
|
if (this.route.snapshot.params.customerId) { |
||||
|
this.currentCustomerId = this.route.snapshot.params.customerId; |
||||
|
this.currentDashboardScope = 'customer'; |
||||
|
} else { |
||||
|
this.currentDashboardScope = this.authUser.authority === Authority.TENANT_ADMIN ? 'tenant' : 'customer'; |
||||
|
this.currentCustomerId = this.authUser.customerId; |
||||
|
} |
||||
|
|
||||
|
this.dashboard = data.dashboard; |
||||
|
this.dashboardConfiguration = this.dashboard.configuration; |
||||
|
this.widgetEditMode = data.widgetEditMode; |
||||
|
this.singlePageMode = data.singlePageMode; |
||||
|
|
||||
|
this.dashboardCtx.dashboard = this.dashboard; |
||||
|
this.dashboardCtx.dashboardTimewindow = this.dashboardConfiguration.timewindow; |
||||
|
this.dashboardCtx.aliasController = new AliasController(this.utils, |
||||
|
this.entityService, |
||||
|
this.dashboardCtx.stateController, |
||||
|
this.dashboardConfiguration.entityAliases); |
||||
|
|
||||
|
if (this.widgetEditMode) { |
||||
|
const message: WindowMessage = { |
||||
|
type: 'widgetEditModeInited' |
||||
|
}; |
||||
|
this.window.parent.postMessage(JSON.stringify(message), '*'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private reset() { |
||||
|
this.dashboard = null; |
||||
|
this.dashboardConfiguration = null; |
||||
|
this.prevDashboard = null; |
||||
|
|
||||
|
this.widgetEditMode = false; |
||||
|
this.singlePageMode = false; |
||||
|
|
||||
|
this.isFullscreen = false; |
||||
|
this.isEdit = false; |
||||
|
this.isEditingWidget = false; |
||||
|
this.forceDashboardMobileMode = false; |
||||
|
this.isAddingWidget = false; |
||||
|
|
||||
|
this.isToolbarOpened = false; |
||||
|
this.isRightLayoutOpened = false; |
||||
|
|
||||
|
this.editingWidget = null; |
||||
|
this.editingWidgetLayout = null; |
||||
|
this.editingWidgetOriginal = null; |
||||
|
this.editingWidgetLayoutOriginal = null; |
||||
|
this.editingWidgetSubtitle = null; |
||||
|
this.editingLayoutCtx = null; |
||||
|
|
||||
|
this.currentDashboardId = null; |
||||
|
this.currentCustomerId = null; |
||||
|
this.currentDashboardScope = null; |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy(): void { |
||||
|
this.rxSubscriptions.forEach((subscription) => { |
||||
|
subscription.unsubscribe(); |
||||
|
}); |
||||
|
this.rxSubscriptions.length = 0; |
||||
|
} |
||||
|
|
||||
|
public openToolbar() { |
||||
|
this.isToolbarOpened = true; |
||||
|
} |
||||
|
|
||||
|
public closeToolbar() { |
||||
|
this.isToolbarOpened = false; |
||||
|
} |
||||
|
|
||||
|
public showCloseToolbar() { |
||||
|
return !this.toolbarAlwaysOpen() && !this.isEdit && !this.showRightLayoutSwitch(); |
||||
|
} |
||||
|
|
||||
|
public hideFullscreenButton(): boolean { |
||||
|
return this.widgetEditMode || this.iframeMode || this.forceFullscreen || this.singlePageMode; |
||||
|
} |
||||
|
|
||||
|
public toolbarAlwaysOpen(): boolean { |
||||
|
if (this.dashboard.configuration.settings && |
||||
|
isDefined(this.dashboard.configuration.settings.toolbarAlwaysOpen)) { |
||||
|
return this.dashboard.configuration.settings.toolbarAlwaysOpen; |
||||
|
} else { |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public displayTitle(): boolean { |
||||
|
if (this.dashboard.configuration.settings && |
||||
|
isDefined(this.dashboard.configuration.settings.showTitle)) { |
||||
|
return this.dashboard.configuration.settings.showTitle; |
||||
|
} else { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public displayExport(): boolean { |
||||
|
if (this.dashboard.configuration.settings && |
||||
|
isDefined(this.dashboard.configuration.settings.showDashboardExport)) { |
||||
|
return this.dashboard.configuration.settings.showDashboardExport; |
||||
|
} else { |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public displayDashboardTimewindow(): boolean { |
||||
|
if (this.dashboard.configuration.settings && |
||||
|
isDefined(this.dashboard.configuration.settings.showDashboardTimewindow)) { |
||||
|
return this.dashboard.configuration.settings.showDashboardTimewindow; |
||||
|
} else { |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public displayDashboardsSelect(): boolean { |
||||
|
if (this.dashboard.configuration.settings && |
||||
|
isDefined(this.dashboard.configuration.settings.showDashboardsSelect)) { |
||||
|
return this.dashboard.configuration.settings.showDashboardsSelect; |
||||
|
} else { |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public displayEntitiesSelect(): boolean { |
||||
|
if (this.dashboard.configuration.settings && |
||||
|
isDefined(this.dashboard.configuration.settings.showEntitiesSelect)) { |
||||
|
return this.dashboard.configuration.settings.showEntitiesSelect; |
||||
|
} else { |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public showRightLayoutSwitch(): boolean { |
||||
|
return this.isMobile && this.layouts.right.show; |
||||
|
} |
||||
|
|
||||
|
public toggleLayouts() { |
||||
|
this.isRightLayoutOpened = !this.isRightLayoutOpened; |
||||
|
} |
||||
|
|
||||
|
public openRightLayout() { |
||||
|
this.isRightLayoutOpened = true; |
||||
|
} |
||||
|
|
||||
|
public mainLayoutWidth(): string { |
||||
|
if (this.isEditingWidget && this.editingLayoutCtx.id === 'main') { |
||||
|
return '100%'; |
||||
|
} else { |
||||
|
return this.layouts.right.show && !this.isMobile ? '50%' : '100%'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public mainLayoutHeight(): string { |
||||
|
if (!this.isEditingWidget || this.editingLayoutCtx.id === 'main') { |
||||
|
return '100%'; |
||||
|
} else { |
||||
|
return '0px'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public rightLayoutWidth(): string { |
||||
|
if (this.isEditingWidget && this.editingLayoutCtx.id === 'right') { |
||||
|
return '100%'; |
||||
|
} else { |
||||
|
return this.isMobile ? '100%' : '50%'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public rightLayoutHeight(): string { |
||||
|
if (!this.isEditingWidget || this.editingLayoutCtx.id === 'right') { |
||||
|
return '100%'; |
||||
|
} else { |
||||
|
return '0px'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public isPublicUser(): boolean { |
||||
|
return this.authUser.isPublic; |
||||
|
} |
||||
|
|
||||
|
public isTenantAdmin(): boolean { |
||||
|
return this.authUser.authority === Authority.TENANT_ADMIN; |
||||
|
} |
||||
|
|
||||
|
public isSystemAdmin(): boolean { |
||||
|
return this.authUser.authority === Authority.SYS_ADMIN; |
||||
|
} |
||||
|
|
||||
|
public exportDashboard($event: Event) { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
// TODO:
|
||||
|
this.dialogService.todo(); |
||||
|
} |
||||
|
|
||||
|
public openEntityAliases($event: Event) { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
// TODO:
|
||||
|
this.dialogService.todo(); |
||||
|
} |
||||
|
|
||||
|
public openDashboardSettings($event: Event) { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
// TODO:
|
||||
|
this.dialogService.todo(); |
||||
|
} |
||||
|
|
||||
|
public currentDashboardIdChanged(dashboardId: string) { |
||||
|
if (!this.widgetEditMode) { |
||||
|
if (this.currentDashboardScope === 'customer' && this.authUser.authority === Authority.TENANT_ADMIN) { |
||||
|
this.router.navigateByUrl(`customers/${this.currentCustomerId}/dashboards/${dashboardId}`); |
||||
|
} else { |
||||
|
if (this.singlePageMode) { |
||||
|
this.router.navigateByUrl(`dashboard/${dashboardId}`); |
||||
|
} else { |
||||
|
this.router.navigateByUrl(`dashboards/${dashboardId}`); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public toggleDashboardEditMode() { |
||||
|
this.setEditMode(!this.isEdit, true); |
||||
|
} |
||||
|
|
||||
|
private setEditMode(isEdit: boolean, revert: boolean) { |
||||
|
this.isEdit = isEdit; |
||||
|
if (this.isEdit) { |
||||
|
// TODO:
|
||||
|
// this.dashboardCtx.stateController.preserveState();
|
||||
|
this.prevDashboard = deepClone(this.dashboard); |
||||
|
} else { |
||||
|
if (this.widgetEditMode) { |
||||
|
if (revert) { |
||||
|
this.dashboard = this.prevDashboard; |
||||
|
} |
||||
|
} else { |
||||
|
this.resetHighlight(); |
||||
|
if (revert) { |
||||
|
this.dashboard = this.prevDashboard; |
||||
|
this.dashboardConfiguration = this.dashboard.configuration; |
||||
|
this.dashboardCtx.dashboardTimewindow = this.dashboardConfiguration.timewindow; |
||||
|
this.entityAliasesUpdated(); |
||||
|
} else { |
||||
|
this.dashboard.configuration.timewindow = this.dashboardCtx.dashboardTimewindow; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private resetHighlight() { |
||||
|
for (const l of Object.keys(this.layouts)) { |
||||
|
if (this.layouts[l].layoutCtx) { |
||||
|
if (this.layouts[l].layoutCtx.ctrl) { |
||||
|
this.layouts[l].layoutCtx.ctrl.resetHighlight(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private entityAliasesUpdated() { |
||||
|
this.dashboardCtx.aliasController.updateEntityAliases(this.dashboard.configuration.entityAliases); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2019 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { DashboardLayoutId, GridSettings, WidgetLayout, Dashboard } from '@app/shared/models/dashboard.models'; |
||||
|
import { Widget } from '@app/shared/models/widget.models'; |
||||
|
import { Timewindow } from '@shared/models/time/time.models'; |
||||
|
import { IAliasController, IStateController } from '@core/api/widget-api.models'; |
||||
|
|
||||
|
export declare type DashboardPageScope = 'tenant' | 'customer'; |
||||
|
|
||||
|
export interface DashboardContext { |
||||
|
state: string; |
||||
|
dashboard: Dashboard; |
||||
|
dashboardTimewindow: Timewindow; |
||||
|
aliasController: IAliasController; |
||||
|
stateController: IStateController; |
||||
|
} |
||||
|
|
||||
|
export interface DashboardPageLayoutContext { |
||||
|
id: DashboardLayoutId; |
||||
|
widgets: Array<Widget>; |
||||
|
widgetLayouts: {[id: string]: WidgetLayout}; |
||||
|
gridSettings: GridSettings; |
||||
|
ignoreLoading: boolean; |
||||
|
} |
||||
|
|
||||
|
export interface DashboardPageLayout { |
||||
|
show: boolean; |
||||
|
layoutCtx: DashboardPageLayoutContext; |
||||
|
} |
||||
|
|
||||
|
export interface DashboardPageLayouts { |
||||
|
main: DashboardPageLayout; |
||||
|
right: DashboardPageLayout; |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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-fab-toolbar [isOpen]="toolbarOpened" |
||||
|
color="primary" |
||||
|
direction="left" |
||||
|
[ngClass]="{'is-fullscreen': forceFullscreen, 'mat-elevation-z1': forceFullscreen && toolbarOpened }"> |
||||
|
<mat-fab-trigger> |
||||
|
<button mat-button mat-icon-button mat-fab color="primary" |
||||
|
[matTooltip]="!toolbarOpened ? ('dashboard.open-toolbar' | translate) : ''" |
||||
|
matTooltipPosition="below" |
||||
|
(click)="onTriggerClick()"> |
||||
|
<mat-icon>more_horiz</mat-icon> |
||||
|
</button> |
||||
|
</mat-fab-trigger> |
||||
|
<mat-toolbar color="primary"> |
||||
|
<mat-fab-actions class="mat-toolbar-tools"> |
||||
|
<ng-content></ng-content> |
||||
|
</mat-fab-actions> |
||||
|
</mat-toolbar> |
||||
|
</mat-fab-toolbar> |
||||
@ -0,0 +1,185 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2019 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
@import "../../../../../scss/constants"; |
||||
|
|
||||
|
$toolbar-height: 50px !default; |
||||
|
$fullscreen-toolbar-height: 64px !default; |
||||
|
$mobile-toolbar-height: 80px !default; |
||||
|
$half-mobile-toolbar-height: 40px !default; |
||||
|
$mobile-toolbar-height-total: 84px !default; |
||||
|
|
||||
|
tb-dashboard-toolbar { |
||||
|
mat-fab-toolbar { |
||||
|
mat-fab-trigger { |
||||
|
.mat-button { |
||||
|
&.mat-fab { |
||||
|
width: 36px; |
||||
|
height: 36px; |
||||
|
margin: 4px 0 0 4px; |
||||
|
line-height: 36px; |
||||
|
.mat-button-wrapper { |
||||
|
width: 36px; |
||||
|
height: 36px; |
||||
|
padding: 0 6px; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
mat-icon { |
||||
|
display: block; |
||||
|
position: absolute; |
||||
|
top: 25%; |
||||
|
width: 18px; |
||||
|
min-width: 18px; |
||||
|
height: 18px; |
||||
|
min-height: 18px; |
||||
|
margin: 0; |
||||
|
line-height: 18px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&.is-fullscreen { |
||||
|
|
||||
|
.mat-fab-toolbar-wrapper { |
||||
|
height: $mobile-toolbar-height-total; |
||||
|
|
||||
|
@media #{$mat-gt-sm} { |
||||
|
height: $fullscreen-toolbar-height; |
||||
|
} |
||||
|
|
||||
|
mat-toolbar { |
||||
|
height: $mobile-toolbar-height; |
||||
|
min-height: $mobile-toolbar-height; |
||||
|
.mat-toolbar-tools { |
||||
|
height: $mobile-toolbar-height; |
||||
|
min-height: $mobile-toolbar-height; |
||||
|
} |
||||
|
@media #{$mat-gt-sm} { |
||||
|
height: $fullscreen-toolbar-height; |
||||
|
min-height: $fullscreen-toolbar-height; |
||||
|
.mat-toolbar-tools { |
||||
|
height: $fullscreen-toolbar-height; |
||||
|
min-height: $fullscreen-toolbar-height; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.mat-fab-toolbar-wrapper { |
||||
|
height: $mobile-toolbar-height-total; |
||||
|
|
||||
|
@media #{$mat-gt-sm} { |
||||
|
height: $toolbar-height; |
||||
|
} |
||||
|
|
||||
|
mat-toolbar { |
||||
|
height: $mobile-toolbar-height; |
||||
|
min-height: $mobile-toolbar-height; |
||||
|
.mat-toolbar-tools { |
||||
|
height: $mobile-toolbar-height; |
||||
|
min-height: $mobile-toolbar-height; |
||||
|
} |
||||
|
|
||||
|
@media #{$mat-gt-sm} { |
||||
|
height: $toolbar-height; |
||||
|
min-height: $toolbar-height; |
||||
|
.mat-toolbar-tools { |
||||
|
height: $toolbar-height; |
||||
|
min-height: $toolbar-height; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
mat-fab-actions { |
||||
|
margin-top: 0; |
||||
|
font-size: 16px; |
||||
|
|
||||
|
@media #{$mat-lt-md} { |
||||
|
height: $mobile-toolbar-height; |
||||
|
max-height: $mobile-toolbar-height; |
||||
|
} |
||||
|
|
||||
|
.close-action { |
||||
|
margin-right: -18px; |
||||
|
} |
||||
|
|
||||
|
.mat-fab-action-item { |
||||
|
width: 100%; |
||||
|
height: $mobile-toolbar-height; |
||||
|
|
||||
|
@media #{$mat-gt-sm} { |
||||
|
height: 46px; |
||||
|
} |
||||
|
|
||||
|
.tb-dashboard-action-panels { |
||||
|
height: $mobile-toolbar-height; |
||||
|
|
||||
|
@media #{$mat-gt-sm} { |
||||
|
height: 46px; |
||||
|
} |
||||
|
|
||||
|
.tb-dashboard-action-panel { |
||||
|
min-width: 0; |
||||
|
height: $half-mobile-toolbar-height; |
||||
|
|
||||
|
@media #{$mat-lt-md} { |
||||
|
mat-menu{ |
||||
|
margin: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@media #{$mat-gt-sm} { |
||||
|
height: 46px; |
||||
|
} |
||||
|
|
||||
|
> div { |
||||
|
height: $half-mobile-toolbar-height; |
||||
|
|
||||
|
@media #{$mat-gt-sm} { |
||||
|
height: 46px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
mat-select { |
||||
|
margin: 0; |
||||
|
pointer-events: all; |
||||
|
} |
||||
|
|
||||
|
tb-states-component { |
||||
|
pointer-events: all; |
||||
|
} |
||||
|
|
||||
|
button.mat-icon-button:not(.tb-mat-32) { |
||||
|
min-width: 40px; |
||||
|
|
||||
|
@media #{$mat-lt-md} { |
||||
|
min-width: 28px; |
||||
|
padding: 2px; |
||||
|
margin: 0; |
||||
|
.mat-button-wrapper { |
||||
|
display: block; |
||||
|
line-height: 24px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2019 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { Component, OnDestroy, OnInit, ViewEncapsulation, Input, Output, EventEmitter } from '@angular/core'; |
||||
|
import { PageComponent } from '@shared/components/page.component'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-dashboard-toolbar', |
||||
|
templateUrl: './dashboard-toolbar.component.html', |
||||
|
styleUrls: ['./dashboard-toolbar.component.scss'], |
||||
|
encapsulation: ViewEncapsulation.None |
||||
|
}) |
||||
|
export class DashboardToolbarComponent implements OnInit { |
||||
|
|
||||
|
@Input() |
||||
|
toolbarOpened: boolean; |
||||
|
|
||||
|
@Input() |
||||
|
forceFullscreen: boolean; |
||||
|
|
||||
|
@Output() |
||||
|
triggerClick = new EventEmitter<void>(); |
||||
|
|
||||
|
constructor() { |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
} |
||||
|
|
||||
|
onTriggerClick() { |
||||
|
this.triggerClick.emit(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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 style="width: 800px;"> |
||||
|
<mat-toolbar fxLayout="row" color="primary"> |
||||
|
<h2 translate>widget.select-widget-type</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 [disabled]="(isLoading$ | async)"> |
||||
|
<div fxLayout="column" fxLayoutGap="16px" fxLayout.gt-sm="row" fxLayoutAlign="center center"> |
||||
|
<button *ngFor="let type of allWidgetTypes;" class="tb-card-button" mat-button mat-raised-button color="primary" |
||||
|
type="button" |
||||
|
(click)="typeSelected(type)"> |
||||
|
<mat-icon *ngIf="!widgetTypesDataMap.get(type).isMdiIcon; else mdiIconBlock" class="tb-mat-96"> |
||||
|
{{ widgetTypesDataMap.get(type).icon }} |
||||
|
</mat-icon> |
||||
|
<ng-template #mdiIconBlock> |
||||
|
<mat-icon class="tb-mat-96" [svgIcon]="widgetTypesDataMap.get(type).icon"> |
||||
|
</mat-icon> |
||||
|
</ng-template> |
||||
|
<span translate>{{ widgetTypesDataMap.get(type).name }}</span> |
||||
|
</button> |
||||
|
</div> |
||||
|
</fieldset> |
||||
|
</div> |
||||
|
<div mat-dialog-actions fxLayout="row"> |
||||
|
<span fxFlex></span> |
||||
|
<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,50 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2019 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
|
||||
|
:host ::ng-deep { |
||||
|
button.tb-card-button { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
max-width: 240px; |
||||
|
.mat-button-wrapper { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
mat-icon { |
||||
|
margin: auto; |
||||
|
} |
||||
|
span { |
||||
|
height: 18px; |
||||
|
min-height: 18px; |
||||
|
max-height: 18px; |
||||
|
padding: 0 0 20px 0; |
||||
|
margin: auto; |
||||
|
font-size: 18px; |
||||
|
font-weight: 400; |
||||
|
line-height: 18px; |
||||
|
white-space: normal; |
||||
|
} |
||||
|
} |
||||
|
&.mat-raised-button.mat-primary { |
||||
|
.mat-ripple-element { |
||||
|
opacity: 0.3; |
||||
|
background-color: rgba(255, 255, 255, 0.3); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,52 @@ |
|||||
|
///
|
||||
|
/// 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 } from '@angular/core'; |
||||
|
import { MatDialogRef } from '@angular/material'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { DialogComponent } from '@shared/components/dialog.component'; |
||||
|
import { Router } from '@angular/router'; |
||||
|
import { widgetType, widgetTypesData } from '@shared/models/widget.models'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-select-widget-type-dialog', |
||||
|
templateUrl: './select-widget-type-dialog.component.html', |
||||
|
styleUrls: ['./select-widget-type-dialog.component.scss'] |
||||
|
}) |
||||
|
export class SelectWidgetTypeDialogComponent extends |
||||
|
DialogComponent<SelectWidgetTypeDialogComponent, widgetType> { |
||||
|
|
||||
|
widgetTypes = widgetType; |
||||
|
|
||||
|
allWidgetTypes = Object.keys(widgetType); |
||||
|
|
||||
|
widgetTypesDataMap = widgetTypesData; |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
protected router: Router, |
||||
|
public dialogRef: MatDialogRef<SelectWidgetTypeDialogComponent, widgetType>) { |
||||
|
super(store, router, dialogRef); |
||||
|
} |
||||
|
|
||||
|
cancel(): void { |
||||
|
this.dialogRef.close(null); |
||||
|
} |
||||
|
|
||||
|
typeSelected(type: widgetType) { |
||||
|
this.dialogRef.close(type); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,255 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
--> |
||||
|
<hotkeys-cheatsheet></hotkeys-cheatsheet> |
||||
|
<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-label></mat-label> |
||||
|
<input [disabled]="isReadOnly" matInput [(ngModel)]="widget.widgetName" (ngModelChange)="isDirty = true" |
||||
|
placeholder="{{ 'widget.title' | translate }}"/> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field> |
||||
|
<mat-select [disabled]="isReadOnly" matInput placeholder="{{ 'widget.type' | translate }}" |
||||
|
[(ngModel)]="widget.type" (ngModelChange)="widetTypeChanged()"> |
||||
|
<mat-option *ngFor="let type of allWidgetTypes" [value]="type"> |
||||
|
{{ widgetTypesDataMap.get(type).name | translate }} |
||||
|
</mat-option> |
||||
|
</mat-select> |
||||
|
</mat-form-field> |
||||
|
<span fxFlex></span> |
||||
|
<button mat-button fxHide.xs fxHide.sm [disabled]="!iframeWidgetEditModeInited" |
||||
|
(click)="applyWidgetScript()" |
||||
|
matTooltip="{{ 'widget.run' | translate }} (CTRL + Return)" |
||||
|
matTooltipPosition="below"> |
||||
|
<mat-icon>play_arrow</mat-icon> |
||||
|
<span translate>action.run</span> |
||||
|
</button> |
||||
|
<button mat-button mat-raised-button |
||||
|
fxHide.xs fxHide.sm [disabled]="(isLoading$ | async) || undoDisabled()" |
||||
|
(click)="undoWidget()" |
||||
|
matTooltip="{{ 'widget.undo' | translate }} (CTRL + Q)" |
||||
|
matTooltipPosition="below"> |
||||
|
<mat-icon>undo</mat-icon> |
||||
|
<span translate>action.undo</span> |
||||
|
</button> |
||||
|
<button *ngIf="!isReadOnly" mat-button mat-raised-button |
||||
|
fxHide.xs fxHide.sm [disabled]="(isLoading$ | async) || saveDisabled()" |
||||
|
(click)="saveWidget()" |
||||
|
[tb-circular-progress]="saveWidgetPending" |
||||
|
matTooltip="{{ 'widget.save' | translate }} (CTRL + S)" |
||||
|
matTooltipPosition="below"> |
||||
|
<mat-icon>save</mat-icon> |
||||
|
<span translate>action.save</span> |
||||
|
</button> |
||||
|
<button mat-button mat-raised-button |
||||
|
fxHide.xs fxHide.sm [disabled]="(isLoading$ | async) || saveAsDisabled()" |
||||
|
(click)="saveWidgetAs()" |
||||
|
[tb-circular-progress]="saveWidgetAsPending" |
||||
|
matTooltip="{{ 'widget.saveAs' | translate }} (Shift + CTRL + S)" |
||||
|
matTooltipPosition="below"> |
||||
|
<mat-icon>save</mat-icon> |
||||
|
<span translate>action.saveAs</span> |
||||
|
</button> |
||||
|
<button mat-button |
||||
|
fxHide.xs fxHide.sm |
||||
|
(click)="fullscreen = !fullscreen" |
||||
|
matTooltip="{{ 'widget.toggle-fullscreen' | translate }} (Shift + CTRL + F)" |
||||
|
matTooltipPosition="below"> |
||||
|
<mat-icon>{{ fullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> |
||||
|
<span translate>widget.toggle-fullscreen</span> |
||||
|
</button> |
||||
|
<button mat-button mat-icon-button fxHide.gt-sm |
||||
|
[matMenuTriggerFor]="widgetEditMenu"> |
||||
|
<mat-icon>more_vert</mat-icon> |
||||
|
</button> |
||||
|
<mat-menu #widgetEditMenu="matMenu" xPosition="before"> |
||||
|
<button mat-menu-item |
||||
|
[disabled]="!iframeWidgetEditModeInited" |
||||
|
(click)="applyWidgetScript()"> |
||||
|
<mat-icon>play_arrow</mat-icon> |
||||
|
<span translate>action.run</span> |
||||
|
</button> |
||||
|
<button mat-menu-item |
||||
|
[disabled]="(isLoading$ | async) || undoDisabled()" |
||||
|
(click)="undoWidget()"> |
||||
|
<mat-icon>undo</mat-icon> |
||||
|
<span translate>action.undo</span> |
||||
|
</button> |
||||
|
<button *ngIf="!isReadOnly" mat-menu-item |
||||
|
[disabled]="(isLoading$ | async) || saveDisabled()" |
||||
|
(click)="saveWidget()"> |
||||
|
<mat-icon>save</mat-icon> |
||||
|
<span translate>action.save</span> |
||||
|
</button> |
||||
|
<button mat-menu-item |
||||
|
[disabled]="(isLoading$ | async) || saveAsDisabled()" |
||||
|
(click)="saveWidgetAs()"> |
||||
|
<mat-icon>save</mat-icon> |
||||
|
<span translate>action.saveAs</span> |
||||
|
</button> |
||||
|
</mat-menu> |
||||
|
</mat-toolbar> |
||||
|
<div fxFlex style="position: relative;"> |
||||
|
<div class="tb-editor tb-absolute-fill"> |
||||
|
<div #topPanel class="tb-split tb-split-vertical"> |
||||
|
<div #topLeftPanel class="tb-split tb-content"> |
||||
|
<mat-tab-group selectedIndex="1" dynamicHeight="true" style="width: 100%; height: 100%;"> |
||||
|
<mat-tab label="{{ 'widget.resources' | translate }}" style="width: 100%; height: 100%;"> |
||||
|
<div class="tb-resize-container" style="background-color: #fff;"> |
||||
|
<div class="mat-padding"> |
||||
|
<div fxFlex fxLayout="row" style="max-height: 40px;" |
||||
|
fxLayoutAlign="start center" |
||||
|
*ngFor="let resource of widget.resources; let i = index" > |
||||
|
<mat-form-field fxFlex class="mat-block resource-field" floatLabel="never" |
||||
|
style="margin: 10px 0px 0px 0px; max-height: 40px;"> |
||||
|
<input required matInput [(ngModel)]="resource.url" |
||||
|
(ngModelChange)="isDirty = true" |
||||
|
placeholder="{{ 'widget.resource-url' | translate }}"/> |
||||
|
</mat-form-field> |
||||
|
<button mat-button mat-icon-button color="primary" |
||||
|
[disabled]="isLoading$ | async" |
||||
|
(click)="removeResource(i)" |
||||
|
matTooltip="{{'widget.remove-resource' | translate}}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon>close</mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
<div style="margin-top: 6px;"> |
||||
|
<button mat-button mat-raised-button color="primary" |
||||
|
[disabled]="isLoading$ | async" |
||||
|
(click)="addResource()" |
||||
|
matTooltip="{{'widget.add-resource' | translate}}" |
||||
|
matTooltipPosition="above"> |
||||
|
<span translate>action.add</span> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</mat-tab> |
||||
|
<mat-tab label="{{ 'widget.html' | translate }}" style="width: 100%; height: 100%;"> |
||||
|
<div class="tb-resize-container" tb-fullscreen [fullscreen]="htmlFullscreen"> |
||||
|
<div class="tb-editor-area-title-panel"> |
||||
|
<button mat-button (click)="beautifyHtml()"> |
||||
|
{{ 'widget.tidy' | translate }} |
||||
|
</button> |
||||
|
<button mat-button mat-icon-button class="tb-mat-32" |
||||
|
(click)="htmlFullscreen = !htmlFullscreen" |
||||
|
matTooltip="{{(htmlFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon>{{ htmlFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
<div #htmlInput></div> |
||||
|
</div> |
||||
|
</mat-tab> |
||||
|
<mat-tab label="{{ 'widget.css' | translate }}" style="width: 100%; height: 100%;"> |
||||
|
<div class="tb-resize-container" tb-fullscreen [fullscreen]="cssFullscreen"> |
||||
|
<div class="tb-editor-area-title-panel"> |
||||
|
<button mat-button (click)="beautifyCss()"> |
||||
|
{{ 'widget.tidy' | translate }} |
||||
|
</button> |
||||
|
<button mat-button mat-icon-button class="tb-mat-32" |
||||
|
(click)="cssFullscreen = !cssFullscreen" |
||||
|
matTooltip="{{(cssFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon>{{ cssFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
<div #cssInput></div> |
||||
|
</div> |
||||
|
</mat-tab> |
||||
|
</mat-tab-group> |
||||
|
</div> |
||||
|
<div #topRightPanel class="tb-split tb-content"> |
||||
|
<mat-tab-group dynamicHeight="true" style="width: 100%; height: 100%;"> |
||||
|
<mat-tab label="{{ 'widget.settings-schema' | translate }}" style="width: 100%; height: 100%;"> |
||||
|
<div class="tb-resize-container" tb-fullscreen [fullscreen]="jsonSettingsFullscreen"> |
||||
|
<div class="tb-editor-area-title-panel"> |
||||
|
<button mat-button (click)="beautifyJson()"> |
||||
|
{{ 'widget.tidy' | translate }} |
||||
|
</button> |
||||
|
<button mat-button mat-icon-button class="tb-mat-32" |
||||
|
(click)="jsonSettingsFullscreen = !jsonSettingsFullscreen" |
||||
|
matTooltip="{{(jsonSettingsFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon>{{ jsonSettingsFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
<div #settingsJsonInput></div> |
||||
|
</div> |
||||
|
</mat-tab> |
||||
|
<mat-tab label="{{ 'widget.datakey-settings-schema' | translate }}" style="width: 100%; height: 100%;"> |
||||
|
<div class="tb-resize-container" tb-fullscreen [fullscreen]="jsonDataKeySettingsFullscreen"> |
||||
|
<div class="tb-editor-area-title-panel"> |
||||
|
<button mat-button (click)="beautifyDataKeyJson()"> |
||||
|
{{ 'widget.tidy' | translate }} |
||||
|
</button> |
||||
|
<button mat-button mat-icon-button class="tb-mat-32" |
||||
|
(click)="jsonDataKeySettingsFullscreen = !jsonDataKeySettingsFullscreen" |
||||
|
matTooltip="{{(jsonDataKeySettingsFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon>{{ jsonDataKeySettingsFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
<div #dataKeySettingsJsonInput></div> |
||||
|
</div> |
||||
|
</mat-tab> |
||||
|
</mat-tab-group> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div #bottomPanel class="tb-split tb-split-vertical"> |
||||
|
<div #javascriptPanel class="tb-split tb-content" tb-toast toastTarget="javascriptPanel"> |
||||
|
<div class="tb-resize-container" tb-fullscreen [fullscreen]="javascriptFullscreen"> |
||||
|
<div class="tb-editor-area-title-panel"> |
||||
|
<label translate>widget.javascript</label> |
||||
|
<button mat-button (click)="beautifyJs()"> |
||||
|
{{ 'widget.tidy' | translate }} |
||||
|
</button> |
||||
|
<button mat-button mat-icon-button class="tb-mat-32" |
||||
|
(click)="javascriptFullscreen = !javascriptFullscreen" |
||||
|
matTooltip="{{(javascriptFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}" |
||||
|
matTooltipPosition="above"> |
||||
|
<mat-icon>{{ javascriptFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
<div #javascriptInput></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div #framePanel class="tb-split tb-content" style="overflow-y: hidden; position: relative;"> |
||||
|
<div class="mat-content tb-progress-cover" fxFlex fxLayout="column" fxLayoutAlign="center center" |
||||
|
*ngIf="!iframeWidgetEditModeInited"> |
||||
|
<mat-spinner diameter="100" mode="indeterminate" color="warn"></mat-spinner> |
||||
|
</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> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,186 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
$edit-toolbar-height: 40px !default; |
||||
|
|
||||
|
tb-widget-editor { |
||||
|
flex: 1; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.tb-editor { |
||||
|
.tb-split { |
||||
|
box-sizing: border-box; |
||||
|
overflow-x: hidden; |
||||
|
overflow-y: auto; |
||||
|
} |
||||
|
|
||||
|
mat-form-field.resource-field { |
||||
|
max-height: 40px; |
||||
|
margin: 10px 0px 0px 0px; |
||||
|
.mat-form-field-wrapper { |
||||
|
padding-bottom: 0; |
||||
|
.mat-form-field-flex { |
||||
|
max-height: 40px; |
||||
|
.mat-form-field-infix { |
||||
|
border: 0; |
||||
|
} |
||||
|
} |
||||
|
.mat-form-field-underline { |
||||
|
bottom: 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.ace_editor { |
||||
|
font-size: 14px !important; |
||||
|
} |
||||
|
|
||||
|
.tb-content { |
||||
|
border: 1px solid #c0c0c0; |
||||
|
} |
||||
|
|
||||
|
.gutter { |
||||
|
background-color: transparent; |
||||
|
|
||||
|
background-repeat: no-repeat; |
||||
|
background-position: 50%; |
||||
|
|
||||
|
&.gutter-horizontal { |
||||
|
cursor: col-resize; |
||||
|
background-image: url("../../../../../assets/split.js/grips/vertical.png"); |
||||
|
} |
||||
|
|
||||
|
&.gutter-vertical { |
||||
|
cursor: row-resize; |
||||
|
background-image: url("../../../../../assets/split.js/grips/horizontal.png"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.tb-split.tb-split-horizontal, |
||||
|
.gutter.gutter-horizontal { |
||||
|
float: left; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.tb-split.tb-split-vertical { |
||||
|
display: flex; |
||||
|
|
||||
|
.tb-split.tb-content { |
||||
|
height: 100%; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.tb-split-vertical { |
||||
|
mat-tab-group { |
||||
|
.mat-tab-body-wrapper { |
||||
|
height: calc(100% - 49px); |
||||
|
|
||||
|
mat-tab-body { |
||||
|
height: 100%; |
||||
|
|
||||
|
& > div { |
||||
|
height: 100%; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
div.tb-editor-area-title-panel { |
||||
|
position: absolute; |
||||
|
top: 5px; |
||||
|
right: 20px; |
||||
|
z-index: 5; |
||||
|
font-size: .8rem; |
||||
|
font-weight: 500; |
||||
|
|
||||
|
label { |
||||
|
padding: 4px; |
||||
|
color: #00acc1; |
||||
|
text-transform: uppercase; |
||||
|
background: rgba(220, 220, 220, .35); |
||||
|
border-radius: 5px; |
||||
|
&:not(:last-child) { |
||||
|
margin-right: 4px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
button.mat-button, button.mat-icon-button, button.mat-icon-button.tb-mat-32 { |
||||
|
align-items: center; |
||||
|
vertical-align: middle; |
||||
|
min-width: 32px; |
||||
|
min-height: 15px; |
||||
|
padding: 4px; |
||||
|
margin: 0; |
||||
|
font-size: .8rem; |
||||
|
line-height: 15px; |
||||
|
color: #7b7b7b; |
||||
|
background: rgba(220, 220, 220, .35); |
||||
|
&:not(:last-child) { |
||||
|
margin-right: 4px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.tb-resize-container { |
||||
|
position: relative; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
overflow-y: auto; |
||||
|
|
||||
|
.ace_editor { |
||||
|
height: 100%; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
mat-toolbar.tb-edit-toolbar { |
||||
|
|
||||
|
min-height: $edit-toolbar-height !important; |
||||
|
max-height: $edit-toolbar-height !important; |
||||
|
|
||||
|
button.mat-button { |
||||
|
min-width: 65px; |
||||
|
min-height: 30px; |
||||
|
font-size: 12px; |
||||
|
line-height: 30px; |
||||
|
|
||||
|
mat-icon { |
||||
|
font-size: 20px; |
||||
|
} |
||||
|
|
||||
|
span { |
||||
|
padding-right: 6px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
mat-form-field { |
||||
|
input { |
||||
|
font-size: 1.2rem; |
||||
|
font-weight: 400; |
||||
|
letter-spacing: .005em; |
||||
|
} |
||||
|
div.mat-form-field-infix { |
||||
|
padding-bottom: 5px; |
||||
|
} |
||||
|
&.tb-widget-title { |
||||
|
min-width: 250px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,626 @@ |
|||||
|
///
|
||||
|
/// 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 { PageComponent } from '@shared/components/page.component'; |
||||
|
import { Component, ElementRef, Inject, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; |
||||
|
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 { WidgetConfig, widgetType, WidgetType, widgetTypesData, Widget } from '@shared/models/widget.models'; |
||||
|
import { ActivatedRoute } from '@angular/router'; |
||||
|
import { deepClone } from '@core/utils'; |
||||
|
import { HasDirtyFlag } from '@core/guards/confirm-on-exit.guard'; |
||||
|
import { AuthUser } from '@shared/models/user.model'; |
||||
|
import { getCurrentAuthUser } from '@core/auth/auth.selectors'; |
||||
|
import { Authority } from '@shared/models/authority.enum'; |
||||
|
import { NULL_UUID } from '@shared/models/id/has-uuid'; |
||||
|
import { Hotkey, HotkeysService } from 'angular2-hotkeys'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
import { getCurrentIsLoading } from '@app/core/interceptors/load.selectors'; |
||||
|
import * as ace from 'ace-builds'; |
||||
|
import { css_beautify, html_beautify } from 'js-beautify'; |
||||
|
import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; |
||||
|
import { WINDOW } from '@core/services/window.service'; |
||||
|
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'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-widget-editor', |
||||
|
templateUrl: './widget-editor.component.html', |
||||
|
styleUrls: ['./widget-editor.component.scss'], |
||||
|
encapsulation: ViewEncapsulation.None |
||||
|
}) |
||||
|
export class WidgetEditorComponent extends PageComponent implements OnInit, OnDestroy, HasDirtyFlag { |
||||
|
|
||||
|
@ViewChild('topPanel', {static: true}) |
||||
|
topPanelElmRef: ElementRef; |
||||
|
|
||||
|
@ViewChild('topLeftPanel', {static: true}) |
||||
|
topLeftPanelElmRef: ElementRef; |
||||
|
|
||||
|
@ViewChild('topRightPanel', {static: true}) |
||||
|
topRightPanelElmRef: ElementRef; |
||||
|
|
||||
|
@ViewChild('bottomPanel', {static: true}) |
||||
|
bottomPanelElmRef: ElementRef; |
||||
|
|
||||
|
@ViewChild('javascriptPanel', {static: true}) |
||||
|
javascriptPanelElmRef: ElementRef; |
||||
|
|
||||
|
@ViewChild('framePanel', {static: true}) |
||||
|
framePanelElmRef: ElementRef; |
||||
|
|
||||
|
@ViewChild('htmlInput', {static: true}) |
||||
|
htmlInputElmRef: ElementRef; |
||||
|
|
||||
|
@ViewChild('cssInput', {static: true}) |
||||
|
cssInputElmRef: ElementRef; |
||||
|
|
||||
|
@ViewChild('settingsJsonInput', {static: true}) |
||||
|
settingsJsonInputElmRef: ElementRef; |
||||
|
|
||||
|
@ViewChild('dataKeySettingsJsonInput', {static: true}) |
||||
|
dataKeySettingsJsonInputElmRef: ElementRef; |
||||
|
|
||||
|
@ViewChild('javascriptInput', {static: true}) |
||||
|
javascriptInputElmRef: ElementRef; |
||||
|
|
||||
|
@ViewChild('widgetIFrame', {static: true}) |
||||
|
widgetIFrameElmRef: ElementRef<HTMLIFrameElement>; |
||||
|
|
||||
|
iframe: JQuery<HTMLIFrameElement>; |
||||
|
|
||||
|
widgetTypes = widgetType; |
||||
|
allWidgetTypes = Object.keys(widgetType); |
||||
|
widgetTypesDataMap = widgetTypesData; |
||||
|
|
||||
|
authUser: AuthUser; |
||||
|
|
||||
|
isReadOnly: boolean; |
||||
|
|
||||
|
widgetsBundle: WidgetsBundle; |
||||
|
widgetType: WidgetType; |
||||
|
widget: WidgetInfo; |
||||
|
origWidget: WidgetInfo; |
||||
|
|
||||
|
isDirty = false; |
||||
|
|
||||
|
fullscreen = false; |
||||
|
htmlFullscreen = false; |
||||
|
cssFullscreen = false; |
||||
|
jsonSettingsFullscreen = false; |
||||
|
jsonDataKeySettingsFullscreen = false; |
||||
|
javascriptFullscreen = false; |
||||
|
iFrameFullscreen = false; |
||||
|
|
||||
|
aceEditors: ace.Ace.Editor[] = []; |
||||
|
editorsResizeCafs: {[editorId: string]: CancelAnimationFrame} = {}; |
||||
|
htmlEditor: ace.Ace.Editor; |
||||
|
cssEditor: ace.Ace.Editor; |
||||
|
jsonSettingsEditor: ace.Ace.Editor; |
||||
|
dataKeyJsonSettingsEditor: ace.Ace.Editor; |
||||
|
jsEditor: ace.Ace.Editor; |
||||
|
aceResizeListeners: { element: any, resizeListener: any }[] = []; |
||||
|
|
||||
|
onWindowMessageListener = this.onWindowMessage.bind(this); |
||||
|
|
||||
|
iframeWidgetEditModeInited = false; |
||||
|
saveWidgetPending = false; |
||||
|
saveWidgetAsPending = false; |
||||
|
|
||||
|
gotError = false; |
||||
|
errorMarkers: number[] = []; |
||||
|
errorAnnotationId = -1; |
||||
|
|
||||
|
saveWidgetTimeout: Timeout; |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
@Inject(WINDOW) private window: Window, |
||||
|
private route: ActivatedRoute, |
||||
|
private widgetService: WidgetService, |
||||
|
private hotkeysService: HotkeysService, |
||||
|
private translate: TranslateService, |
||||
|
private raf: RafService) { |
||||
|
super(store); |
||||
|
|
||||
|
this.authUser = getCurrentAuthUser(store); |
||||
|
|
||||
|
this.widgetsBundle = this.route.snapshot.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; |
||||
|
if (this.widgetType) { |
||||
|
const config = JSON.parse(this.widget.defaultConfig); |
||||
|
this.widget.defaultConfig = JSON.stringify(config); |
||||
|
} |
||||
|
this.origWidget = deepClone(this.widget); |
||||
|
if (!this.widgetType) { |
||||
|
this.isDirty = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.initHotKeys(); |
||||
|
this.initSplitLayout(); |
||||
|
this.initAceEditors(); |
||||
|
this.iframe = $(this.widgetIFrameElmRef.nativeElement); |
||||
|
this.window.addEventListener('message', this.onWindowMessageListener); |
||||
|
this.iframe.attr('data-widget', JSON.stringify(this.widget)); |
||||
|
this.iframe.attr('src', '/widget-editor'); |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy(): void { |
||||
|
this.window.removeEventListener('message', this.onWindowMessageListener); |
||||
|
this.aceResizeListeners.forEach((resizeListener) => { |
||||
|
// @ts-ignore
|
||||
|
removeResizeListener(resizeListener.element, resizeListener.resizeListener); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private initHotKeys(): void { |
||||
|
this.hotkeysService.add( |
||||
|
new Hotkey('ctrl+q', (event: KeyboardEvent) => { |
||||
|
if (!getCurrentIsLoading(this.store) && !this.undoDisabled()) { |
||||
|
event.preventDefault(); |
||||
|
this.undoWidget(); |
||||
|
} |
||||
|
return false; |
||||
|
}, ['INPUT', 'SELECT', 'TEXTAREA'], |
||||
|
this.translate.instant('widget.undo')) |
||||
|
); |
||||
|
this.hotkeysService.add( |
||||
|
new Hotkey('ctrl+s', (event: KeyboardEvent) => { |
||||
|
if (!getCurrentIsLoading(this.store) && !this.saveDisabled()) { |
||||
|
event.preventDefault(); |
||||
|
this.saveWidget(); |
||||
|
} |
||||
|
return false; |
||||
|
}, ['INPUT', 'SELECT', 'TEXTAREA'], |
||||
|
this.translate.instant('widget.save')) |
||||
|
); |
||||
|
this.hotkeysService.add( |
||||
|
new Hotkey('shift+ctrl+s', (event: KeyboardEvent) => { |
||||
|
if (!getCurrentIsLoading(this.store) && !this.saveAsDisabled()) { |
||||
|
event.preventDefault(); |
||||
|
this.saveWidgetAs(); |
||||
|
} |
||||
|
return false; |
||||
|
}, ['INPUT', 'SELECT', 'TEXTAREA'], |
||||
|
this.translate.instant('widget.saveAs')) |
||||
|
); |
||||
|
this.hotkeysService.add( |
||||
|
new Hotkey('shift+ctrl+f', (event: KeyboardEvent) => { |
||||
|
event.preventDefault(); |
||||
|
this.fullscreen = !this.fullscreen; |
||||
|
return false; |
||||
|
}, ['INPUT', 'SELECT', 'TEXTAREA'], |
||||
|
this.translate.instant('widget.toggle-fullscreen')) |
||||
|
); |
||||
|
this.hotkeysService.add( |
||||
|
new Hotkey('ctrl+enter', (event: KeyboardEvent) => { |
||||
|
event.preventDefault(); |
||||
|
this.applyWidgetScript(); |
||||
|
return false; |
||||
|
}, ['INPUT', 'SELECT', 'TEXTAREA'], |
||||
|
this.translate.instant('widget.run')) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
private initSplitLayout() { |
||||
|
Split([this.topPanelElmRef.nativeElement, this.bottomPanelElmRef.nativeElement], { |
||||
|
sizes: [35, 65], |
||||
|
gutterSize: 8, |
||||
|
cursor: 'row-resize', |
||||
|
direction: 'vertical' |
||||
|
}); |
||||
|
Split([this.topLeftPanelElmRef.nativeElement, this.topRightPanelElmRef.nativeElement], { |
||||
|
sizes: [50, 50], |
||||
|
gutterSize: 8, |
||||
|
cursor: 'col-resize' |
||||
|
}); |
||||
|
Split([this.javascriptPanelElmRef.nativeElement, this.framePanelElmRef.nativeElement], { |
||||
|
sizes: [50, 50], |
||||
|
gutterSize: 8, |
||||
|
cursor: 'col-resize' |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private initAceEditors() { |
||||
|
this.htmlEditor = this.createAceEditor(this.htmlInputElmRef, 'html'); |
||||
|
this.htmlEditor.on('input', () => { |
||||
|
const editorValue = this.htmlEditor.getValue(); |
||||
|
if (this.widget.templateHtml !== editorValue) { |
||||
|
this.widget.templateHtml = editorValue; |
||||
|
this.isDirty = true; |
||||
|
} |
||||
|
}); |
||||
|
this.cssEditor = this.createAceEditor(this.cssInputElmRef, 'css'); |
||||
|
this.cssEditor.on('input', () => { |
||||
|
const editorValue = this.cssEditor.getValue(); |
||||
|
if (this.widget.templateCss !== editorValue) { |
||||
|
this.widget.templateCss = editorValue; |
||||
|
this.isDirty = true; |
||||
|
} |
||||
|
}); |
||||
|
this.jsonSettingsEditor = this.createAceEditor(this.settingsJsonInputElmRef, 'json'); |
||||
|
this.jsonSettingsEditor.on('input', () => { |
||||
|
const editorValue = this.jsonSettingsEditor.getValue(); |
||||
|
if (this.widget.settingsSchema !== editorValue) { |
||||
|
this.widget.settingsSchema = editorValue; |
||||
|
this.isDirty = true; |
||||
|
} |
||||
|
}); |
||||
|
this.dataKeyJsonSettingsEditor = this.createAceEditor(this.dataKeySettingsJsonInputElmRef, 'json'); |
||||
|
this.dataKeyJsonSettingsEditor.on('input', () => { |
||||
|
const editorValue = this.dataKeyJsonSettingsEditor.getValue(); |
||||
|
if (this.widget.dataKeySettingsSchema !== editorValue) { |
||||
|
this.widget.dataKeySettingsSchema = editorValue; |
||||
|
this.isDirty = true; |
||||
|
} |
||||
|
}); |
||||
|
this.jsEditor = this.createAceEditor(this.javascriptInputElmRef, 'javascript'); |
||||
|
this.jsEditor.on('input', () => { |
||||
|
const editorValue = this.jsEditor.getValue(); |
||||
|
if (this.widget.controllerScript !== editorValue) { |
||||
|
this.widget.controllerScript = editorValue; |
||||
|
this.isDirty = true; |
||||
|
} |
||||
|
}); |
||||
|
this.jsEditor.on('change', () => { |
||||
|
this.cleanupJsErrors(); |
||||
|
}); |
||||
|
this.setAceEditorValues(); |
||||
|
} |
||||
|
|
||||
|
private setAceEditorValues() { |
||||
|
this.htmlEditor.setValue(this.widget.templateHtml ? this.widget.templateHtml : '', -1); |
||||
|
this.cssEditor.setValue(this.widget.templateCss ? this.widget.templateCss : '', -1); |
||||
|
this.jsonSettingsEditor.setValue(this.widget.settingsSchema ? this.widget.settingsSchema : '', -1); |
||||
|
this.dataKeyJsonSettingsEditor.setValue(this.widget.dataKeySettingsSchema ? this.widget.dataKeySettingsSchema : '', -1); |
||||
|
this.jsEditor.setValue(this.widget.controllerScript ? this.widget.controllerScript : '', -1); |
||||
|
} |
||||
|
|
||||
|
private createAceEditor(editorElementRef: ElementRef, mode: string): ace.Ace.Editor { |
||||
|
const editorElement = editorElementRef.nativeElement; |
||||
|
let editorOptions: Partial<ace.Ace.EditorOptions> = { |
||||
|
mode: `ace/mode/${mode}`, |
||||
|
showGutter: true, |
||||
|
showPrintMargin: true |
||||
|
}; |
||||
|
const advancedOptions = { |
||||
|
enableSnippets: true, |
||||
|
enableBasicAutocompletion: true, |
||||
|
enableLiveAutocompletion: true |
||||
|
}; |
||||
|
editorOptions = {...editorOptions, ...advancedOptions}; |
||||
|
const aceEditor = ace.edit(editorElement, editorOptions); |
||||
|
aceEditor.session.setUseWrapMode(true); |
||||
|
this.aceEditors.push(aceEditor); |
||||
|
|
||||
|
const resizeListener = this.onAceEditorResize.bind(this, aceEditor); |
||||
|
|
||||
|
// @ts-ignore
|
||||
|
addResizeListener(editorElement, resizeListener); |
||||
|
this.aceResizeListeners.push({element: editorElement, resizeListener}); |
||||
|
return aceEditor; |
||||
|
} |
||||
|
|
||||
|
private onAceEditorResize(aceEditor: ace.Ace.Editor) { |
||||
|
if (this.editorsResizeCafs[aceEditor.id]) { |
||||
|
this.editorsResizeCafs[aceEditor.id](); |
||||
|
delete this.editorsResizeCafs[aceEditor.id]; |
||||
|
} |
||||
|
this.editorsResizeCafs[aceEditor.id] = this.raf.raf(() => { |
||||
|
aceEditor.resize(); |
||||
|
aceEditor.renderer.updateFull(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private onWindowMessage(event: MessageEvent) { |
||||
|
let message: WindowMessage; |
||||
|
if (event.data) { |
||||
|
try { |
||||
|
message = JSON.parse(event.data); |
||||
|
} catch (e) {} |
||||
|
} |
||||
|
if (message) { |
||||
|
switch (message.type) { |
||||
|
case 'widgetException': |
||||
|
this.onWidgetException(message.data); |
||||
|
break; |
||||
|
case 'widgetEditModeInited': |
||||
|
this.onWidgetEditModeInited(); |
||||
|
break; |
||||
|
case 'widgetEditUpdated': |
||||
|
this.onWidgetEditUpdated(message.data); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private onWidgetEditModeInited() { |
||||
|
this.iframeWidgetEditModeInited = true; |
||||
|
if (this.saveWidgetPending || this.saveWidgetAsPending) { |
||||
|
if (!this.saveWidgetTimeout) { |
||||
|
this.saveWidgetTimeout = setTimeout(() => { |
||||
|
if (!this.gotError) { |
||||
|
if (this.saveWidgetPending) { |
||||
|
this.commitSaveWidget(); |
||||
|
} else if (this.saveWidgetAsPending) { |
||||
|
this.commitSaveWidgetAs(); |
||||
|
} |
||||
|
} else { |
||||
|
this.store.dispatch(new ActionNotificationShow( |
||||
|
{message: this.translate.instant('widget.unable-to-save-widget-error'), type: 'error'})); |
||||
|
this.saveWidgetPending = false; |
||||
|
this.saveWidgetAsPending = false; |
||||
|
} |
||||
|
this.saveWidgetTimeout = undefined; |
||||
|
}, 1500); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private onWidgetEditUpdated(widget: Widget) { |
||||
|
this.widget.sizeX = widget.sizeX / 2; |
||||
|
this.widget.sizeY = widget.sizeY / 2; |
||||
|
this.widget.defaultConfig = JSON.stringify(widget.config); |
||||
|
this.iframe.attr('data-widget', JSON.stringify(this.widget)); |
||||
|
this.isDirty = true; |
||||
|
} |
||||
|
|
||||
|
private onWidgetException(details: ExceptionData) { |
||||
|
if (!this.gotError) { |
||||
|
this.gotError = true; |
||||
|
let errorInfo = 'Error:'; |
||||
|
if (details.name) { |
||||
|
errorInfo += ' ' + details.name + ':'; |
||||
|
} |
||||
|
if (details.message) { |
||||
|
errorInfo += ' ' + details.message; |
||||
|
} |
||||
|
if (details.lineNumber) { |
||||
|
errorInfo += '<br>Line ' + details.lineNumber; |
||||
|
if (details.columnNumber) { |
||||
|
errorInfo += ' column ' + details.columnNumber; |
||||
|
} |
||||
|
errorInfo += ' of script.'; |
||||
|
} |
||||
|
if (!this.saveWidgetPending && !this.saveWidgetAsPending) { |
||||
|
this.store.dispatch(new ActionNotificationShow( |
||||
|
{message: errorInfo, type: 'error', target: 'javascriptPanel'})); |
||||
|
} |
||||
|
if (details.lineNumber) { |
||||
|
const line = details.lineNumber - 1; |
||||
|
let column = 0; |
||||
|
if (details.columnNumber) { |
||||
|
column = details.columnNumber; |
||||
|
} |
||||
|
const errorMarkerId = this.jsEditor.session.addMarker(new ace.Range(line, 0, line, Infinity), |
||||
|
'ace_active-line', 'screenLine'); |
||||
|
this.errorMarkers.push(errorMarkerId); |
||||
|
const annotations = this.jsEditor.session.getAnnotations(); |
||||
|
const errorAnnotation: ace.Ace.Annotation = { |
||||
|
row: line, |
||||
|
column, |
||||
|
text: details.message, |
||||
|
type: 'error' |
||||
|
}; |
||||
|
this.errorAnnotationId = annotations.push(errorAnnotation) - 1; |
||||
|
this.jsEditor.session.setAnnotations(annotations); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private cleanupJsErrors() { |
||||
|
this.store.dispatch(new ActionNotificationHide({})); |
||||
|
this.errorMarkers.forEach((errorMarker) => { |
||||
|
this.jsEditor.session.removeMarker(errorMarker); |
||||
|
}); |
||||
|
this.errorMarkers.length = 0; |
||||
|
if (this.errorAnnotationId > -1) { |
||||
|
const annotations = this.jsEditor.session.getAnnotations(); |
||||
|
annotations.splice(this.errorAnnotationId, 1); |
||||
|
this.jsEditor.session.setAnnotations(annotations); |
||||
|
this.errorAnnotationId = -1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private commitSaveWidget() { |
||||
|
// TODO:
|
||||
|
this.saveWidgetPending = false; |
||||
|
} |
||||
|
|
||||
|
private commitSaveWidgetAs() { |
||||
|
// TODO:
|
||||
|
this.saveWidgetAsPending = false; |
||||
|
} |
||||
|
|
||||
|
applyWidgetScript(): void { |
||||
|
this.cleanupJsErrors(); |
||||
|
this.gotError = false; |
||||
|
this.iframeWidgetEditModeInited = false; |
||||
|
const config: WidgetConfig = JSON.parse(this.widget.defaultConfig); |
||||
|
config.title = this.widget.widgetName; |
||||
|
this.widget.defaultConfig = JSON.stringify(config); |
||||
|
this.iframe.attr('data-widget', JSON.stringify(this.widget)); |
||||
|
this.iframe[0].contentWindow.location.reload(true); |
||||
|
} |
||||
|
|
||||
|
undoWidget(): void { |
||||
|
this.widget = deepClone(this.origWidget); |
||||
|
this.setAceEditorValues(); |
||||
|
this.isDirty = false; |
||||
|
this.applyWidgetScript(); |
||||
|
} |
||||
|
|
||||
|
saveWidget(): void { |
||||
|
if (!this.widget.widgetName) { |
||||
|
this.store.dispatch(new ActionNotificationShow( |
||||
|
{message: this.translate.instant('widget.missing-widget-title-error'), type: 'error'})); |
||||
|
} else { |
||||
|
this.saveWidgetPending = true; |
||||
|
this.applyWidgetScript(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
saveWidgetAs(): void { |
||||
|
this.saveWidgetAsPending = true; |
||||
|
this.applyWidgetScript(); |
||||
|
} |
||||
|
|
||||
|
undoDisabled(): boolean { |
||||
|
return !this.isDirty |
||||
|
|| !this.iframeWidgetEditModeInited |
||||
|
|| this.saveWidgetPending |
||||
|
|| this.saveWidgetAsPending; |
||||
|
} |
||||
|
|
||||
|
saveDisabled(): boolean { |
||||
|
return this.isReadOnly |
||||
|
|| !this.isDirty |
||||
|
|| !this.iframeWidgetEditModeInited |
||||
|
|| this.saveWidgetPending |
||||
|
|| this.saveWidgetAsPending; |
||||
|
} |
||||
|
|
||||
|
saveAsDisabled(): boolean { |
||||
|
return !this.iframeWidgetEditModeInited |
||||
|
|| this.saveWidgetPending |
||||
|
|| this.saveWidgetAsPending; |
||||
|
} |
||||
|
|
||||
|
beautifyCss(): void { |
||||
|
const res = css_beautify(this.widget.templateCss, {indent_size: 4}); |
||||
|
if (this.widget.templateCss !== res) { |
||||
|
this.isDirty = true; |
||||
|
this.widget.templateCss = res; |
||||
|
this.cssEditor.setValue(this.widget.templateCss ? this.widget.templateCss : '', -1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
beautifyHtml(): void { |
||||
|
const res = html_beautify(this.widget.templateHtml, {indent_size: 4, wrap_line_length: 60}); |
||||
|
if (this.widget.templateHtml !== res) { |
||||
|
this.isDirty = true; |
||||
|
this.widget.templateHtml = res; |
||||
|
this.htmlEditor.setValue(this.widget.templateHtml ? this.widget.templateHtml : '', -1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
beautifyJson(): void { |
||||
|
const res = js_beautify(this.widget.settingsSchema, {indent_size: 4}); |
||||
|
if (this.widget.settingsSchema !== res) { |
||||
|
this.isDirty = true; |
||||
|
this.widget.settingsSchema = res; |
||||
|
this.jsonSettingsEditor.setValue(this.widget.settingsSchema ? this.widget.settingsSchema : '', -1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
beautifyDataKeyJson(): void { |
||||
|
const res = js_beautify(this.widget.dataKeySettingsSchema, {indent_size: 4}); |
||||
|
if (this.widget.dataKeySettingsSchema !== res) { |
||||
|
this.isDirty = true; |
||||
|
this.widget.dataKeySettingsSchema = res; |
||||
|
this.dataKeyJsonSettingsEditor.setValue(this.widget.dataKeySettingsSchema ? this.widget.dataKeySettingsSchema : '', -1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
beautifyJs(): void { |
||||
|
const res = js_beautify(this.widget.controllerScript, {indent_size: 4, wrap_line_length: 60}); |
||||
|
if (this.widget.controllerScript !== res) { |
||||
|
this.isDirty = true; |
||||
|
this.widget.controllerScript = res; |
||||
|
this.jsEditor.setValue(this.widget.controllerScript ? this.widget.controllerScript : '', -1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
removeResource(index: number) { |
||||
|
if (index > -1) { |
||||
|
if (this.widget.resources.splice(index, 1).length > 0) { |
||||
|
this.isDirty = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
addResource() { |
||||
|
this.widget.resources.push({url: ''}); |
||||
|
this.isDirty = true; |
||||
|
} |
||||
|
|
||||
|
widetTypeChanged() { |
||||
|
const config: WidgetConfig = JSON.parse(this.widget.defaultConfig); |
||||
|
if (this.widget.type !== widgetType.rpc && |
||||
|
this.widget.type !== widgetType.alarm) { |
||||
|
if (config.targetDeviceAliases) { |
||||
|
delete config.targetDeviceAliases; |
||||
|
} |
||||
|
if (config.alarmSource) { |
||||
|
delete config.alarmSource; |
||||
|
} |
||||
|
if (!config.datasources) { |
||||
|
config.datasources = []; |
||||
|
} |
||||
|
if (!config.timewindow) { |
||||
|
config.timewindow = { |
||||
|
realtime: { |
||||
|
timewindowMs: 60000 |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
} else if (this.widget.type === widgetType.rpc) { |
||||
|
if (config.datasources) { |
||||
|
delete config.datasources; |
||||
|
} |
||||
|
if (config.alarmSource) { |
||||
|
delete config.alarmSource; |
||||
|
} |
||||
|
if (config.timewindow) { |
||||
|
delete config.timewindow; |
||||
|
} |
||||
|
if (!config.targetDeviceAliases) { |
||||
|
config.targetDeviceAliases = []; |
||||
|
} |
||||
|
} else { // alarm
|
||||
|
if (config.datasources) { |
||||
|
delete config.datasources; |
||||
|
} |
||||
|
if (config.targetDeviceAliases) { |
||||
|
delete config.targetDeviceAliases; |
||||
|
} |
||||
|
if (!config.alarmSource) { |
||||
|
config.alarmSource = {}; |
||||
|
} |
||||
|
if (!config.timewindow) { |
||||
|
config.timewindow = { |
||||
|
realtime: { |
||||
|
timewindowMs: 24 * 60 * 60 * 1000 |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
this.widget.defaultConfig = JSON.stringify(config); |
||||
|
this.isDirty = true; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,83 @@ |
|||||
|
///
|
||||
|
/// 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 { Directive, ElementRef, ViewContainerRef, ComponentFactoryResolver, ComponentRef, Input } from '@angular/core'; |
||||
|
import { Overlay } from '@angular/cdk/overlay'; |
||||
|
import { MatProgressBar, MatSpinner } from '@angular/material'; |
||||
|
|
||||
|
@Directive({ |
||||
|
selector: '[tb-circular-progress]' |
||||
|
}) |
||||
|
export class CircularProgressDirective { |
||||
|
|
||||
|
showProgressValue = false; |
||||
|
|
||||
|
children: JQuery<any>; |
||||
|
|
||||
|
cssWidth: any; |
||||
|
|
||||
|
@Input('tb-circular-progress') |
||||
|
set showProgress(showProgress: boolean) { |
||||
|
if (this.showProgressValue !== showProgress) { |
||||
|
const element = this.elementRef.nativeElement; |
||||
|
this.showProgressValue = showProgress; |
||||
|
this.spinnerRef.instance._elementRef.nativeElement.style.display = showProgress ? 'block' : 'none'; |
||||
|
if (showProgress) { |
||||
|
this.cssWidth = $(element).prop('style').width; |
||||
|
if (!this.cssWidth) { |
||||
|
$(element).css('width', ''); |
||||
|
const width = $(element).prop('offsetWidth'); |
||||
|
$(element).css('width', width + 'px'); |
||||
|
} |
||||
|
this.children = $(element).children(); |
||||
|
$(element).empty(); |
||||
|
$(element).append($(this.spinnerRef.instance._elementRef.nativeElement)); |
||||
|
} else { |
||||
|
$(element).empty(); |
||||
|
$(element).append(this.children); |
||||
|
if (this.cssWidth) { |
||||
|
$(element).css('width', this.cssWidth); |
||||
|
} else { |
||||
|
$(element).css('width', ''); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
spinnerRef: ComponentRef<MatSpinner>; |
||||
|
|
||||
|
constructor(private elementRef: ElementRef, |
||||
|
private componentFactoryResolver: ComponentFactoryResolver, |
||||
|
private viewContainerRef: ViewContainerRef) { |
||||
|
this.createCircularProgress(); |
||||
|
} |
||||
|
|
||||
|
createCircularProgress() { |
||||
|
this.elementRef.nativeElement.style.position = 'relative'; |
||||
|
const factory = this.componentFactoryResolver.resolveComponentFactory(MatSpinner); |
||||
|
this.spinnerRef = this.viewContainerRef.createComponent(factory, 0); |
||||
|
this.spinnerRef.instance.mode = 'indeterminate'; |
||||
|
this.spinnerRef.instance.diameter = 20; |
||||
|
const el = this.spinnerRef.instance._elementRef.nativeElement; |
||||
|
el.style.margin = 'auto'; |
||||
|
el.style.position = 'absolute'; |
||||
|
el.style.left = '0'; |
||||
|
el.style.right = '0'; |
||||
|
el.style.top = '0'; |
||||
|
el.style.bottom = '0'; |
||||
|
el.style.display = 'none'; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2019 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<div fxLayout="column" class="mat-content mat-padding"> |
||||
|
<mat-form-field> |
||||
|
<mat-label>{{'dashboard.select-dashboard' | translate}}</mat-label> |
||||
|
<mat-select matInput [(ngModel)]="dashboardId" |
||||
|
(ngModelChange)="dashboardSelected(dashboardId)"> |
||||
|
<mat-option *ngFor="let dashboard of dashboards$ | async" [value]="dashboard.id.id"> |
||||
|
{{dashboard.title}} |
||||
|
</mat-option> |
||||
|
</mat-select> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
@ -0,0 +1,55 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2019 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
@import "../../../scss/constants"; |
||||
|
|
||||
|
:host { |
||||
|
min-width: 300px; |
||||
|
max-width: 320px; |
||||
|
max-height: 150px; |
||||
|
overflow-x: hidden; |
||||
|
overflow-y: auto; |
||||
|
background: #fff; |
||||
|
border-radius: 4px; |
||||
|
box-shadow: |
||||
|
0 7px 8px -4px rgba(0, 0, 0, .2), |
||||
|
0 13px 19px 2px rgba(0, 0, 0, .14), |
||||
|
0 5px 24px 4px rgba(0, 0, 0, .12); |
||||
|
|
||||
|
@media (min-height: 350px) { |
||||
|
max-height: 250px; |
||||
|
} |
||||
|
|
||||
|
@media #{$mat-gt-xs} { |
||||
|
max-width: 100%; |
||||
|
} |
||||
|
|
||||
|
.mat-content { |
||||
|
background-color: #fff; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
:host ::ng-deep { |
||||
|
mat-form-field { |
||||
|
.mat-form-field-infix { |
||||
|
width: 100%; |
||||
|
mat-select { |
||||
|
.mat-select-value { |
||||
|
max-width: 100%; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2019 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { Component, Inject, InjectionToken } from '@angular/core'; |
||||
|
import { Observable } from 'rxjs'; |
||||
|
import { DashboardInfo } from '../models/dashboard.models'; |
||||
|
|
||||
|
export const DASHBOARD_SELECT_PANEL_DATA = new InjectionToken<any>('DashboardSelectPanelData'); |
||||
|
|
||||
|
export interface DashboardSelectPanelData { |
||||
|
dashboards$: Observable<Array<DashboardInfo>>; |
||||
|
dashboardId: string; |
||||
|
onDashboardSelected: (dashboardId: string) => void; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-dashboard-select-panel', |
||||
|
templateUrl: './dashboard-select-panel.component.html', |
||||
|
styleUrls: ['./dashboard-select-panel.component.scss'] |
||||
|
}) |
||||
|
export class DashboardSelectPanelComponent { |
||||
|
|
||||
|
dashboards$: Observable<Array<DashboardInfo>>; |
||||
|
dashboardId: string; |
||||
|
|
||||
|
constructor(@Inject(DASHBOARD_SELECT_PANEL_DATA) |
||||
|
private data: DashboardSelectPanelData) { |
||||
|
this.dashboards$ = this.data.dashboards$; |
||||
|
this.dashboardId = this.data.dashboardId; |
||||
|
} |
||||
|
|
||||
|
public dashboardSelected(dashboardId: string) { |
||||
|
this.data.onDashboardSelected(dashboardId); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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-select fxHide.xs fxHide.sm fxHide.md |
||||
|
[required]="required" |
||||
|
[disabled]="disabled" |
||||
|
[(ngModel)]="dashboardId" |
||||
|
(ngModelChange)="dashboardIdChanged()"> |
||||
|
<mat-option *ngFor="let dashboard of dashboards$ | async" [value]="dashboard.id.id"> |
||||
|
{{dashboard.title}} |
||||
|
</mat-option> |
||||
|
</mat-select> |
||||
|
<section fxHide.gt-md class="tb-dashboard-select"> |
||||
|
<button mat-button mat-icon-button |
||||
|
cdkOverlayOrigin #dashboardSelectPanelOrigin="cdkOverlayOrigin" |
||||
|
(click)="openDashboardSelectPanel()" |
||||
|
matTooltip="{{ 'dashboard.select-dashboard' | translate }}" |
||||
|
[matTooltipPosition]="tooltipPosition"> |
||||
|
<mat-icon>dashboards</mat-icon> |
||||
|
</button> |
||||
|
</section> |
||||
@ -0,0 +1,40 @@ |
|||||
|
/** |
||||
|
* 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 { |
||||
|
min-width: 52px; |
||||
|
|
||||
|
mat-select { |
||||
|
max-width: 300px; |
||||
|
pointer-events: all; |
||||
|
} |
||||
|
|
||||
|
.tb-dashboard-select { |
||||
|
min-height: 32px; |
||||
|
|
||||
|
span { |
||||
|
pointer-events: all; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
:host ::ng-deep { |
||||
|
mat-select { |
||||
|
.mat-select-value { |
||||
|
max-width: 282px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,214 @@ |
|||||
|
///
|
||||
|
/// 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, Inject, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; |
||||
|
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; |
||||
|
import { Observable, of } from 'rxjs'; |
||||
|
import { PageLink } from '@shared/models/page/page-link'; |
||||
|
import { map, share } from 'rxjs/operators'; |
||||
|
import { emptyPageData, PageData } from '@shared/models/page/page-data'; |
||||
|
import { DashboardInfo } from '@app/shared/models/dashboard.models'; |
||||
|
import { DashboardService } from '@core/http/dashboard.service'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@app/core/core.state'; |
||||
|
import { getCurrentAuthUser } from '@app/core/auth/auth.selectors'; |
||||
|
import { Authority } from '@shared/models/authority.enum'; |
||||
|
import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
||||
|
import { TooltipPosition } from '@angular/material/tooltip'; |
||||
|
import { CdkOverlayOrigin, ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; |
||||
|
import { BreakpointObserver } from '@angular/cdk/layout'; |
||||
|
import { DOCUMENT } from '@angular/common'; |
||||
|
import { WINDOW } from '@core/services/window.service'; |
||||
|
import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; |
||||
|
import { |
||||
|
DASHBOARD_SELECT_PANEL_DATA, |
||||
|
DashboardSelectPanelComponent, |
||||
|
DashboardSelectPanelData |
||||
|
} from './dashboard-select-panel.component'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-dashboard-select', |
||||
|
templateUrl: './dashboard-select.component.html', |
||||
|
styleUrls: ['./dashboard-select.component.scss'], |
||||
|
providers: [{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => DashboardSelectComponent), |
||||
|
multi: true |
||||
|
}] |
||||
|
}) |
||||
|
export class DashboardSelectComponent implements ControlValueAccessor, OnInit { |
||||
|
|
||||
|
@Input() |
||||
|
dashboardsScope: 'customer' | 'tenant'; |
||||
|
|
||||
|
@Input() |
||||
|
customerId: string; |
||||
|
|
||||
|
@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; |
||||
|
|
||||
|
dashboards$: Observable<Array<DashboardInfo>>; |
||||
|
|
||||
|
dashboardId: string | null; |
||||
|
|
||||
|
@ViewChild('dashboardSelectPanelOrigin', {static: false}) dashboardSelectPanelOrigin: CdkOverlayOrigin; |
||||
|
|
||||
|
private propagateChange = (v: any) => { }; |
||||
|
|
||||
|
constructor(private store: Store<AppState>, |
||||
|
private dashboardService: DashboardService, |
||||
|
private overlay: Overlay, |
||||
|
private breakpointObserver: BreakpointObserver, |
||||
|
private viewContainerRef: ViewContainerRef, |
||||
|
@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.dashboards$ = this.getDashboards(pageLink).pipe( |
||||
|
map((pageData) => pageData.data), |
||||
|
share() |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
setDisabledState(isDisabled: boolean): void { |
||||
|
this.disabled = isDisabled; |
||||
|
} |
||||
|
|
||||
|
writeValue(value: string | null): void { |
||||
|
this.dashboardId = value; |
||||
|
} |
||||
|
|
||||
|
dashboardIdChanged() { |
||||
|
this.updateView(); |
||||
|
} |
||||
|
|
||||
|
openDashboardSelectPanel() { |
||||
|
if (this.disabled) { |
||||
|
return; |
||||
|
} |
||||
|
const panelHeight = this.breakpointObserver.isMatched('min-height: 350px') ? 250 : 150; |
||||
|
const panelWidth = 300; |
||||
|
const position = this.overlay.position(); |
||||
|
const config = new OverlayConfig({ |
||||
|
panelClass: 'tb-dashboard-select-panel', |
||||
|
backdropClass: 'cdk-overlay-transparent-backdrop', |
||||
|
hasBackdrop: true, |
||||
|
}); |
||||
|
const el = this.dashboardSelectPanelOrigin.elementRef.nativeElement; |
||||
|
const offset = el.getBoundingClientRect(); |
||||
|
const scrollTop = this.window.pageYOffset || this.document.documentElement.scrollTop || this.document.body.scrollTop || 0; |
||||
|
const scrollLeft = this.window.pageXOffset || this.document.documentElement.scrollLeft || this.document.body.scrollLeft || 0; |
||||
|
const bottomY = offset.bottom - scrollTop; |
||||
|
const leftX = offset.left - scrollLeft; |
||||
|
let originX; |
||||
|
let originY; |
||||
|
let overlayX; |
||||
|
let overlayY; |
||||
|
const wHeight = this.document.documentElement.clientHeight; |
||||
|
const wWidth = this.document.documentElement.clientWidth; |
||||
|
if (bottomY + panelHeight > wHeight) { |
||||
|
originY = 'top'; |
||||
|
overlayY = 'bottom'; |
||||
|
} else { |
||||
|
originY = 'bottom'; |
||||
|
overlayY = 'top'; |
||||
|
} |
||||
|
if (leftX + panelWidth > wWidth) { |
||||
|
originX = 'end'; |
||||
|
overlayX = 'end'; |
||||
|
} else { |
||||
|
originX = 'start'; |
||||
|
overlayX = 'start'; |
||||
|
} |
||||
|
const connectedPosition: ConnectedPosition = { |
||||
|
originX, |
||||
|
originY, |
||||
|
overlayX, |
||||
|
overlayY |
||||
|
}; |
||||
|
config.positionStrategy = position.flexibleConnectedTo(this.dashboardSelectPanelOrigin.elementRef) |
||||
|
.withPositions([connectedPosition]); |
||||
|
const overlayRef = this.overlay.create(config); |
||||
|
overlayRef.backdropClick().subscribe(() => { |
||||
|
overlayRef.dispose(); |
||||
|
}); |
||||
|
|
||||
|
const injector = this._createDashboardSelectPanelInjector( |
||||
|
overlayRef, |
||||
|
{ |
||||
|
dashboards$: this.dashboards$, |
||||
|
dashboardId: this.dashboardId, |
||||
|
onDashboardSelected: (dashboardId) => { |
||||
|
overlayRef.dispose(); |
||||
|
this.dashboardId = dashboardId; |
||||
|
this.updateView(); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
overlayRef.attach(new ComponentPortal(DashboardSelectPanelComponent, this.viewContainerRef, injector)); |
||||
|
} |
||||
|
|
||||
|
private _createDashboardSelectPanelInjector(overlayRef: OverlayRef, data: DashboardSelectPanelData): PortalInjector { |
||||
|
const injectionTokens = new WeakMap<any, any>([ |
||||
|
[DASHBOARD_SELECT_PANEL_DATA, data], |
||||
|
[OverlayRef, overlayRef] |
||||
|
]); |
||||
|
return new PortalInjector(this.viewContainerRef.injector, injectionTokens); |
||||
|
} |
||||
|
|
||||
|
private updateView() { |
||||
|
this.propagateChange(this.dashboardId); |
||||
|
} |
||||
|
|
||||
|
private getDashboards(pageLink: PageLink): Observable<PageData<DashboardInfo>> { |
||||
|
let dashboardsObservable: Observable<PageData<DashboardInfo>>; |
||||
|
const authUser = getCurrentAuthUser(this.store); |
||||
|
if (this.dashboardsScope === 'customer' || authUser.authority === Authority.CUSTOMER_USER) { |
||||
|
if (this.customerId) { |
||||
|
dashboardsObservable = this.dashboardService.getCustomerDashboards(this.customerId, pageLink, false, true); |
||||
|
} else { |
||||
|
dashboardsObservable = of(emptyPageData()); |
||||
|
} |
||||
|
} else { |
||||
|
dashboardsObservable = this.dashboardService.getTenantDashboards(pageLink, false, true); |
||||
|
} |
||||
|
return dashboardsObservable; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2019 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<div class="mat-fab-toolbar-wrapper"> |
||||
|
<div class="mat-fab-toolbar-content"> |
||||
|
<ng-content></ng-content> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,187 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
$font-size: 10px !default; |
||||
|
@function rem($multiplier) { |
||||
|
@return $multiplier * $font-size; |
||||
|
} |
||||
|
|
||||
|
$button-fab-width: rem(5.600) !default; |
||||
|
$button-fab-height: rem(5.600) !default; |
||||
|
$button-fab-padding: rem(1.60) !default; |
||||
|
$icon-button-margin: rem(0.600) !default; |
||||
|
$z-index-fab: 20 !default; |
||||
|
|
||||
|
$swift-ease-in-duration: 0.3s !default; |
||||
|
$swift-ease-in-timing-function: cubic-bezier(0.55, 0, 0.55, 0.2) !default; |
||||
|
$swift-ease-in: all $swift-ease-in-duration $swift-ease-in-timing-function !default; |
||||
|
|
||||
|
@mixin rtl-prop($ltr-prop, $rtl-prop, $value, $reset-value) { |
||||
|
#{$ltr-prop}: $value; |
||||
|
[dir=rtl] & { |
||||
|
#{$ltr-prop}: $reset-value; |
||||
|
#{$rtl-prop}: $value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@mixin fab-position($spot, $top: auto, $right: auto, $bottom: auto, $left: auto) { |
||||
|
&.mat-fab-#{$spot} { |
||||
|
top: $top; |
||||
|
right: $right; |
||||
|
bottom: $bottom; |
||||
|
left: $left; |
||||
|
position: absolute; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@mixin fab-all-positions() { |
||||
|
@include fab-position(bottom-right, auto, ($button-fab-width - $button-fab-padding)/2, ($button-fab-height - $button-fab-padding)/2, auto); |
||||
|
@include fab-position(bottom-left, auto, auto, ($button-fab-height - $button-fab-padding)/2, ($button-fab-width - $button-fab-padding)/2); |
||||
|
@include fab-position(top-right, ($button-fab-height - $button-fab-padding)/2, ($button-fab-width - $button-fab-padding)/2, auto, auto); |
||||
|
@include fab-position(top-left, ($button-fab-height - $button-fab-padding)/2, auto, auto, ($button-fab-width - $button-fab-padding)/2); |
||||
|
} |
||||
|
|
||||
|
mat-fab-toolbar { |
||||
|
$icon-delay: 200ms; |
||||
|
@include fab-all-positions(); |
||||
|
display: block; |
||||
|
|
||||
|
.mat-fab-toolbar-wrapper { |
||||
|
display: block; |
||||
|
position: relative; |
||||
|
overflow: hidden; |
||||
|
|
||||
|
height: $button-fab-width + ($icon-button-margin * 2); |
||||
|
} |
||||
|
|
||||
|
mat-fab-trigger { |
||||
|
position: absolute; |
||||
|
z-index: $z-index-fab; |
||||
|
|
||||
|
button { |
||||
|
overflow: visible !important; |
||||
|
opacity: .5; |
||||
|
} |
||||
|
|
||||
|
.mat-fab-toolbar-background { |
||||
|
display: block; |
||||
|
position: absolute; |
||||
|
z-index: $z-index-fab + 1; |
||||
|
opacity: 1; |
||||
|
} |
||||
|
|
||||
|
mat-icon { |
||||
|
position: relative; |
||||
|
z-index: $z-index-fab + 2; |
||||
|
|
||||
|
opacity: 1; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
&.mat-left { |
||||
|
mat-fab-trigger { |
||||
|
@include rtl-prop(right, left, 0, auto); |
||||
|
} |
||||
|
|
||||
|
.mat-toolbar-tools { |
||||
|
flex-direction: row-reverse; |
||||
|
|
||||
|
> .mat-button:first-child { |
||||
|
@include rtl-prop(margin-right, margin-left, 0.6rem, auto) |
||||
|
} |
||||
|
|
||||
|
> .mat-button:first-child { |
||||
|
@include rtl-prop(margin-left, margin-right, -0.8rem, auto); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
> .mat-button:last-child { |
||||
|
@include rtl-prop(margin-right, margin-left, 8px, auto); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&.mat-right { |
||||
|
mat-fab-trigger { |
||||
|
@include rtl-prop(left, right, 0, auto); |
||||
|
} |
||||
|
|
||||
|
.mat-toolbar-tools { |
||||
|
flex-direction: row; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
mat-toolbar { |
||||
|
padding: 0 !important; |
||||
|
background-color: transparent !important; |
||||
|
pointer-events: none; |
||||
|
position: relative; |
||||
|
z-index: $z-index-fab + 3; |
||||
|
|
||||
|
.mat-toolbar-tools { |
||||
|
padding: 0 20px !important; |
||||
|
margin-top: 3px; |
||||
|
} |
||||
|
|
||||
|
.mat-fab-action-item { |
||||
|
opacity: 0; |
||||
|
transform: scale(0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&.mat-is-open { |
||||
|
mat-fab-trigger > button { |
||||
|
box-shadow: none; |
||||
|
opacity: 1; |
||||
|
|
||||
|
mat-icon { |
||||
|
opacity: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.mat-fab-action-item { |
||||
|
opacity: 1; |
||||
|
transform: scale(1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&.mat-animation { |
||||
|
mat-fab-trigger { |
||||
|
button { |
||||
|
transition: opacity .3s cubic-bezier(.55, 0, .55, .2) .2s; |
||||
|
} |
||||
|
.mat-fab-toolbar-background { |
||||
|
transition: $swift-ease-in; |
||||
|
} |
||||
|
mat-icon { |
||||
|
transition: all $icon-delay ease-in; |
||||
|
} |
||||
|
} |
||||
|
mat-toolbar { |
||||
|
.mat-fab-action-item { |
||||
|
transition: $swift-ease-in; |
||||
|
transition-duration: $swift-ease-in-duration / 2; |
||||
|
} |
||||
|
} |
||||
|
&.mat-is-open { |
||||
|
mat-fab-trigger > button { |
||||
|
transition: opacity .3s cubic-bezier(.55, 0, .55, .2); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,188 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2019 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { |
||||
|
Component, |
||||
|
ElementRef, |
||||
|
Input, |
||||
|
OnChanges, |
||||
|
OnInit, |
||||
|
Renderer2, |
||||
|
ViewEncapsulation, |
||||
|
SimpleChanges, |
||||
|
Inject, AfterViewInit, AfterViewChecked, Directive, OnDestroy |
||||
|
} from '@angular/core'; |
||||
|
import { PageComponent } from '@shared/components/page.component'; |
||||
|
import { WINDOW } from '@core/services/window.service'; |
||||
|
import { mixinColor, CanColorCtor } from '@angular/material'; |
||||
|
|
||||
|
export declare type FabToolbarDirection = 'left' | 'right'; |
||||
|
|
||||
|
class MatFabToolbarBase { |
||||
|
// tslint:disable-next-line:variable-name
|
||||
|
constructor(public _elementRef: ElementRef) {} |
||||
|
} |
||||
|
const MatFabToolbarMixinBase: CanColorCtor & typeof MatFabToolbarBase = mixinColor(MatFabToolbarBase); |
||||
|
|
||||
|
@Directive({ |
||||
|
selector: 'mat-fab-trigger' |
||||
|
}) |
||||
|
export class FabTriggerDirective { |
||||
|
|
||||
|
constructor(private el: ElementRef<HTMLElement>) { |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
@Directive({ |
||||
|
selector: 'mat-fab-actions' |
||||
|
}) |
||||
|
export class FabActionsDirective implements OnInit { |
||||
|
|
||||
|
constructor(private el: ElementRef<HTMLElement>) { |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
const element = $(this.el.nativeElement); |
||||
|
const children = element.children(); |
||||
|
children.wrap('<div class="mat-fab-action-item">'); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'mat-fab-toolbar', |
||||
|
templateUrl: './fab-toolbar.component.html', |
||||
|
styleUrls: ['./fab-toolbar.component.scss'], |
||||
|
inputs: ['color'], |
||||
|
encapsulation: ViewEncapsulation.None |
||||
|
}) |
||||
|
export class FabToolbarComponent extends MatFabToolbarMixinBase implements OnInit, OnDestroy, AfterViewInit, OnChanges { |
||||
|
|
||||
|
@Input() |
||||
|
isOpen: boolean; |
||||
|
|
||||
|
@Input() |
||||
|
direction: FabToolbarDirection; |
||||
|
|
||||
|
fabToolbarResizeListener = this.onFabToolbarResize.bind(this); |
||||
|
|
||||
|
constructor(private el: ElementRef<HTMLElement>, |
||||
|
@Inject(WINDOW) private window: Window) { |
||||
|
super(el); |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
const element = $(this.el.nativeElement); |
||||
|
element.addClass('mat-fab-toolbar'); |
||||
|
element.find('mat-fab-trigger').find('button') |
||||
|
.prepend('<div class="mat-fab-toolbar-background"></div>'); |
||||
|
element.addClass(`mat-${this.direction}`); |
||||
|
// @ts-ignore
|
||||
|
addResizeListener(this.el.nativeElement, this.fabToolbarResizeListener); |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy(): void { |
||||
|
// @ts-ignore
|
||||
|
removeResizeListener(this.el.nativeElement, this.fabToolbarResizeListener); |
||||
|
} |
||||
|
|
||||
|
ngAfterViewInit(): void { |
||||
|
this.triggerOpenClose(true); |
||||
|
} |
||||
|
|
||||
|
ngOnChanges(changes: SimpleChanges): void { |
||||
|
for (const propName of Object.keys(changes)) { |
||||
|
const change = changes[propName]; |
||||
|
if (!change.firstChange && change.currentValue !== change.previousValue) { |
||||
|
if (propName === 'isOpen') { |
||||
|
this.triggerOpenClose(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private onFabToolbarResize() { |
||||
|
if (this.isOpen) { |
||||
|
this.triggerOpenClose(true); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private triggerOpenClose(disableAnimation?: boolean): void { |
||||
|
const el = this.el.nativeElement; |
||||
|
const element = $(this.el.nativeElement); |
||||
|
if (disableAnimation) { |
||||
|
element.removeClass('mat-animation'); |
||||
|
} else { |
||||
|
element.addClass('mat-animation'); |
||||
|
} |
||||
|
const backgroundElement: HTMLElement = el.querySelector('.mat-fab-toolbar-background'); |
||||
|
const triggerElement: HTMLElement = el.querySelector('mat-fab-trigger button'); |
||||
|
const toolbarElement: HTMLElement = el.querySelector('mat-toolbar'); |
||||
|
const iconElement: HTMLElement = el.querySelector('mat-fab-trigger button mat-icon'); |
||||
|
const actions = element.find('mat-fab-actions').children(); |
||||
|
if (triggerElement && backgroundElement) { |
||||
|
const width = el.offsetWidth; |
||||
|
const height = el.offsetHeight; |
||||
|
const scale = 2 * (width / triggerElement.offsetWidth); |
||||
|
|
||||
|
backgroundElement.style.borderRadius = width + 'px'; |
||||
|
|
||||
|
if (this.isOpen) { |
||||
|
element.addClass('mat-is-open'); |
||||
|
toolbarElement.style.pointerEvents = 'inherit'; |
||||
|
|
||||
|
backgroundElement.style.width = triggerElement.offsetWidth + 'px'; |
||||
|
backgroundElement.style.height = triggerElement.offsetHeight + 'px'; |
||||
|
backgroundElement.style.transform = 'scale(' + scale + ')'; |
||||
|
|
||||
|
backgroundElement.style.transitionDelay = '0ms'; |
||||
|
if (iconElement) { |
||||
|
iconElement.style.transitionDelay = disableAnimation ? '0ms' : '.3s'; |
||||
|
} |
||||
|
|
||||
|
actions.each((index, action) => { |
||||
|
action.style.transitionDelay = disableAnimation ? '0ms' : ((actions.length - index) * 25 + 'ms'); |
||||
|
}); |
||||
|
|
||||
|
} else { |
||||
|
element.removeClass('mat-is-open'); |
||||
|
toolbarElement.style.pointerEvents = 'none'; |
||||
|
|
||||
|
backgroundElement.style.transform = 'scale(1)'; |
||||
|
|
||||
|
backgroundElement.style.top = '0'; |
||||
|
|
||||
|
if (element.hasClass('mat-right')) { |
||||
|
backgroundElement.style.left = '0'; |
||||
|
backgroundElement.style.right = null; |
||||
|
} |
||||
|
|
||||
|
if (element.hasClass('mat-left')) { |
||||
|
backgroundElement.style.right = '0'; |
||||
|
backgroundElement.style.left = null; |
||||
|
} |
||||
|
|
||||
|
backgroundElement.style.transitionDelay = disableAnimation ? '0ms' : '200ms'; |
||||
|
|
||||
|
actions.each((index, action) => { |
||||
|
action.style.transitionDelay = (disableAnimation ? 0 : 200) + (index * 25) + 'ms'; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,70 @@ |
|||||
|
///
|
||||
|
/// 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 { EntityType } from '@shared/models/entity-type.models'; |
||||
|
|
||||
|
export enum AliasFilterType { |
||||
|
singleEntity = 'singleEntity', |
||||
|
entityList = 'entityList', |
||||
|
entityName = 'entityName', |
||||
|
stateEntity = 'stateEntity', |
||||
|
assetType = 'assetType', |
||||
|
deviceType = 'deviceType', |
||||
|
entityViewType = 'entityViewType', |
||||
|
relationsQuery = 'relationsQuery', |
||||
|
assetSearchQuery = 'assetSearchQuery', |
||||
|
deviceSearchQuery = 'deviceSearchQuery', |
||||
|
entityViewSearchQuery = 'entityViewSearchQuery' |
||||
|
} |
||||
|
|
||||
|
export const aliasFilterTypeTranslationMap = new Map<AliasFilterType, string>( |
||||
|
[ |
||||
|
[ AliasFilterType.singleEntity, 'alias.filter-type-single-entity' ], |
||||
|
[ AliasFilterType.entityList, 'alias.filter-type-entity-list' ], |
||||
|
[ AliasFilterType.entityName, 'alias.filter-type-entity-name' ], |
||||
|
[ AliasFilterType.stateEntity, 'alias.filter-type-state-entity' ], |
||||
|
[ AliasFilterType.assetType, 'alias.filter-type-asset-type' ], |
||||
|
[ AliasFilterType.deviceType, 'alias.filter-type-device-type' ], |
||||
|
[ AliasFilterType.entityViewType, 'alias.filter-type-entity-view-type' ], |
||||
|
[ AliasFilterType.relationsQuery, 'alias.filter-type-relations-query' ], |
||||
|
[ AliasFilterType.assetSearchQuery, 'alias.filter-type-asset-search-query' ], |
||||
|
[ AliasFilterType.deviceSearchQuery, 'alias.filter-type-device-search-query' ], |
||||
|
[ AliasFilterType.entityViewSearchQuery, 'alias.filter-type-entity-view-search-query' ] |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
export interface EntityAliasFilter { |
||||
|
type: AliasFilterType; |
||||
|
entityType: EntityType; |
||||
|
resolveMultiple: boolean; |
||||
|
entityList?: string[]; |
||||
|
entityNameFilter?: string; |
||||
|
[key: string]: any; |
||||
|
// TODO:
|
||||
|
|
||||
|
} |
||||
|
|
||||
|
export interface EntityAlias { |
||||
|
id: string; |
||||
|
alias: string; |
||||
|
filter: EntityAliasFilter; |
||||
|
[key: string]: any; |
||||
|
// TODO:
|
||||
|
} |
||||
|
|
||||
|
export interface EntityAliases { |
||||
|
[id: string]: EntityAlias; |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
///
|
||||
|
/// 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 { BaseData } from '@shared/models/base-data'; |
||||
|
import { EntityType } from '@shared/models/entity-type.models'; |
||||
|
import { EntityId } from '@shared/models/id/entity-id'; |
||||
|
|
||||
|
export interface EntityInfo { |
||||
|
origEntity?: BaseData<EntityId>; |
||||
|
name?: string; |
||||
|
label?: string; |
||||
|
entityType?: EntityType; |
||||
|
id?: string; |
||||
|
entityDescription?: string; |
||||
|
} |
||||
|
After Width: | Height: | Size: 104 B |
|
After Width: | Height: | Size: 91 B |
@ -0,0 +1,39 @@ |
|||||
|
///
|
||||
|
/// 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.
|
||||
|
///
|
||||
|
|
||||
|
interface SplitOptions { |
||||
|
sizes?: number[]; |
||||
|
minSize?: number[] | number; |
||||
|
gutterSize?: number; |
||||
|
snapOffset?: number; |
||||
|
direction?: 'horizontal' | 'vertical'; |
||||
|
cursor?: 'col-resize' | 'row-resize'; |
||||
|
gutter?: (index: number, direction: string) => HTMLElement; |
||||
|
elementStyle?: (dimension: string, elementSize: number, gutterSize: number) => any; |
||||
|
gutterStyle?: (dimension: string, gutterSize: number) => any; |
||||
|
onDrag?: () => void; |
||||
|
onDragStart?: () => void; |
||||
|
onDragEnd?: () => void; |
||||
|
} |
||||
|
|
||||
|
interface SplitObject { |
||||
|
setSizes: (sizes: number[]) => void; |
||||
|
getSizes: () => number[]; |
||||
|
collapse: (index: number) => void; |
||||
|
destroy: () => void; |
||||
|
} |
||||
|
|
||||
|
declare function Split(elements: HTMLElement | string[], options?: SplitOptions): SplitObject; |
||||
Loading…
Reference in new issue