33 changed files with 1569 additions and 178 deletions
@ -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.
|
|||
///
|
|||
|
|||
import { Injectable } from '@angular/core'; |
|||
import { Dashboard, DashboardLayoutId } from '@app/shared/models/dashboard.models'; |
|||
|
|||
@Injectable({ |
|||
providedIn: 'root' |
|||
}) |
|||
export class ItemBufferService { |
|||
constructor() {} |
|||
|
|||
public hasWidget(): boolean { |
|||
// TODO:
|
|||
return false; |
|||
} |
|||
|
|||
public canPasteWidgetReference(dashboard: Dashboard, state: string, layout: DashboardLayoutId): boolean { |
|||
// TODO:
|
|||
return false; |
|||
} |
|||
} |
|||
@ -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. |
|||
|
|||
--> |
|||
<mat-tab-group [ngClass]="{'tb-headless': (widgetType === widgetTypes.static && !displayAdvanced())}" |
|||
fxFlex class="tb-widget-config tb-absolute-fill" [selectedIndex]="selectedTab"> |
|||
<mat-tab label="{{ 'widget-config.data' | translate }}" [fxShow]="widgetType !== widgetTypes.static"> |
|||
<div class="mat-content mat-padding" fxLayout="column"> |
|||
<div [fxShow]="widgetType === widgetTypes.timeseries || widgetType === widgetTypes.alarm" |
|||
fxLayout="column" fxLayoutAlign="center" fxLayout.gt-sm="row" fxLayoutAlign.gt-sm="start center"> |
|||
<div fxLayout="column" fxFlex> |
|||
<mat-checkbox fxFlex [(ngModel)]="useDashboardTimewindow" (ngModelChange)="updateModel()"> |
|||
{{ 'widget-config.use-dashboard-timewindow' | translate }} |
|||
</mat-checkbox> |
|||
<mat-checkbox [disabled]="useDashboardTimewindow" fxFlex [(ngModel)]="displayTimewindow" (ngModelChange)="updateModel()"> |
|||
{{ 'widget-config.display-timewindow' | translate }} |
|||
</mat-checkbox> |
|||
</div> |
|||
<section fxFlex fxLayout="row" fxLayoutAlign="start center" style="margin-bottom: 16px;"> |
|||
<span [ngClass]="{'tb-disabled-label': useDashboardTimewindow}" translate style="padding-right: 8px;">widget-config.timewindow</span> |
|||
<tb-timewindow [disabled]="useDashboardTimewindow" asButton="true" |
|||
aggregation="{{ widgetType === widgetTypes.timeseries }}" |
|||
fxFlex [(ngModel)]="timewindow" (ngModelChange)="updateModel()"></tb-timewindow> |
|||
</section> |
|||
</div> |
|||
</div> |
|||
</mat-tab> |
|||
<mat-tab label="{{ 'widget-config.settings' | translate }}"> |
|||
</mat-tab> |
|||
</mat-tab-group> |
|||
@ -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. |
|||
*/ |
|||
:host { |
|||
.tb-widget-config { |
|||
.tb-advanced-widget-config { |
|||
height: 100%; |
|||
} |
|||
} |
|||
} |
|||
|
|||
:host ::ng-deep { |
|||
.tb-widget-config { |
|||
.mat-tab-body.mat-tab-body-active { |
|||
.mat-tab-body-content > div { |
|||
height: 100%; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,282 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component, forwardRef, Input, OnInit } from '@angular/core'; |
|||
import { PageComponent } from '@shared/components/page.component'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { |
|||
Datasource, |
|||
LegendConfig, |
|||
WidgetActionDescriptor, |
|||
WidgetActionSource, WidgetConfigSettings, |
|||
widgetType, |
|||
WidgetTypeParameters |
|||
} from '@shared/models/widget.models'; |
|||
import { ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms'; |
|||
import { WidgetConfigComponentData } from '@home/models/widget-component.models'; |
|||
import { deepClone, isDefined } from '@app/core/utils'; |
|||
import { Timewindow } from '@shared/models/time/time.models'; |
|||
import { AlarmSearchStatus } from '@shared/models/alarm.models'; |
|||
import { IAliasController } from '@core/api/widget-api.models'; |
|||
import { EntityAlias } from '@shared/models/alias.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-widget-config', |
|||
templateUrl: './widget-config.component.html', |
|||
styleUrls: ['./widget-config.component.scss'], |
|||
providers: [ |
|||
{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => WidgetConfigComponent), |
|||
multi: true |
|||
}, |
|||
{ |
|||
provide: NG_VALIDATORS, |
|||
useExisting: forwardRef(() => WidgetConfigComponent), |
|||
multi: true, |
|||
} |
|||
] |
|||
}) |
|||
export class WidgetConfigComponent extends PageComponent implements OnInit, ControlValueAccessor, Validator { |
|||
|
|||
widgetTypes = widgetType; |
|||
|
|||
@Input() |
|||
forceExpandDatasources: boolean; |
|||
|
|||
@Input() |
|||
isDataEnabled: boolean; |
|||
|
|||
@Input() |
|||
widgetType: widgetType; |
|||
|
|||
@Input() |
|||
typeParameters: WidgetTypeParameters; |
|||
|
|||
@Input() |
|||
actionSources: {[key: string]: WidgetActionSource}; |
|||
|
|||
@Input() |
|||
aliasController: IAliasController; |
|||
|
|||
@Input() |
|||
widgetSettingsSchema: any; |
|||
|
|||
@Input() |
|||
dataKeySettingsSchema: any; |
|||
|
|||
@Input() |
|||
functionsOnly: boolean; |
|||
|
|||
@Input() disabled: boolean; |
|||
|
|||
selectedTab: number; |
|||
title: string; |
|||
showTitleIcon: boolean; |
|||
titleIcon: string; |
|||
iconColor: string; |
|||
iconSize: string; |
|||
showTitle: boolean; |
|||
dropShadow: boolean; |
|||
enableFullscreen: boolean; |
|||
backgroundColor: string; |
|||
color: string; |
|||
padding: string; |
|||
margin: string; |
|||
widgetStyle: string; |
|||
titleStyle: string; |
|||
units: string; |
|||
decimals: number; |
|||
useDashboardTimewindow: boolean; |
|||
displayTimewindow: boolean; |
|||
timewindow: Timewindow; |
|||
showLegend: boolean; |
|||
legendConfig: LegendConfig; |
|||
actions: {[actionSourceId: string]: Array<WidgetActionDescriptor>}; |
|||
datasources: Array<Datasource>; |
|||
targetDeviceAlias: EntityAlias; |
|||
alarmSource: Datasource; |
|||
alarmSearchStatus: AlarmSearchStatus; |
|||
alarmsPollingInterval: number; |
|||
settings: WidgetConfigSettings; |
|||
mobileOrder: number; |
|||
mobileHeight: number; |
|||
|
|||
emptySettingsSchema = { |
|||
type: 'object', |
|||
properties: {} |
|||
}; |
|||
|
|||
emptySettingsGroupInfoes = []; |
|||
|
|||
defaultSettingsForm = [ |
|||
'*' |
|||
]; |
|||
|
|||
currentSettingsSchema = deepClone(this.emptySettingsSchema); |
|||
|
|||
currentSettings: WidgetConfigSettings = {}; |
|||
currentSettingsGroupInfoes = deepClone(this.emptySettingsGroupInfoes); |
|||
|
|||
currentSettingsForm: any; |
|||
|
|||
private modelValue: WidgetConfigComponentData; |
|||
|
|||
private propagateChange = null; |
|||
|
|||
constructor(protected store: Store<AppState>) { |
|||
super(store); |
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
} |
|||
|
|||
registerOnChange(fn: any): void { |
|||
this.propagateChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: any): void { |
|||
} |
|||
|
|||
setDisabledState(isDisabled: boolean): void { |
|||
this.disabled = isDisabled; |
|||
} |
|||
|
|||
writeValue(value: WidgetConfigComponentData): void { |
|||
this.modelValue = value; |
|||
if (this.modelValue) { |
|||
const config = this.modelValue.config; |
|||
const layout = this.modelValue.layout; |
|||
if (config) { |
|||
this.selectedTab = 0; |
|||
this.title = config.title; |
|||
this.showTitleIcon = isDefined(config.showTitleIcon) ? config.showTitleIcon : false; |
|||
this.titleIcon = isDefined(config.titleIcon) ? config.titleIcon : ''; |
|||
this.iconColor = isDefined(config.iconColor) ? config.iconColor : 'rgba(0, 0, 0, 0.87)'; |
|||
this.iconSize = isDefined(config.iconSize) ? config.iconSize : '24px'; |
|||
this.showTitle = config.showTitle; |
|||
this.dropShadow = isDefined(config.dropShadow) ? config.dropShadow : true; |
|||
this.enableFullscreen = isDefined(config.enableFullscreen) ? config.enableFullscreen : true; |
|||
this.backgroundColor = config.backgroundColor; |
|||
this.color = config.color; |
|||
this.padding = config.padding; |
|||
this.margin = config.margin; |
|||
this.widgetStyle = |
|||
JSON.stringify(isDefined(config.widgetStyle) ? config.widgetStyle : {}, null, 2); |
|||
this.titleStyle = |
|||
JSON.stringify(isDefined(config.titleStyle) ? config.titleStyle : { |
|||
fontSize: '16px', |
|||
fontWeight: 400 |
|||
}, null, 2); |
|||
this.units = config.units; |
|||
this.decimals = config.decimals; |
|||
this.useDashboardTimewindow = isDefined(config.useDashboardTimewindow) ? |
|||
config.useDashboardTimewindow : true; |
|||
this.displayTimewindow = isDefined(config.displayTimewindow) ? |
|||
config.displayTimewindow : true; |
|||
this.timewindow = config.timewindow; |
|||
this.actions = config.actions; |
|||
if (!this.actions) { |
|||
this.actions = {}; |
|||
} |
|||
if (this.isDataEnabled) { |
|||
if (this.widgetType !== widgetType.rpc && |
|||
this.widgetType !== widgetType.alarm && |
|||
this.widgetType !== widgetType.static) { |
|||
if (config.datasources) { |
|||
this.datasources = config.datasources; |
|||
} else { |
|||
this.datasources = []; |
|||
} |
|||
} else if (this.widgetType === widgetType.rpc) { |
|||
if (config.targetDeviceAliasIds && config.targetDeviceAliasIds.length > 0) { |
|||
const aliasId = config.targetDeviceAliasIds[0]; |
|||
const entityAliases = this.aliasController.getEntityAliases(); |
|||
if (entityAliases[aliasId]) { |
|||
this.targetDeviceAlias = entityAliases[aliasId]; |
|||
} else { |
|||
this.targetDeviceAlias = null; |
|||
} |
|||
} else { |
|||
this.targetDeviceAlias = null; |
|||
} |
|||
} else if (this.widgetType === widgetType.alarm) { |
|||
this.alarmSearchStatus = isDefined(config.alarmSearchStatus) ? |
|||
config.alarmSearchStatus : AlarmSearchStatus.ANY; |
|||
this.alarmsPollingInterval = isDefined(config.alarmsPollingInterval) ? |
|||
config.alarmsPollingInterval : 5; |
|||
if (config.alarmSource) { |
|||
this.alarmSource = config.alarmSource; |
|||
} else { |
|||
this.alarmSource = null; |
|||
} |
|||
} |
|||
} |
|||
this.settings = config.settings; |
|||
|
|||
this.updateSchemaForm(); |
|||
|
|||
if (layout) { |
|||
this.mobileOrder = layout.mobileOrder; |
|||
this.mobileHeight = layout.mobileHeight; |
|||
} else { |
|||
this.mobileOrder = undefined; |
|||
this.mobileHeight = undefined; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private updateSchemaForm() { |
|||
if (this.widgetSettingsSchema && this.widgetSettingsSchema.schema) { |
|||
this.currentSettingsSchema = this.widgetSettingsSchema.schema; |
|||
this.currentSettingsForm = this.widgetSettingsSchema.form || deepClone(this.defaultSettingsForm); |
|||
this.currentSettingsGroupInfoes = this.widgetSettingsSchema.groupInfoes; |
|||
this.currentSettings = this.settings; |
|||
} else { |
|||
this.currentSettingsForm = deepClone(this.defaultSettingsForm); |
|||
this.currentSettingsSchema = deepClone(this.emptySettingsSchema); |
|||
this.currentSettingsGroupInfoes = deepClone(this.emptySettingsGroupInfoes); |
|||
this.currentSettings = {}; |
|||
} |
|||
} |
|||
|
|||
public updateModel() { |
|||
if (this.modelValue) { |
|||
if (this.modelValue.config) { |
|||
const config = this.modelValue.config; |
|||
config.useDashboardTimewindow = this.useDashboardTimewindow; |
|||
config.displayTimewindow = this.displayTimewindow; |
|||
config.timewindow = this.timewindow; |
|||
} |
|||
this.propagateChange(this.modelValue); |
|||
} |
|||
} |
|||
|
|||
public displayAdvanced(): boolean { |
|||
return this.widgetSettingsSchema && this.widgetSettingsSchema.schema; |
|||
} |
|||
|
|||
public validate(c: FormControl) { |
|||
return null; /*{ |
|||
targetDeviceAliasIds: { |
|||
valid: false, |
|||
}, |
|||
};*/ |
|||
} |
|||
|
|||
} |
|||
@ -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. |
|||
|
|||
--> |
|||
<form [formGroup]="widgetFormGroup"> |
|||
<fieldset [disabled]="isLoading$ | async"> |
|||
<tb-widget-config |
|||
[widgetType]="widget.type" |
|||
[typeParameters]="typeParameters" |
|||
[actionSources]="actionSources" |
|||
[isDataEnabled]="isDataEnabled" |
|||
[widgetSettingsSchema]="settingsSchema" |
|||
[dataKeySettingsSchema]="dataKeySettingsSchema" |
|||
[aliasController]="aliasController" |
|||
[functionsOnly]="widgetEditMode" |
|||
formControlName="widgetConfig"> |
|||
</tb-widget-config> |
|||
</fieldset> |
|||
</form> |
|||
@ -0,0 +1,115 @@ |
|||
///
|
|||
/// Copyright © 2016-2019 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { Component, OnInit, Input, OnChanges, SimpleChanges } 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 { WidgetService } from '@core/http/widget.service'; |
|||
import { DialogService } from '@core/services/dialog.service'; |
|||
import { MatDialog } from '@angular/material/dialog'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { Dashboard, WidgetLayout } from '@shared/models/dashboard.models'; |
|||
import { IAliasController } from '@core/api/widget-api.models'; |
|||
import { Widget, WidgetActionSource, WidgetTypeParameters } from '@shared/models/widget.models'; |
|||
import { WidgetComponentService } from '@home/components/widget/widget-component.service'; |
|||
import { WidgetConfigComponentData } from '../../models/widget-component.models'; |
|||
import { isString } from '@core/utils'; |
|||
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-edit-widget', |
|||
templateUrl: './edit-widget.component.html', |
|||
styleUrls: [] |
|||
}) |
|||
export class EditWidgetComponent extends PageComponent implements OnInit, OnChanges { |
|||
|
|||
@Input() |
|||
dashboard: Dashboard; |
|||
|
|||
@Input() |
|||
aliasController: IAliasController; |
|||
|
|||
@Input() |
|||
widgetEditMode: boolean; |
|||
|
|||
@Input() |
|||
widget: Widget; |
|||
|
|||
@Input() |
|||
widgetLayout: WidgetLayout; |
|||
|
|||
@Input() |
|||
widgetFormGroup: FormGroup; |
|||
|
|||
widgetConfig: WidgetConfigComponentData; |
|||
typeParameters: WidgetTypeParameters; |
|||
actionSources: {[key: string]: WidgetActionSource}; |
|||
isDataEnabled: boolean; |
|||
settingsSchema: any; |
|||
dataKeySettingsSchema: any; |
|||
functionsOnly: boolean; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
private widgetComponentService: WidgetComponentService) { |
|||
super(store); |
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
this.loadWidgetConfig(); |
|||
} |
|||
|
|||
ngOnChanges(changes: SimpleChanges): void { |
|||
let reloadConfig = false; |
|||
for (const propName of Object.keys(changes)) { |
|||
const change = changes[propName]; |
|||
if (!change.firstChange && change.currentValue !== change.previousValue) { |
|||
if (['widget', 'widgetLayout'].includes(propName)) { |
|||
reloadConfig = true; |
|||
} |
|||
} |
|||
} |
|||
if (reloadConfig) { |
|||
this.loadWidgetConfig(); |
|||
} |
|||
} |
|||
|
|||
private loadWidgetConfig() { |
|||
const widgetInfo = this.widgetComponentService.getInstantWidgetInfo(this.widget); |
|||
this.widgetConfig = { |
|||
config: this.widget.config, |
|||
layout: this.widgetLayout |
|||
}; |
|||
const settingsSchema = widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema; |
|||
const dataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema; |
|||
this.typeParameters = widgetInfo.typeParameters; |
|||
this.actionSources = widgetInfo.actionSources; |
|||
this.isDataEnabled = widgetInfo.typeParameters && !widgetInfo.typeParameters.useCustomDatasources; |
|||
if (!settingsSchema || settingsSchema === '') { |
|||
this.settingsSchema = {}; |
|||
} else { |
|||
this.settingsSchema = isString(settingsSchema) ? JSON.parse(settingsSchema) : settingsSchema; |
|||
} |
|||
if (!dataKeySettingsSchema || dataKeySettingsSchema === '') { |
|||
this.dataKeySettingsSchema = {}; |
|||
} else { |
|||
this.dataKeySettingsSchema = isString(dataKeySettingsSchema) ? JSON.parse(dataKeySettingsSchema) : dataKeySettingsSchema; |
|||
} |
|||
this.functionsOnly = this.dashboard ? false : true; |
|||
this.widgetFormGroup.reset({widgetConfig: this.widgetConfig}); |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
///
|
|||
/// 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 { Inject, Pipe, PipeTransform } from '@angular/core'; |
|||
import { WINDOW } from '@core/services/window.service'; |
|||
|
|||
@Pipe({ |
|||
name: 'keyboardShortcut' |
|||
}) |
|||
export class KeyboardShortcutPipe implements PipeTransform { |
|||
|
|||
constructor(@Inject(WINDOW) private window: Window) {} |
|||
|
|||
transform(value: string): string { |
|||
if (!value) { |
|||
return; |
|||
} |
|||
const keys = value.split('-'); |
|||
const isOSX = /Mac OS X/.test(this.window.navigator.userAgent); |
|||
|
|||
const seperator = (!isOSX || keys.length > 2) ? '+' : ''; |
|||
|
|||
const abbreviations = { |
|||
M: isOSX ? '⌘' : 'Ctrl', |
|||
A: isOSX ? 'Option' : 'Alt', |
|||
S: 'Shift' |
|||
}; |
|||
|
|||
return keys.map((key, index) => { |
|||
const last = index === keys.length - 1; |
|||
return last ? key : abbreviations[key]; |
|||
}).join(seperator); |
|||
return (!value) ? '' : value.replace(/ /g, ''); |
|||
} |
|||
|
|||
} |
|||
Loading…
Reference in new issue