Add ability to set widget and dashboard level CSS. Introduce dashboard state component with ability to be embedded inside markdown widget.
This commit is contained in:
parent
6b9bdba92f
commit
3874f8ff46
@ -17,7 +17,7 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component, ElementRef,
|
||||
Component, ElementRef, HostBinding,
|
||||
Inject,
|
||||
Injector,
|
||||
Input,
|
||||
@ -48,7 +48,7 @@ import {
|
||||
} from '@app/shared/models/dashboard.models';
|
||||
import { WINDOW } from '@core/services/window.service';
|
||||
import { WindowMessage } from '@shared/models/window-message.model';
|
||||
import { deepClone, isDefined, isDefinedAndNotNull } from '@app/core/utils';
|
||||
import { deepClone, guid, hashCode, isDefined, isDefinedAndNotNull, isNotEmptyStr } from '@app/core/utils';
|
||||
import {
|
||||
DashboardContext,
|
||||
DashboardPageLayout,
|
||||
@ -132,6 +132,8 @@ import {
|
||||
DashboardImageDialogData, DashboardImageDialogResult
|
||||
} from '@home/components/dashboard-page/dashboard-image-dialog.component';
|
||||
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
|
||||
import cssjs from '@core/css/css';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
|
||||
// @dynamic
|
||||
@Component({
|
||||
@ -147,6 +149,9 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
|
||||
|
||||
authUser: AuthUser = this.authState.authUser;
|
||||
|
||||
@HostBinding('class')
|
||||
dashboardPageClass: string;
|
||||
|
||||
@Input()
|
||||
embedded = false;
|
||||
|
||||
@ -302,6 +307,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
|
||||
|
||||
constructor(protected store: Store<AppState>,
|
||||
@Inject(WINDOW) private window: Window,
|
||||
@Inject(DOCUMENT) private document: Document,
|
||||
private breakpointObserver: BreakpointObserver,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
@ -419,6 +425,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
|
||||
this.dashboardConfiguration.entityAliases,
|
||||
this.dashboardConfiguration.filters);
|
||||
|
||||
this.updateDashboardCss();
|
||||
|
||||
if (this.widgetEditMode) {
|
||||
const message: WindowMessage = {
|
||||
type: 'widgetEditModeInited'
|
||||
@ -427,6 +435,27 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
|
||||
}
|
||||
}
|
||||
|
||||
private updateDashboardCss() {
|
||||
this.cleanupDashboardCss();
|
||||
const cssString = this.dashboardConfiguration.settings.dashboardCss;
|
||||
if (isNotEmptyStr(cssString)) {
|
||||
const cssParser = new cssjs();
|
||||
cssParser.testMode = false;
|
||||
this.dashboardPageClass = 'tb-dashboard-page-css-' + guid();
|
||||
cssParser.cssPreviewNamespace = this.dashboardPageClass;
|
||||
cssParser.createStyleElement(this.dashboardPageClass, cssString);
|
||||
}
|
||||
}
|
||||
|
||||
private cleanupDashboardCss() {
|
||||
if (this.dashboardPageClass) {
|
||||
const el = this.document.getElementById(this.dashboardPageClass);
|
||||
if (el) {
|
||||
el.parentNode.removeChild(el);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private reset() {
|
||||
this.dashboard = null;
|
||||
this.translatedDashboardTitle = null;
|
||||
@ -466,6 +495,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.cleanupDashboardCss();
|
||||
if (this.isMobileApp && this.syncStateWithQueryParam) {
|
||||
this.mobileService.unregisterToggleLayoutFunction();
|
||||
}
|
||||
@ -729,6 +759,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
|
||||
if (data) {
|
||||
this.dashboard.configuration.settings = data.settings;
|
||||
this.dashboardLogoCache = undefined;
|
||||
this.updateDashboardCss();
|
||||
const newGridSettings = data.gridSettings;
|
||||
if (newGridSettings) {
|
||||
const layout = this.dashboard.configuration.states[layoutKeys.state].layouts[layoutKeys.layout];
|
||||
@ -893,6 +924,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
|
||||
this.dashboardLogoCache = undefined;
|
||||
this.dashboardConfiguration = this.dashboard.configuration;
|
||||
this.dashboardCtx.dashboardTimewindow = this.dashboardConfiguration.timewindow;
|
||||
this.updateDashboardCss();
|
||||
this.entityAliasesUpdated();
|
||||
this.filtersUpdated();
|
||||
this.updateLayouts();
|
||||
|
||||
@ -87,6 +87,19 @@
|
||||
{{ 'dashboard.display-update-dashboard-image' | translate }}
|
||||
</mat-slide-toggle>
|
||||
</fieldset>
|
||||
<mat-expansion-panel class="tb-settings">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-description fxLayoutAlign="end" translate>
|
||||
dashboard.advanced-settings
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
<ng-template matExpansionPanelContent>
|
||||
<tb-css
|
||||
label="{{ 'dashboard.dashboard-css' | translate }}"
|
||||
formControlName="dashboardCss"
|
||||
></tb-css>
|
||||
</ng-template>
|
||||
</mat-expansion-panel>
|
||||
</div>
|
||||
<div *ngIf="gridSettings" [formGroup]="gridSettingsFormGroup" fxLayout="column">
|
||||
<fieldset class="fields-group" fxLayout="column" fxLayoutGap="8px">
|
||||
|
||||
@ -30,4 +30,33 @@
|
||||
.mat-slide-toggle-content {
|
||||
white-space: normal;
|
||||
}
|
||||
.mat-expansion-panel {
|
||||
&.tb-settings {
|
||||
box-shadow: none;
|
||||
.mat-content {
|
||||
overflow: visible;
|
||||
}
|
||||
.mat-expansion-panel-header {
|
||||
padding: 0;
|
||||
&:hover {
|
||||
background: none;
|
||||
}
|
||||
.mat-expansion-indicator {
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
.mat-expansion-panel-header-description {
|
||||
align-items: center;
|
||||
}
|
||||
.mat-expansion-panel-body{
|
||||
padding: 0;
|
||||
}
|
||||
.tb-css-content-panel {
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
}
|
||||
.mat-expansion-panel-content {
|
||||
font: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,7 +97,8 @@ export class DashboardSettingsDialogComponent extends DialogComponent<DashboardS
|
||||
disabled: hideToolbar}, []],
|
||||
showUpdateDashboardImage: [
|
||||
{value: isUndefined(this.settings.showUpdateDashboardImage) ? true : this.settings.showUpdateDashboardImage,
|
||||
disabled: hideToolbar}, []]
|
||||
disabled: hideToolbar}, []],
|
||||
dashboardCss: [isUndefined(this.settings.dashboardCss) ? '' : this.settings.dashboardCss, []],
|
||||
});
|
||||
this.settingsFormGroup.get('stateControllerId').valueChanges.subscribe(
|
||||
(stateControllerId: StateControllerId) => {
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
<!--
|
||||
|
||||
Copyright © 2016-2021 The Thingsboard Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<tb-dashboard-page
|
||||
[embedded]="true"
|
||||
[syncStateWithQueryParam]="false"
|
||||
[hideToolbar]="true"
|
||||
[currentState]="currentState"
|
||||
[dashboard]="dashboard"
|
||||
[parentDashboard]="parentDashboard">
|
||||
</tb-dashboard-page>
|
||||
@ -0,0 +1,70 @@
|
||||
///
|
||||
/// Copyright © 2016-2021 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, Input, OnInit } from '@angular/core';
|
||||
import { PageComponent } from '@shared/components/page.component';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState } from '@core/core.state';
|
||||
import { Dashboard } from '@shared/models/dashboard.models';
|
||||
import { StateObject, StateParams } from '@core/api/widget-api.models';
|
||||
import { updateEntityParams, WidgetContext } from '../../models/widget-component.models';
|
||||
import { deepClone, objToBase64 } from '@core/utils';
|
||||
import { IDashboardComponent } from '@home/models/dashboard-component.models';
|
||||
import { EntityId } from '@shared/models/id/entity-id';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-dashboard-state',
|
||||
templateUrl: './dashboard-state.component.html',
|
||||
styleUrls: []
|
||||
})
|
||||
export class DashboardStateComponent extends PageComponent implements OnInit {
|
||||
|
||||
@Input()
|
||||
ctx: WidgetContext;
|
||||
|
||||
@Input()
|
||||
stateId: string;
|
||||
|
||||
@Input()
|
||||
entityParamName: string;
|
||||
|
||||
@Input()
|
||||
entityId: EntityId;
|
||||
|
||||
currentState: string;
|
||||
|
||||
dashboard: Dashboard;
|
||||
|
||||
parentDashboard: IDashboardComponent;
|
||||
|
||||
constructor(protected store: Store<AppState>) {
|
||||
super(store);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.dashboard = deepClone(this.ctx.stateController.dashboardCtrl.dashboardCtx.getDashboard());
|
||||
const stateObject: StateObject = {};
|
||||
const params = deepClone(this.ctx.stateController.getStateParams());
|
||||
updateEntityParams(params, this.entityParamName, this.entityId);
|
||||
stateObject.params = params;
|
||||
if (this.stateId) {
|
||||
stateObject.id = this.stateId;
|
||||
}
|
||||
this.currentState = objToBase64([stateObject]);
|
||||
this.parentDashboard = this.ctx.parentDashboard ?
|
||||
this.ctx.parentDashboard : this.ctx.dashboard;
|
||||
}
|
||||
}
|
||||
@ -63,8 +63,9 @@
|
||||
</mat-menu>
|
||||
<div [ngClass]="dashboardClass" id="gridster-background" style="height: auto; min-height: 100%; display: inline;">
|
||||
<gridster #gridster id="gridster-child" [options]="gridsterOpts">
|
||||
<gridster-item [item]="widget" [ngClass]="{'tb-noselect': isEdit}" *ngFor="let widget of dashboardWidgets">
|
||||
<gridster-item #gridsterItem [item]="widget" [ngClass]="{'tb-noselect': isEdit}" *ngFor="let widget of dashboardWidgets">
|
||||
<tb-widget-container
|
||||
[gridsterItem]="gridsterItem"
|
||||
[widget]="widget"
|
||||
[dashboardWidgets]="dashboardWidgets"
|
||||
[dashboardStyle]="dashboardStyle"
|
||||
|
||||
@ -143,8 +143,10 @@ import { DeviceCredentialsModule } from '@home/components/device/device-credenti
|
||||
import { DeviceProfileCommonModule } from '@home/components/profile/device/common/device-profile-common.module';
|
||||
import {
|
||||
COMPLEX_FILTER_PREDICATE_DIALOG_COMPONENT_TOKEN,
|
||||
DASHBOARD_PAGE_COMPONENT_TOKEN
|
||||
DASHBOARD_PAGE_COMPONENT_TOKEN,
|
||||
HOME_COMPONENTS_MODULE_TOKEN
|
||||
} from '@home/components/tokens';
|
||||
import { DashboardStateComponent } from '@home/components/dashboard-page/dashboard-state.component';
|
||||
|
||||
@NgModule({
|
||||
declarations:
|
||||
@ -252,6 +254,7 @@ import {
|
||||
TwilioSmsProviderConfigurationComponent,
|
||||
DashboardToolbarComponent,
|
||||
DashboardPageComponent,
|
||||
DashboardStateComponent,
|
||||
DashboardLayoutComponent,
|
||||
EditWidgetComponent,
|
||||
DashboardWidgetSelectComponent,
|
||||
@ -363,6 +366,7 @@ import {
|
||||
TwilioSmsProviderConfigurationComponent,
|
||||
DashboardToolbarComponent,
|
||||
DashboardPageComponent,
|
||||
DashboardStateComponent,
|
||||
DashboardLayoutComponent,
|
||||
EditWidgetComponent,
|
||||
DashboardWidgetSelectComponent,
|
||||
@ -381,7 +385,8 @@ import {
|
||||
ImportExportService,
|
||||
{provide: EMBED_DASHBOARD_DIALOG_TOKEN, useValue: EmbedDashboardDialogComponent},
|
||||
{provide: COMPLEX_FILTER_PREDICATE_DIALOG_COMPONENT_TOKEN, useValue: ComplexFilterPredicateDialogComponent},
|
||||
{provide: DASHBOARD_PAGE_COMPONENT_TOKEN, useValue: DashboardPageComponent}
|
||||
{provide: DASHBOARD_PAGE_COMPONENT_TOKEN, useValue: DashboardPageComponent},
|
||||
{provide: HOME_COMPONENTS_MODULE_TOKEN, useValue: HomeComponentsModule }
|
||||
]
|
||||
})
|
||||
export class HomeComponentsModule { }
|
||||
|
||||
@ -20,6 +20,9 @@ import { ComponentType } from '@angular/cdk/portal';
|
||||
export const SHARED_HOME_COMPONENTS_MODULE_TOKEN: InjectionToken<Type<any>> =
|
||||
new InjectionToken<Type<any>>('SHARED_HOME_COMPONENTS_MODULE_TOKEN');
|
||||
|
||||
export const HOME_COMPONENTS_MODULE_TOKEN: InjectionToken<Type<any>> =
|
||||
new InjectionToken<Type<any>>('HOME_COMPONENTS_MODULE_TOKEN');
|
||||
|
||||
export const COMPLEX_FILTER_PREDICATE_DIALOG_COMPONENT_TOKEN: InjectionToken<ComponentType<any>> =
|
||||
new InjectionToken<ComponentType<any>>('COMPLEX_FILTER_PREDICATE_DIALOG_COMPONENT_TOKEN');
|
||||
|
||||
|
||||
@ -15,4 +15,5 @@
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<tb-markdown [data]="markdownText" lineNumbers fallbackToPlainMarkdown (click)="markdownClick($event)"></tb-markdown>
|
||||
<tb-markdown [data]="markdownText" [additionalCompileModules]="[ homeComponentsModule ]"
|
||||
[context]="{ ctx: ctx }" lineNumbers fallbackToPlainMarkdown (click)="markdownClick($event)"></tb-markdown>
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import { ChangeDetectorRef, Component, HostBinding, Input, OnInit } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, HostBinding, Inject, Input, OnInit, Type } from '@angular/core';
|
||||
import { PageComponent } from '@shared/components/page.component';
|
||||
import { WidgetContext } from '@home/models/widget-component.models';
|
||||
import { Store } from '@ngrx/store';
|
||||
@ -32,6 +32,7 @@ import { FormattedData } from '@home/components/widget/lib/maps/map-models';
|
||||
import { hashCode, isNotEmptyStr } from '@core/utils';
|
||||
import cssjs from '@core/css/css';
|
||||
import { UtilsService } from '@core/services/utils.service';
|
||||
import { HOME_COMPONENTS_MODULE_TOKEN } from '@home/components/tokens';
|
||||
|
||||
interface MarkdownWidgetSettings {
|
||||
markdownTextPattern: string;
|
||||
@ -62,6 +63,7 @@ export class MarkdownWidgetComponent extends PageComponent implements OnInit {
|
||||
|
||||
constructor(protected store: Store<AppState>,
|
||||
private utils: UtilsService,
|
||||
@Inject(HOME_COMPONENTS_MODULE_TOKEN) public homeComponentsModule: Type<any>,
|
||||
private cd: ChangeDetectorRef) {
|
||||
super(store);
|
||||
}
|
||||
|
||||
@ -45,6 +45,7 @@ import { MODULES_MAP } from '@shared/public-api';
|
||||
import * as tinycolor_ from 'tinycolor2';
|
||||
import moment from 'moment';
|
||||
import { IModulesMap } from '@modules/common/modules-map.models';
|
||||
import { HOME_COMPONENTS_MODULE_TOKEN } from '@home/components/tokens';
|
||||
|
||||
const tinycolor = tinycolor_;
|
||||
|
||||
@ -66,6 +67,7 @@ export class WidgetComponentService {
|
||||
|
||||
constructor(@Inject(WINDOW) private window: Window,
|
||||
@Optional() @Inject(MODULES_MAP) private modulesMap: IModulesMap,
|
||||
@Inject(HOME_COMPONENTS_MODULE_TOKEN) private homeComponentsModule: Type<any>,
|
||||
private dynamicComponentFactoryService: DynamicComponentFactoryService,
|
||||
private widgetService: WidgetService,
|
||||
private utils: UtilsService,
|
||||
@ -177,8 +179,10 @@ export class WidgetComponentService {
|
||||
forkJoin(widgetModulesTasks).subscribe(
|
||||
() => {
|
||||
const loadDefaultWidgetInfoTasks = [
|
||||
this.loadWidgetResources(this.missingWidgetType, 'global-widget-missing-type', [SharedModule, WidgetComponentsModule]),
|
||||
this.loadWidgetResources(this.errorWidgetType, 'global-widget-error-type', [SharedModule, WidgetComponentsModule]),
|
||||
this.loadWidgetResources(this.missingWidgetType, 'global-widget-missing-type',
|
||||
[SharedModule, WidgetComponentsModule, this.homeComponentsModule]),
|
||||
this.loadWidgetResources(this.errorWidgetType, 'global-widget-error-type',
|
||||
[SharedModule, WidgetComponentsModule, this.homeComponentsModule]),
|
||||
];
|
||||
forkJoin(loadDefaultWidgetInfoTasks).subscribe(
|
||||
() => {
|
||||
@ -274,7 +278,7 @@ export class WidgetComponentService {
|
||||
}
|
||||
if (widgetControllerDescriptor) {
|
||||
const widgetNamespace = `widget-type-${(isSystem ? 'sys-' : '')}${bundleAlias}-${widgetInfo.alias}`;
|
||||
this.loadWidgetResources(widgetInfo, widgetNamespace, [SharedModule, WidgetComponentsModule]).subscribe(
|
||||
this.loadWidgetResources(widgetInfo, widgetNamespace, [SharedModule, WidgetComponentsModule, this.homeComponentsModule]).subscribe(
|
||||
() => {
|
||||
if (widgetControllerDescriptor.settingsSchema) {
|
||||
widgetInfo.typeSettingsSchema = widgetControllerDescriptor.settingsSchema;
|
||||
|
||||
@ -418,6 +418,10 @@
|
||||
label="{{ 'widget-config.widget-style' | translate }}"
|
||||
formControlName="widgetStyle"
|
||||
></tb-json-object-edit>
|
||||
<tb-css
|
||||
label="{{ 'widget-config.widget-css' | translate }}"
|
||||
formControlName="widgetCss"
|
||||
></tb-css>
|
||||
</ng-template>
|
||||
</mat-expansion-panel>
|
||||
</fieldset>
|
||||
|
||||
@ -133,7 +133,7 @@
|
||||
.mat-expansion-panel-body{
|
||||
padding: 0;
|
||||
}
|
||||
.tb-json-object-panel {
|
||||
.tb-json-object-panel, .tb-css-content-panel {
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
.mat-checkbox-layout {
|
||||
|
||||
@ -206,6 +206,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
|
||||
padding: [null, []],
|
||||
margin: [null, []],
|
||||
widgetStyle: [null, []],
|
||||
widgetCss: [null, []],
|
||||
titleStyle: [null, []],
|
||||
units: [null, []],
|
||||
decimals: [null, [Validators.min(0), Validators.max(15), Validators.pattern(/^\d*$/)]],
|
||||
@ -406,6 +407,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
|
||||
padding: config.padding,
|
||||
margin: config.margin,
|
||||
widgetStyle: isDefined(config.widgetStyle) ? config.widgetStyle : {},
|
||||
widgetCss: isDefined(config.widgetCss) ? config.widgetCss : '',
|
||||
titleStyle: isDefined(config.titleStyle) ? config.titleStyle : {
|
||||
fontSize: '16px',
|
||||
fontWeight: 400
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<div tb-fullscreen [fullscreen]="widget.isFullscreen"
|
||||
<div #tbWidgetElement tb-fullscreen [fullscreen]="widget.isFullscreen"
|
||||
[fullscreenBackgroundStyle]="dashboardStyle"
|
||||
[fullscreenBackgroundImage]="backgroundImage"
|
||||
(fullscreenChanged)="onFullscreenChanged($event)"
|
||||
|
||||
@ -17,17 +17,21 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
Component, ElementRef,
|
||||
EventEmitter, HostBinding, Inject,
|
||||
Input, OnDestroy,
|
||||
OnInit,
|
||||
Output
|
||||
Output, Renderer2, ViewChild
|
||||
} from '@angular/core';
|
||||
import { PageComponent } from '@shared/components/page.component';
|
||||
import { DashboardWidget, DashboardWidgets } from '@home/models/dashboard-component.models';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState } from '@core/core.state';
|
||||
import { SafeStyle } from '@angular/platform-browser';
|
||||
import { guid, hashCode, isNotEmptyStr } from '@core/utils';
|
||||
import cssjs from '@core/css/css';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { GridsterItemComponent } from 'angular-gridster2';
|
||||
|
||||
export enum WidgetComponentActionType {
|
||||
MOUSE_DOWN,
|
||||
@ -43,13 +47,23 @@ export class WidgetComponentAction {
|
||||
actionType: WidgetComponentActionType;
|
||||
}
|
||||
|
||||
// @dynamic
|
||||
@Component({
|
||||
selector: 'tb-widget-container',
|
||||
templateUrl: './widget-container.component.html',
|
||||
styleUrls: ['./widget-container.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class WidgetContainerComponent extends PageComponent implements OnInit {
|
||||
export class WidgetContainerComponent extends PageComponent implements OnInit, OnDestroy {
|
||||
|
||||
@HostBinding('class')
|
||||
widgetContainerClass = 'tb-widget-container';
|
||||
|
||||
@ViewChild('tbWidgetElement', {static: true})
|
||||
tbWidgetElement: ElementRef;
|
||||
|
||||
@Input()
|
||||
gridsterItem: GridsterItemComponent;
|
||||
|
||||
@Input()
|
||||
widget: DashboardWidget;
|
||||
@ -87,13 +101,35 @@ export class WidgetContainerComponent extends PageComponent implements OnInit {
|
||||
@Output()
|
||||
widgetComponentAction: EventEmitter<WidgetComponentAction> = new EventEmitter<WidgetComponentAction>();
|
||||
|
||||
private cssClass: string;
|
||||
|
||||
constructor(protected store: Store<AppState>,
|
||||
private cd: ChangeDetectorRef) {
|
||||
private cd: ChangeDetectorRef,
|
||||
private renderer: Renderer2,
|
||||
@Inject(DOCUMENT) private document: Document) {
|
||||
super(store);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.widget.widgetContext.containerChangeDetector = this.cd;
|
||||
const cssString = this.widget.widget.config.widgetCss;
|
||||
if (isNotEmptyStr(cssString)) {
|
||||
const cssParser = new cssjs();
|
||||
cssParser.testMode = false;
|
||||
this.cssClass = 'tb-widget-css-' + guid();
|
||||
this.renderer.addClass(this.gridsterItem.el, this.cssClass);
|
||||
cssParser.cssPreviewNamespace = this.cssClass;
|
||||
cssParser.createStyleElement(this.cssClass, cssString);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.cssClass) {
|
||||
const el = this.document.getElementById(this.cssClass);
|
||||
if (el) {
|
||||
el.parentNode.removeChild(el);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isHighlighted(widget: DashboardWidget) {
|
||||
@ -105,6 +141,11 @@ export class WidgetContainerComponent extends PageComponent implements OnInit {
|
||||
}
|
||||
|
||||
onFullscreenChanged(expanded: boolean) {
|
||||
if (expanded) {
|
||||
this.renderer.addClass(this.tbWidgetElement.nativeElement, this.cssClass);
|
||||
} else {
|
||||
this.renderer.removeClass(this.tbWidgetElement.nativeElement, this.cssClass);
|
||||
}
|
||||
this.widgetFullscreenChanged.emit(expanded);
|
||||
}
|
||||
|
||||
|
||||
@ -65,7 +65,7 @@ import {
|
||||
validateEntityId
|
||||
} from '@core/utils';
|
||||
import {
|
||||
IDynamicWidgetComponent, ShowWidgetHeaderActionFunction,
|
||||
IDynamicWidgetComponent, ShowWidgetHeaderActionFunction, updateEntityParams,
|
||||
WidgetContext,
|
||||
WidgetHeaderAction,
|
||||
WidgetInfo,
|
||||
@ -1070,7 +1070,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
|
||||
case WidgetActionType.updateDashboardState:
|
||||
let targetDashboardStateId = descriptor.targetDashboardStateId;
|
||||
const params = deepClone(this.widgetContext.stateController.getStateParams());
|
||||
this.updateEntityParams(params, targetEntityParamName, targetEntityId, entityName, entityLabel);
|
||||
updateEntityParams(params, targetEntityParamName, targetEntityId, entityName, entityLabel);
|
||||
if (type === WidgetActionType.openDashboardState) {
|
||||
if (descriptor.openInPopover) {
|
||||
this.openDashboardStateInPopover($event, descriptor.targetDashboardStateId, params,
|
||||
@ -1091,7 +1091,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
|
||||
targetDashboardStateId = descriptor.targetDashboardStateId;
|
||||
const stateObject: StateObject = {};
|
||||
stateObject.params = {};
|
||||
this.updateEntityParams(stateObject.params, targetEntityParamName, targetEntityId, entityName, entityLabel);
|
||||
updateEntityParams(stateObject.params, targetEntityParamName, targetEntityId, entityName, entityLabel);
|
||||
if (targetDashboardStateId) {
|
||||
stateObject.id = targetDashboardStateId;
|
||||
}
|
||||
@ -1360,7 +1360,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
|
||||
this.widgetContentContainer, this.dashboardPageComponent, preferredPlacement, hideOnClickOutside,
|
||||
injector,
|
||||
{
|
||||
embed: true,
|
||||
embedded: true,
|
||||
syncStateWithQueryParam: false,
|
||||
hideToolbar: hideDashboardToolbar,
|
||||
currentState: objToBase64([stateObject]),
|
||||
@ -1443,30 +1443,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
|
||||
}
|
||||
}
|
||||
|
||||
private updateEntityParams(params: StateParams, targetEntityParamName?: string, targetEntityId?: EntityId,
|
||||
entityName?: string, entityLabel?: string) {
|
||||
if (targetEntityId) {
|
||||
let targetEntityParams: StateParams;
|
||||
if (targetEntityParamName && targetEntityParamName.length) {
|
||||
targetEntityParams = params[targetEntityParamName];
|
||||
if (!targetEntityParams) {
|
||||
targetEntityParams = {};
|
||||
params[targetEntityParamName] = targetEntityParams;
|
||||
params.targetEntityParamName = targetEntityParamName;
|
||||
}
|
||||
} else {
|
||||
targetEntityParams = params;
|
||||
}
|
||||
targetEntityParams.entityId = targetEntityId;
|
||||
if (entityName) {
|
||||
targetEntityParams.entityName = entityName;
|
||||
}
|
||||
if (entityLabel) {
|
||||
targetEntityParams.entityLabel = entityLabel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private loadCustomActionResources(actionNamespace: string, customCss: string, customResources: Array<WidgetResource>): Observable<any> {
|
||||
if (isDefined(customCss) && customCss.length > 0) {
|
||||
this.cssParser.cssPreviewNamespace = actionNamespace;
|
||||
|
||||
@ -37,7 +37,7 @@ import {
|
||||
IStateController,
|
||||
IWidgetSubscription,
|
||||
IWidgetUtils,
|
||||
RpcApi,
|
||||
RpcApi, StateParams,
|
||||
SubscriptionEntityInfo,
|
||||
TimewindowFunctions,
|
||||
WidgetActionsApi,
|
||||
@ -81,6 +81,7 @@ import { Router } from '@angular/router';
|
||||
import { catchError, map, mergeMap, switchMap } from 'rxjs/operators';
|
||||
import { FormattedData } from '@home/components/widget/lib/maps/map-models';
|
||||
import { TbPopoverComponent } from '@shared/components/popover.component';
|
||||
import { EntityId } from '@shared/models/id/entity-id';
|
||||
|
||||
export interface IWidgetAction {
|
||||
name: string;
|
||||
@ -545,3 +546,27 @@ export function toWidgetType(widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId:
|
||||
descriptor
|
||||
};
|
||||
}
|
||||
|
||||
export function updateEntityParams(params: StateParams, targetEntityParamName?: string, targetEntityId?: EntityId,
|
||||
entityName?: string, entityLabel?: string) {
|
||||
if (targetEntityId) {
|
||||
let targetEntityParams: StateParams;
|
||||
if (targetEntityParamName && targetEntityParamName.length) {
|
||||
targetEntityParams = params[targetEntityParamName];
|
||||
if (!targetEntityParams) {
|
||||
targetEntityParams = {};
|
||||
params[targetEntityParamName] = targetEntityParams;
|
||||
params.targetEntityParamName = targetEntityParamName;
|
||||
}
|
||||
} else {
|
||||
targetEntityParams = params;
|
||||
}
|
||||
targetEntityParams.entityId = targetEntityId;
|
||||
if (entityName) {
|
||||
targetEntityParams.entityName = entityName;
|
||||
}
|
||||
if (entityLabel) {
|
||||
targetEntityParams.entityLabel = entityLabel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
41
ui-ngx/src/app/shared/components/css.component.html
Normal file
41
ui-ngx/src/app/shared/components/css.component.html
Normal file
@ -0,0 +1,41 @@
|
||||
<!--
|
||||
|
||||
Copyright © 2016-2021 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-css" style="background: #fff;" [ngClass]="{'tb-disabled': disabled, 'fill-height': fillHeight}"
|
||||
tb-fullscreen
|
||||
[fullscreen]="fullscreen" fxLayout="column">
|
||||
<div fxLayout="row" fxLayoutAlign="start center" style="height: 40px;" class="tb-css-toolbar">
|
||||
<label class="tb-title no-padding" [ngClass]="{'tb-error': hasErrors}">{{ label }}</label>
|
||||
<span fxFlex></span>
|
||||
<button type='button' *ngIf="!disabled" mat-button class="tidy" (click)="beautifyCss()">
|
||||
{{'js-func.tidy' | translate }}
|
||||
</button>
|
||||
<fieldset style="width: initial">
|
||||
<div matTooltip="{{(fullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}"
|
||||
matTooltipPosition="above"
|
||||
style="border-radius: 50%"
|
||||
(click)="fullscreen = !fullscreen">
|
||||
<button type='button' mat-button mat-icon-button class="tb-mat-32">
|
||||
<mat-icon class="material-icons">{{ fullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div id="tb-css-panel" class="tb-css-content-panel" fxLayout="column">
|
||||
<div #cssEditor id="tb-css-input" [ngClass]="{'fill-height': fillHeight}"></div>
|
||||
</div>
|
||||
</div>
|
||||
66
ui-ngx/src/app/shared/components/css.component.scss
Normal file
66
ui-ngx/src/app/shared/components/css.component.scss
Normal file
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright © 2016-2021 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
.tb-css {
|
||||
position: relative;
|
||||
|
||||
&.tb-disabled {
|
||||
color: rgba(0, 0, 0, .38);
|
||||
}
|
||||
|
||||
&.fill-height {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tb-css-content-panel {
|
||||
height: calc(100% - 80px);
|
||||
margin-left: 15px;
|
||||
border: 1px solid #c0c0c0;
|
||||
|
||||
#tb-css-input {
|
||||
width: 100%;
|
||||
min-width: 200px;
|
||||
height: 100%;
|
||||
|
||||
&:not(.fill-height) {
|
||||
min-height: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tb-css-toolbar {
|
||||
& > * {
|
||||
&:not(:last-child) {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
button.mat-button, button.mat-icon-button, button.mat-icon-button.tb-mat-32 {
|
||||
background: rgba(220, 220, 220, .35);
|
||||
align-items: center;
|
||||
vertical-align: middle;
|
||||
min-width: 32px;
|
||||
min-height: 15px;
|
||||
padding: 4px;
|
||||
font-size: .8rem;
|
||||
line-height: 15px;
|
||||
&:not(.tb-help-popup-button) {
|
||||
color: #7b7b7b;
|
||||
}
|
||||
}
|
||||
.tb-help-popup-button-loading {
|
||||
background: #f3f3f3;
|
||||
}
|
||||
}
|
||||
}
|
||||
207
ui-ngx/src/app/shared/components/css.component.ts
Normal file
207
ui-ngx/src/app/shared/components/css.component.ts
Normal file
@ -0,0 +1,207 @@
|
||||
///
|
||||
/// Copyright © 2016-2021 The Thingsboard Authors
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import {
|
||||
Component,
|
||||
ElementRef,
|
||||
forwardRef,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import { ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms';
|
||||
import { Ace } from 'ace-builds';
|
||||
import { getAce } from '@shared/models/ace/ace.models';
|
||||
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState } from '@core/core.state';
|
||||
import { UtilsService } from '@core/services/utils.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
|
||||
import { ResizeObserver } from '@juggle/resize-observer';
|
||||
import { beautifyCss } from '@shared/models/beautify.models';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-css',
|
||||
templateUrl: './css.component.html',
|
||||
styleUrls: ['./css.component.scss'],
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => CssComponent),
|
||||
multi: true
|
||||
},
|
||||
{
|
||||
provide: NG_VALIDATORS,
|
||||
useExisting: forwardRef(() => CssComponent),
|
||||
multi: true,
|
||||
}
|
||||
],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class CssComponent implements OnInit, OnDestroy, ControlValueAccessor, Validator {
|
||||
|
||||
@ViewChild('cssEditor', {static: true})
|
||||
cssEditorElmRef: ElementRef;
|
||||
|
||||
private cssEditor: Ace.Editor;
|
||||
private editorsResizeCaf: CancelAnimationFrame;
|
||||
private editorResize$: ResizeObserver;
|
||||
private ignoreChange = false;
|
||||
|
||||
@Input() label: string;
|
||||
|
||||
@Input() disabled: boolean;
|
||||
|
||||
@Input() fillHeight: boolean;
|
||||
|
||||
private requiredValue: boolean;
|
||||
get required(): boolean {
|
||||
return this.requiredValue;
|
||||
}
|
||||
@Input()
|
||||
set required(value: boolean) {
|
||||
this.requiredValue = coerceBooleanProperty(value);
|
||||
}
|
||||
|
||||
fullscreen = false;
|
||||
|
||||
modelValue: string;
|
||||
|
||||
hasErrors = false;
|
||||
|
||||
private propagateChange = null;
|
||||
|
||||
constructor(public elementRef: ElementRef,
|
||||
private utils: UtilsService,
|
||||
private translate: TranslateService,
|
||||
protected store: Store<AppState>,
|
||||
private raf: RafService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
const editorElement = this.cssEditorElmRef.nativeElement;
|
||||
let editorOptions: Partial<Ace.EditorOptions> = {
|
||||
mode: 'ace/mode/css',
|
||||
showGutter: true,
|
||||
showPrintMargin: true,
|
||||
readOnly: this.disabled
|
||||
};
|
||||
|
||||
const advancedOptions = {
|
||||
enableSnippets: true,
|
||||
enableBasicAutocompletion: true,
|
||||
enableLiveAutocompletion: true
|
||||
};
|
||||
|
||||
editorOptions = {...editorOptions, ...advancedOptions};
|
||||
getAce().subscribe(
|
||||
(ace) => {
|
||||
this.cssEditor = ace.edit(editorElement, editorOptions);
|
||||
this.cssEditor.session.setUseWrapMode(true);
|
||||
this.cssEditor.setValue(this.modelValue ? this.modelValue : '', -1);
|
||||
this.cssEditor.setReadOnly(this.disabled);
|
||||
this.cssEditor.on('change', () => {
|
||||
if (!this.ignoreChange) {
|
||||
this.updateView();
|
||||
}
|
||||
});
|
||||
// @ts-ignore
|
||||
this.cssEditor.session.on('changeAnnotation', () => {
|
||||
const annotations = this.cssEditor.session.getAnnotations();
|
||||
const hasErrors = annotations.filter(annotation => annotation.type === 'error').length > 0;
|
||||
if (this.hasErrors !== hasErrors) {
|
||||
this.hasErrors = hasErrors;
|
||||
this.propagateChange(this.modelValue);
|
||||
}
|
||||
});
|
||||
this.editorResize$ = new ResizeObserver(() => {
|
||||
this.onAceEditorResize();
|
||||
});
|
||||
this.editorResize$.observe(editorElement);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.editorResize$) {
|
||||
this.editorResize$.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private onAceEditorResize() {
|
||||
if (this.editorsResizeCaf) {
|
||||
this.editorsResizeCaf();
|
||||
this.editorsResizeCaf = null;
|
||||
}
|
||||
this.editorsResizeCaf = this.raf.raf(() => {
|
||||
this.cssEditor.resize();
|
||||
this.cssEditor.renderer.updateFull();
|
||||
});
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
this.propagateChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: any): void {
|
||||
}
|
||||
|
||||
setDisabledState(isDisabled: boolean): void {
|
||||
this.disabled = isDisabled;
|
||||
if (this.cssEditor) {
|
||||
this.cssEditor.setReadOnly(this.disabled);
|
||||
}
|
||||
}
|
||||
|
||||
public validate(c: FormControl) {
|
||||
return (!this.hasErrors) ? null : {
|
||||
css: {
|
||||
valid: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
beautifyCss() {
|
||||
beautifyCss(this.modelValue, {indent_size: 4}).subscribe(
|
||||
(res) => {
|
||||
if (this.modelValue !== res) {
|
||||
this.cssEditor.setValue(res ? res : '', -1);
|
||||
this.updateView();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
writeValue(value: string): void {
|
||||
this.modelValue = value;
|
||||
if (this.cssEditor) {
|
||||
this.ignoreChange = true;
|
||||
this.cssEditor.setValue(this.modelValue ? this.modelValue : '', -1);
|
||||
this.ignoreChange = false;
|
||||
}
|
||||
}
|
||||
|
||||
updateView() {
|
||||
const editorValue = this.cssEditor.getValue();
|
||||
if (this.modelValue !== editorValue) {
|
||||
this.modelValue = editorValue;
|
||||
this.propagateChange(this.modelValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -47,6 +47,10 @@ export class TbMarkdownComponent implements OnChanges {
|
||||
|
||||
@Input() data: string | undefined;
|
||||
|
||||
@Input() context: any;
|
||||
|
||||
@Input() additionalCompileModules: Type<any>[];
|
||||
|
||||
@Input() markdownClass: string | undefined;
|
||||
|
||||
@Input() style: { [klass: string]: any } = {};
|
||||
@ -94,6 +98,10 @@ export class TbMarkdownComponent implements OnChanges {
|
||||
this.markdownContainer.clear();
|
||||
const parent = this;
|
||||
let readyObservable: Observable<void>;
|
||||
let compileModules = [this.sharedModule];
|
||||
if (this.additionalCompileModules) {
|
||||
compileModules = compileModules.concat(this.additionalCompileModules);
|
||||
}
|
||||
this.dynamicComponentFactoryService.createDynamicComponentFactory(
|
||||
class TbMarkdownInstance {
|
||||
ngOnDestroy(): void {
|
||||
@ -101,7 +109,7 @@ export class TbMarkdownComponent implements OnChanges {
|
||||
}
|
||||
},
|
||||
template,
|
||||
[this.sharedModule],
|
||||
compileModules,
|
||||
true
|
||||
).subscribe((factory) => {
|
||||
this.tbMarkdownInstanceComponentFactory = factory;
|
||||
@ -109,6 +117,11 @@ export class TbMarkdownComponent implements OnChanges {
|
||||
try {
|
||||
this.tbMarkdownInstanceComponentRef =
|
||||
this.markdownContainer.createComponent(this.tbMarkdownInstanceComponentFactory, 0, injector);
|
||||
if (this.context) {
|
||||
for (const propName of Object.keys(this.context)) {
|
||||
this.tbMarkdownInstanceComponentRef.instance[propName] = this.context[propName];
|
||||
}
|
||||
}
|
||||
this.tbMarkdownInstanceComponentRef.instance.style = this.style;
|
||||
this.handlePlugins(this.tbMarkdownInstanceComponentRef.location.nativeElement);
|
||||
this.markdownService.highlight(this.tbMarkdownInstanceComponentRef.location.nativeElement);
|
||||
|
||||
@ -97,6 +97,7 @@ export interface DashboardSettings {
|
||||
toolbarAlwaysOpen?: boolean;
|
||||
hideToolbar?: boolean;
|
||||
titleColor?: string;
|
||||
dashboardCss?: string;
|
||||
}
|
||||
|
||||
export interface DashboardConfiguration {
|
||||
|
||||
@ -506,6 +506,7 @@ export interface WidgetConfig {
|
||||
padding?: string;
|
||||
margin?: string;
|
||||
widgetStyle?: {[klass: string]: any};
|
||||
widgetCss?: string;
|
||||
titleStyle?: {[klass: string]: any};
|
||||
units?: string;
|
||||
decimals?: number;
|
||||
|
||||
@ -156,6 +156,7 @@ import { TbPopoverService } from '@shared/components/popover.service';
|
||||
import { HELP_MARKDOWN_COMPONENT_TOKEN, SHARED_MODULE_TOKEN } from '@shared/components/tokens';
|
||||
import { TbMarkdownComponent } from '@shared/components/markdown.component';
|
||||
import { ProtobufContentComponent } from '@shared/components/protobuf-content.component';
|
||||
import { CssComponent } from '@shared/components/css.component';
|
||||
|
||||
export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) {
|
||||
return markedOptionsService;
|
||||
@ -233,6 +234,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
|
||||
JsonObjectEditComponent,
|
||||
JsonContentComponent,
|
||||
JsFuncComponent,
|
||||
CssComponent,
|
||||
FabTriggerDirective,
|
||||
FabActionsDirective,
|
||||
FabToolbarComponent,
|
||||
@ -378,6 +380,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
|
||||
JsonObjectEditComponent,
|
||||
JsonContentComponent,
|
||||
JsFuncComponent,
|
||||
CssComponent,
|
||||
FabTriggerDirective,
|
||||
FabActionsDirective,
|
||||
FabToolbarComponent,
|
||||
|
||||
@ -811,6 +811,8 @@
|
||||
"dashboard-logo-settings": "Dashboard logo settings",
|
||||
"display-dashboard-logo": "Display logo in dashboard fullscreen mode",
|
||||
"dashboard-logo-image": "Dashboard logo image",
|
||||
"advanced-settings": "Advanced settings",
|
||||
"dashboard-css": "Dashboard CSS",
|
||||
"import": "Import dashboard",
|
||||
"export": "Export dashboard",
|
||||
"export-failed-error": "Unable to export dashboard: {{error}}",
|
||||
@ -3084,6 +3086,7 @@
|
||||
"padding": "Padding",
|
||||
"margin": "Margin",
|
||||
"widget-style": "Widget style",
|
||||
"widget-css": "Widget CSS",
|
||||
"title-style": "Title style",
|
||||
"mobile-mode-settings": "Mobile mode",
|
||||
"order": "Order",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user