UI: Value card basic config
This commit is contained in:
parent
9d8a9943bf
commit
c0309ac1aa
@ -55,5 +55,50 @@
|
||||
</tb-color-settings>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tb-form-row">
|
||||
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showIcon">
|
||||
{{ 'widgets.value-card.icon' | translate }}
|
||||
</mat-slide-toggle>
|
||||
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
|
||||
<mat-form-field fxFlex appearance="outline" class="number" subscriptSizing="dynamic">
|
||||
<input matInput type="number" min="0" formControlName="iconSize" placeholder="{{ 'widget-config.set' | translate }}">
|
||||
</mat-form-field>
|
||||
<tb-css-unit-select fxFlex formControlName="iconSizeUnit"></tb-css-unit-select>
|
||||
<tb-material-icon-select asBoxInput
|
||||
[color]="valueCardWidgetConfigForm.get('iconColor').value?.color"
|
||||
formControlName="icon">
|
||||
</tb-material-icon-select>
|
||||
<tb-color-settings formControlName="iconColor">
|
||||
</tb-color-settings>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tb-form-row">
|
||||
<div class="fixed-title-width" translate>widgets.value-card.value</div>
|
||||
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
|
||||
<tb-unit-input fxFlex formControlName="units"></tb-unit-input>
|
||||
<mat-form-field fxFlex appearance="outline" class="number" subscriptSizing="dynamic">
|
||||
<input matInput formControlName="decimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}">
|
||||
<div matSuffix translate>widget-config.decimals-suffix</div>
|
||||
</mat-form-field>
|
||||
<tb-font-settings formControlName="valueFont"
|
||||
[previewText]="valuePreviewFn">
|
||||
</tb-font-settings>
|
||||
<tb-color-settings formControlName="valueColor">
|
||||
</tb-color-settings>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tb-form-row">
|
||||
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showDate">
|
||||
{{ 'widgets.value-card.date' | translate }}
|
||||
</mat-slide-toggle>
|
||||
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
|
||||
<tb-date-format-select fxFlex formControlName="dateFormat"></tb-date-format-select>
|
||||
<tb-font-settings formControlName="dateFont"
|
||||
[previewText]="datePreviewFn">
|
||||
</tb-font-settings>
|
||||
<tb-color-settings formControlName="dateColor">
|
||||
</tb-color-settings>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import { ChangeDetectorRef, Component } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, Injector } from '@angular/core';
|
||||
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState } from '@core/core.state';
|
||||
@ -28,8 +28,13 @@ import {
|
||||
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
|
||||
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
|
||||
import { getTimewindowConfig } from '@home/components/widget/config/timewindow-config-panel.component';
|
||||
import { isDefinedAndNotNull, isUndefined } from '@core/utils';
|
||||
import { getLabel, setLabel } from '@home/components/widget/config/widget-settings.models';
|
||||
import { formatValue, isDefinedAndNotNull, isUndefined } from '@core/utils';
|
||||
import {
|
||||
DateFormatProcessor,
|
||||
DateFormatSettings,
|
||||
getLabel,
|
||||
setLabel
|
||||
} from '@home/components/widget/config/widget-settings.models';
|
||||
import {
|
||||
valueCardDefaultSettings,
|
||||
ValueCardLayout,
|
||||
@ -65,9 +70,14 @@ export class ValueCardBasicConfigComponent extends BasicWidgetConfigComponent {
|
||||
|
||||
valueCardWidgetConfigForm: UntypedFormGroup;
|
||||
|
||||
valuePreviewFn = this._valuePreviewFn.bind(this);
|
||||
|
||||
datePreviewFn = this._datePreviewFn.bind(this);
|
||||
|
||||
constructor(protected store: Store<AppState>,
|
||||
protected widgetConfigComponent: WidgetConfigComponent,
|
||||
private cd: ChangeDetectorRef,
|
||||
private $injector: Injector,
|
||||
private fb: UntypedFormBuilder) {
|
||||
super(store, widgetConfigComponent);
|
||||
}
|
||||
@ -251,4 +261,16 @@ export class ValueCardBasicConfigComponent extends BasicWidgetConfigComponent {
|
||||
config.enableFullscreen = buttons.includes('fullscreen');
|
||||
}
|
||||
|
||||
private _valuePreviewFn(): string {
|
||||
const units: string = this.valueCardWidgetConfigForm.get('units').value;
|
||||
const decimals: number = this.valueCardWidgetConfigForm.get('decimals').value;
|
||||
return formatValue(22, decimals, units, true);
|
||||
}
|
||||
|
||||
private _datePreviewFn(): string {
|
||||
const dateFormat: DateFormatSettings = this.valueCardWidgetConfigForm.get('dateFormat').value;
|
||||
const processor = DateFormatProcessor.fromSettings(this.$injector, dateFormat);
|
||||
processor.update(Date.now());
|
||||
return processor.formatted;
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,6 +16,10 @@
|
||||
|
||||
import { isDefinedAndNotNull, isNumber, isNumeric, parseFunction } from '@core/utils';
|
||||
import { DataKey, Datasource, DatasourceData } from '@shared/models/widget.models';
|
||||
import { Injector } from '@angular/core';
|
||||
import { DatePipe, formatDate } from '@angular/common';
|
||||
import { DateAgoPipe } from '@shared/pipe/date-ago.pipe';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
export type ComponentStyle = {[klass: string]: any};
|
||||
|
||||
@ -168,6 +172,104 @@ class FunctionColorProcessor extends ColorProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
export interface DateFormatSettings {
|
||||
format?: string;
|
||||
lastUpdateAgo?: boolean;
|
||||
custom?: boolean;
|
||||
}
|
||||
|
||||
export const simpleDateFormat = (format: string): DateFormatSettings => ({
|
||||
format,
|
||||
lastUpdateAgo: false,
|
||||
custom: false
|
||||
});
|
||||
|
||||
export const lastUpdateAgoDateFormat = (): DateFormatSettings => ({
|
||||
format: null,
|
||||
lastUpdateAgo: true,
|
||||
custom: false
|
||||
});
|
||||
|
||||
export const customDateFormat = (format: string): DateFormatSettings => ({
|
||||
format,
|
||||
lastUpdateAgo: false,
|
||||
custom: true
|
||||
});
|
||||
|
||||
export const dateFormats = ['MMM dd yyyy HH:mm', 'dd MMM yyyy HH:mm', 'yyyy MMM dd HH:mm',
|
||||
'MM/dd/yyyy HH:mm', 'dd/MM/yyyy HH:mm', 'yyyy/MM/dd HH:mm:ss']
|
||||
.map(f => simpleDateFormat(f)).concat([lastUpdateAgoDateFormat(), customDateFormat('EEE, MMMM dd, yyyy')]);
|
||||
|
||||
export const compareDateFormats = (df1: DateFormatSettings, df2: DateFormatSettings): boolean => {
|
||||
if (df1 === df2) {
|
||||
return true;
|
||||
} else if (df1 && df2) {
|
||||
if (df1.lastUpdateAgo && df2.lastUpdateAgo) {
|
||||
return true;
|
||||
} else if (df1.custom && df2.custom) {
|
||||
return true;
|
||||
} else if (!df1.lastUpdateAgo && !df2.lastUpdateAgo && !df1.custom && !df2.custom) {
|
||||
return df1.format === df2.format;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export abstract class DateFormatProcessor {
|
||||
|
||||
static fromSettings($injector: Injector, settings: DateFormatSettings): DateFormatProcessor {
|
||||
if (settings.lastUpdateAgo) {
|
||||
return new LastUpdateAgoDateFormatProcessor($injector, settings);
|
||||
} else {
|
||||
return new SimpleDateFormatProcessor($injector, settings);
|
||||
}
|
||||
}
|
||||
|
||||
formatted = '';
|
||||
|
||||
protected constructor(protected $injector: Injector,
|
||||
protected settings: DateFormatSettings) {
|
||||
}
|
||||
|
||||
abstract update(ts: string | number | Date): void;
|
||||
|
||||
}
|
||||
|
||||
export class SimpleDateFormatProcessor extends DateFormatProcessor {
|
||||
|
||||
private datePipe: DatePipe;
|
||||
|
||||
constructor(protected $injector: Injector,
|
||||
protected settings: DateFormatSettings) {
|
||||
super($injector, settings);
|
||||
this.datePipe = $injector.get(DatePipe);
|
||||
}
|
||||
|
||||
update(ts: string| number | Date): void {
|
||||
this.formatted = this.datePipe.transform(ts, this.settings.format);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class LastUpdateAgoDateFormatProcessor extends DateFormatProcessor {
|
||||
|
||||
private dateAgoPipe: DateAgoPipe;
|
||||
private translate: TranslateService;
|
||||
|
||||
constructor(protected $injector: Injector,
|
||||
protected settings: DateFormatSettings) {
|
||||
super($injector, settings);
|
||||
this.dateAgoPipe = $injector.get(DateAgoPipe);
|
||||
this.translate = $injector.get(TranslateService);
|
||||
}
|
||||
|
||||
update(ts: string| number | Date): void {
|
||||
this.formatted = this.translate.instant('date.last-update-n-ago-text',
|
||||
{agoText: this.dateAgoPipe.transform(ts, {applyAgo: true, short: true, textPart: true})});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export enum BackgroundType {
|
||||
image = 'image',
|
||||
imageUrl = 'imageUrl',
|
||||
|
||||
@ -63,7 +63,7 @@
|
||||
<div *ngIf="showLabel" [style]="labelStyle" [style.color]="labelColor.color">{{ label }}</div>
|
||||
</ng-template>
|
||||
<ng-template #dateTpl>
|
||||
<div *ngIf="showDate" [style]="dateStyle" [style.color]="dateColor.color">{{ dateText }}</div>
|
||||
<div *ngIf="showDate" [style]="dateStyle" [style.color]="dateColor.color">{{ dateFormat.formatted }}</div>
|
||||
</ng-template>
|
||||
<ng-template #valueTpl>
|
||||
<div class="tb-value-card-value" [style]="valueStyle" [style.color]="valueColor.color">{{ valueText }}</div>
|
||||
|
||||
@ -21,7 +21,7 @@ import { DatePipe } from '@angular/common';
|
||||
import {
|
||||
backgroundStyle,
|
||||
ColorProcessor,
|
||||
ComponentStyle,
|
||||
ComponentStyle, DateFormatProcessor,
|
||||
getDataKey,
|
||||
getLabel,
|
||||
getSingleTsValue,
|
||||
@ -62,7 +62,7 @@ export class ValueCardWidgetComponent implements OnInit {
|
||||
valueColor: ColorProcessor;
|
||||
|
||||
showDate = true;
|
||||
dateText = '';
|
||||
dateFormat: DateFormatProcessor;
|
||||
dateStyle: ComponentStyle = {};
|
||||
dateColor: ColorProcessor;
|
||||
|
||||
@ -70,7 +70,6 @@ export class ValueCardWidgetComponent implements OnInit {
|
||||
overlayStyle: ComponentStyle = {};
|
||||
|
||||
private horizontal = false;
|
||||
private dateFormat: string;
|
||||
private decimals = 0;
|
||||
private units = '';
|
||||
|
||||
@ -110,7 +109,7 @@ export class ValueCardWidgetComponent implements OnInit {
|
||||
this.valueColor = ColorProcessor.fromSettings(this.settings.valueColor);
|
||||
|
||||
this.showDate = this.settings.showDate;
|
||||
this.dateFormat = this.settings.dateFormat;
|
||||
this.dateFormat = DateFormatProcessor.fromSettings(this.ctx.$injector, this.settings.dateFormat);
|
||||
this.dateStyle = textStyle(this.settings.dateFont, '1.33', '0.25px');
|
||||
this.dateColor = ColorProcessor.fromSettings(this.settings.dateColor);
|
||||
|
||||
@ -126,15 +125,16 @@ export class ValueCardWidgetComponent implements OnInit {
|
||||
|
||||
public onDataUpdated() {
|
||||
const tsValue = getSingleTsValue(this.ctx.data);
|
||||
let ts;
|
||||
let value;
|
||||
if (tsValue) {
|
||||
ts = tsValue[0];
|
||||
value = tsValue[1];
|
||||
this.valueText = formatValue(value, this.decimals, this.units, true);
|
||||
this.dateText = this.date.transform(tsValue[0], this.dateFormat);
|
||||
} else {
|
||||
this.valueText = 'N/A';
|
||||
this.dateText = '';
|
||||
}
|
||||
this.dateFormat.update(ts);
|
||||
this.iconColor.update(value);
|
||||
this.labelColor.update(value);
|
||||
this.valueColor.update(value);
|
||||
|
||||
@ -19,8 +19,8 @@ import {
|
||||
BackgroundType,
|
||||
ColorSettings,
|
||||
constantColor,
|
||||
cssUnit,
|
||||
Font
|
||||
cssUnit, DateFormatSettings,
|
||||
Font, lastUpdateAgoDateFormat
|
||||
} from '@home/components/widget/config/widget-settings.models';
|
||||
|
||||
export enum ValueCardLayout {
|
||||
@ -75,7 +75,7 @@ export interface ValueCardWidgetSettings {
|
||||
valueFont: Font;
|
||||
valueColor: ColorSettings;
|
||||
showDate: boolean;
|
||||
dateFormat: string;
|
||||
dateFormat: DateFormatSettings;
|
||||
dateFont: Font;
|
||||
dateColor: ColorSettings;
|
||||
background: BackgroundSettings;
|
||||
@ -106,7 +106,7 @@ export const valueCardDefaultSettings = (horizontal: boolean): ValueCardWidgetSe
|
||||
},
|
||||
valueColor: constantColor('rgba(0, 0, 0, 0.87)'),
|
||||
showDate: true,
|
||||
dateFormat: 'yyyy-MM-dd HH:mm:ss',
|
||||
dateFormat: lastUpdateAgoDateFormat(),
|
||||
dateFont: {
|
||||
family: 'Roboto',
|
||||
size: 12,
|
||||
|
||||
@ -108,6 +108,7 @@ export class ColorSettingsPanelComponent extends PageComponent implements OnInit
|
||||
|
||||
removeRange(index: number) {
|
||||
this.rangeListFormArray.removeAt(index);
|
||||
this.colorSettingsFormGroup.markAsDirty();
|
||||
setTimeout(() => {this.popover?.updatePosition();}, 0);
|
||||
}
|
||||
|
||||
@ -115,7 +116,8 @@ export class ColorSettingsPanelComponent extends PageComponent implements OnInit
|
||||
const newRange: ColorRange = {
|
||||
color: 'rgba(0,0,0,0.87)'
|
||||
};
|
||||
this.rangeListFormArray.push(this.colorRangeControl(newRange), {emitEvent: true});
|
||||
this.rangeListFormArray.push(this.colorRangeControl(newRange));
|
||||
this.colorSettingsFormGroup.markAsDirty();
|
||||
setTimeout(() => {this.popover?.updatePosition();}, 0);
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
<!--
|
||||
|
||||
Copyright © 2016-2023 The Thingsboard Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<mat-form-field appearance="outline" subscriptSizing="dynamic" style="width: 100%;">
|
||||
<mat-select [formControl]="cssUnitFormControl">
|
||||
<mat-option *ngFor="let cssUnit of cssUnitsList" [value]="cssUnit">{{ cssUnit }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
@ -0,0 +1,82 @@
|
||||
///
|
||||
/// Copyright © 2016-2023 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 { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormControl } from '@angular/forms';
|
||||
import { cssUnit, cssUnits } from '@home/components/widget/config/widget-settings.models';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-css-unit-select',
|
||||
templateUrl: './css-unit-select.component.html',
|
||||
styleUrls: [],
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => CssUnitSelectComponent),
|
||||
multi: true
|
||||
}
|
||||
]
|
||||
})
|
||||
export class CssUnitSelectComponent implements OnInit, ControlValueAccessor {
|
||||
|
||||
@Input()
|
||||
disabled: boolean;
|
||||
|
||||
cssUnitsList = cssUnits;
|
||||
|
||||
cssUnitFormControl: UntypedFormControl;
|
||||
|
||||
modelValue: cssUnit;
|
||||
|
||||
private propagateChange = null;
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.cssUnitFormControl = new UntypedFormControl();
|
||||
this.cssUnitFormControl.valueChanges.subscribe((value: cssUnit) => {
|
||||
this.updateModel(value);
|
||||
});
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
this.propagateChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: any): void {
|
||||
}
|
||||
|
||||
setDisabledState(isDisabled: boolean): void {
|
||||
this.disabled = isDisabled;
|
||||
if (this.disabled) {
|
||||
this.cssUnitFormControl.disable();
|
||||
} else {
|
||||
this.cssUnitFormControl.enable();
|
||||
}
|
||||
}
|
||||
|
||||
writeValue(value: cssUnit): void {
|
||||
this.modelValue = value;
|
||||
this.cssUnitFormControl.patchValue(this.modelValue, {emitEvent: false});
|
||||
}
|
||||
|
||||
updateModel(value: cssUnit): void {
|
||||
if (this.modelValue !== value) {
|
||||
this.modelValue = value;
|
||||
this.propagateChange(this.modelValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
<!--
|
||||
|
||||
Copyright © 2016-2023 The Thingsboard Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<mat-form-field appearance="outline" subscriptSizing="dynamic" style="width: 100%;">
|
||||
<mat-select [formControl]="dateFormatFormControl" [compareWith]="dateFormatsCompare" >
|
||||
<mat-option *ngFor="let dateFormat of dateFormatList" [value]="dateFormat">{{ dateFormatDisplayValue(dateFormat) }}</mat-option>
|
||||
</mat-select>
|
||||
<button #customFormatButton
|
||||
*ngIf="dateFormatFormControl.value?.custom" matSuffix mat-icon-button (click)="openDateFormatSettingsPopup($event, customFormatButton)">
|
||||
<tb-icon>edit</tb-icon>
|
||||
</button>
|
||||
</mat-form-field>
|
||||
@ -0,0 +1,148 @@
|
||||
///
|
||||
/// Copyright © 2016-2023 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, Renderer2, ViewChild, ViewContainerRef } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormControl } from '@angular/forms';
|
||||
import {
|
||||
compareDateFormats,
|
||||
dateFormats,
|
||||
DateFormatSettings
|
||||
} from '@home/components/widget/config/widget-settings.models';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { MatButton } from '@angular/material/button';
|
||||
import { TbPopoverService } from '@shared/components/popover.service';
|
||||
import { deepClone } from '@core/utils';
|
||||
import {
|
||||
DateFormatSettingsPanelComponent
|
||||
} from '@home/components/widget/lib/settings/common/date-format-settings-panel.component';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-date-format-select',
|
||||
templateUrl: './date-format-select.component.html',
|
||||
styleUrls: [],
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => DateFormatSelectComponent),
|
||||
multi: true
|
||||
}
|
||||
]
|
||||
})
|
||||
export class DateFormatSelectComponent implements OnInit, ControlValueAccessor {
|
||||
|
||||
@ViewChild('customFormatButton', {static: false})
|
||||
customFormatButton: MatButton;
|
||||
|
||||
@Input()
|
||||
disabled: boolean;
|
||||
|
||||
dateFormatList = dateFormats;
|
||||
|
||||
dateFormatsCompare = compareDateFormats;
|
||||
|
||||
dateFormatFormControl: UntypedFormControl;
|
||||
|
||||
modelValue: DateFormatSettings;
|
||||
|
||||
private propagateChange = null;
|
||||
|
||||
private formatCache: {[format: string]: string} = {};
|
||||
|
||||
constructor(private translate: TranslateService,
|
||||
private date: DatePipe,
|
||||
private popoverService: TbPopoverService,
|
||||
private renderer: Renderer2,
|
||||
private viewContainerRef: ViewContainerRef) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.dateFormatFormControl = new UntypedFormControl();
|
||||
this.dateFormatFormControl.valueChanges.subscribe((value: DateFormatSettings) => {
|
||||
this.updateModel(value);
|
||||
if (value?.custom) {
|
||||
setTimeout(() => {
|
||||
this.openDateFormatSettingsPopup(null, this.customFormatButton);
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
this.propagateChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: any): void {
|
||||
}
|
||||
|
||||
setDisabledState(isDisabled: boolean): void {
|
||||
this.disabled = isDisabled;
|
||||
if (this.disabled) {
|
||||
this.dateFormatFormControl.disable();
|
||||
} else {
|
||||
this.dateFormatFormControl.enable();
|
||||
}
|
||||
}
|
||||
|
||||
writeValue(value: DateFormatSettings): void {
|
||||
this.modelValue = value;
|
||||
this.dateFormatFormControl.patchValue(this.modelValue, {emitEvent: false});
|
||||
}
|
||||
|
||||
updateModel(value: DateFormatSettings): void {
|
||||
if (!compareDateFormats(this.modelValue, value)) {
|
||||
this.modelValue = value;
|
||||
this.propagateChange(this.modelValue);
|
||||
}
|
||||
}
|
||||
|
||||
dateFormatDisplayValue(value: DateFormatSettings): string {
|
||||
if (value.custom) {
|
||||
return this.translate.instant('date.custom-date');
|
||||
} else if (value.lastUpdateAgo) {
|
||||
return this.translate.instant('date.last-update-n-ago');
|
||||
} else {
|
||||
if (!this.formatCache[value.format]) {
|
||||
this.formatCache[value.format] = this.date.transform(Date.now(), value.format);
|
||||
}
|
||||
return this.formatCache[value.format];
|
||||
}
|
||||
}
|
||||
|
||||
openDateFormatSettingsPopup($event: Event, matButton: MatButton) {
|
||||
if ($event) {
|
||||
$event.stopPropagation();
|
||||
}
|
||||
const trigger = matButton._elementRef.nativeElement;
|
||||
if (this.popoverService.hasPopover(trigger)) {
|
||||
this.popoverService.hidePopover(trigger);
|
||||
} else {
|
||||
const ctx: any = {
|
||||
dateFormat: deepClone(this.modelValue)
|
||||
};
|
||||
const dateFormatSettingsPanelPopover = this.popoverService.displayPopover(trigger, this.renderer,
|
||||
this.viewContainerRef, DateFormatSettingsPanelComponent, 'top', true, null,
|
||||
ctx,
|
||||
{},
|
||||
{}, {}, true);
|
||||
dateFormatSettingsPanelPopover.tbComponentRef.instance.popover = dateFormatSettingsPanelPopover;
|
||||
dateFormatSettingsPanelPopover.tbComponentRef.instance.dateFormatApplied.subscribe((dateFormat) => {
|
||||
dateFormatSettingsPanelPopover.hide();
|
||||
this.modelValue = dateFormat;
|
||||
this.propagateChange(this.modelValue);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
<!--
|
||||
|
||||
Copyright © 2016-2023 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-date-format-settings-panel">
|
||||
<div class="tb-date-format-settings-title" translate>date.custom-date</div>
|
||||
<div class="tb-form-row no-border no-padding">
|
||||
<div class="fixed-title-width" translate>date.format</div>
|
||||
<mat-form-field class="tb-date-format-input" required fxFlex appearance="outline" subscriptSizing="dynamic">
|
||||
<input matInput [formControl]="dateFormatFormControl" placeholder="{{ 'widget-config.set' | translate }}">
|
||||
<div matSuffix tb-help-popup="date/date-format" [tb-help-popup-style]="{width: '800px'}"></div>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
<div class="tb-form-row no-border no-padding date-format-preview">
|
||||
<div class="fixed-title-width" translate>date.preview</div>
|
||||
<div class="preview-text" fxFlex>{{ previewText }}</div>
|
||||
</div>
|
||||
<div class="tb-date-format-settings-panel-buttons">
|
||||
<button mat-button
|
||||
color="primary"
|
||||
type="button"
|
||||
(click)="cancel()">
|
||||
{{ 'action.cancel' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button
|
||||
color="primary"
|
||||
type="button"
|
||||
(click)="applyDateFormat()"
|
||||
[disabled]="dateFormatFormControl.invalid || !dateFormatFormControl.dirty">
|
||||
{{ 'action.apply' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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';
|
||||
|
||||
.tb-date-format-settings-panel {
|
||||
width: 500px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
@media #{$mat-xs} {
|
||||
width: 90vw;
|
||||
}
|
||||
.tb-date-format-settings-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
letter-spacing: 0.25px;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
}
|
||||
.tb-form-row {
|
||||
.fixed-title-width {
|
||||
min-width: 120px;
|
||||
}
|
||||
&.date-format-preview {
|
||||
align-items: flex-start;
|
||||
.preview-text {
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0.2px;
|
||||
color: rgba(0, 0, 0, 0.38);
|
||||
}
|
||||
}
|
||||
.mat-mdc-form-field.tb-date-format-input {
|
||||
.mat-mdc-text-field-wrapper.mdc-text-field--outlined {
|
||||
.mat-mdc-form-field-icon-suffix {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.tb-date-format-settings-panel-buttons {
|
||||
height: 60px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 16px;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-end;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
///
|
||||
/// Copyright © 2016-2023 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, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
|
||||
import { PageComponent } from '@shared/components/page.component';
|
||||
import { DateFormatSettings } from '@home/components/widget/config/widget-settings.models';
|
||||
import { TbPopoverComponent } from '@shared/components/popover.component';
|
||||
import { UntypedFormControl, Validators } from '@angular/forms';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState } from '@core/core.state';
|
||||
import { DatePipe } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-date-format-settings-panel',
|
||||
templateUrl: './date-format-settings-panel.component.html',
|
||||
providers: [],
|
||||
styleUrls: ['./date-format-settings-panel.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class DateFormatSettingsPanelComponent extends PageComponent implements OnInit {
|
||||
|
||||
@Input()
|
||||
dateFormat: DateFormatSettings;
|
||||
|
||||
@Input()
|
||||
popover: TbPopoverComponent<DateFormatSettingsPanelComponent>;
|
||||
|
||||
@Output()
|
||||
dateFormatApplied = new EventEmitter<DateFormatSettings>();
|
||||
|
||||
dateFormatFormControl: UntypedFormControl;
|
||||
|
||||
previewText = '';
|
||||
|
||||
constructor(private date: DatePipe,
|
||||
protected store: Store<AppState>) {
|
||||
super(store);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.dateFormatFormControl = new UntypedFormControl(this.dateFormat.format, [Validators.required]);
|
||||
this.dateFormatFormControl.valueChanges.subscribe((value: string) => {
|
||||
this.previewText = this.date.transform(Date.now(), value);
|
||||
});
|
||||
this.previewText = this.date.transform(Date.now(), this.dateFormat.format);
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.popover?.hide();
|
||||
}
|
||||
|
||||
applyDateFormat() {
|
||||
this.dateFormat.format = this.dateFormatFormControl.value;
|
||||
this.dateFormatApplied.emit(this.dateFormat);
|
||||
}
|
||||
|
||||
}
|
||||
@ -23,11 +23,7 @@
|
||||
<mat-form-field fxFlex appearance="outline" class="number" subscriptSizing="dynamic">
|
||||
<input matInput type="number" min="0" formControlName="size" placeholder="{{ 'widget-config.set' | translate }}">
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
|
||||
<mat-select formControlName="sizeUnit">
|
||||
<mat-option *ngFor="let cssUnit of cssUnitsList" [value]="cssUnit">{{ cssUnit }}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<tb-css-unit-select fxFlex formControlName="sizeUnit"></tb-css-unit-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tb-form-row no-border no-padding">
|
||||
|
||||
@ -28,7 +28,6 @@ import { PageComponent } from '@shared/components/page.component';
|
||||
import {
|
||||
commonFonts,
|
||||
ComponentStyle,
|
||||
cssUnits,
|
||||
Font,
|
||||
fontStyles,
|
||||
fontStyleTranslations,
|
||||
@ -66,8 +65,6 @@ export class FontSettingsPanelComponent extends PageComponent implements OnInit
|
||||
|
||||
@ViewChild('familyInput', {static: true}) familyInput: ElementRef;
|
||||
|
||||
cssUnitsList = cssUnits;
|
||||
|
||||
fontWeightsList = fontWeights;
|
||||
|
||||
fontWeightTranslationsMap = fontWeightTranslations;
|
||||
|
||||
@ -276,6 +276,11 @@ import { ColorSettingsComponent } from '@home/components/widget/lib/settings/com
|
||||
import {
|
||||
ColorSettingsPanelComponent
|
||||
} from '@home/components/widget/lib/settings/common/color-settings-panel.component';
|
||||
import { CssUnitSelectComponent } from '@home/components/widget/lib/settings/common/css-unit-select.component';
|
||||
import { DateFormatSelectComponent } from '@home/components/widget/lib/settings/common/date-format-select.component';
|
||||
import {
|
||||
DateFormatSettingsPanelComponent
|
||||
} from '@home/components/widget/lib/settings/common/date-format-settings-panel.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -383,7 +388,10 @@ import {
|
||||
FontSettingsComponent,
|
||||
FontSettingsPanelComponent,
|
||||
ColorSettingsComponent,
|
||||
ColorSettingsPanelComponent
|
||||
ColorSettingsPanelComponent,
|
||||
CssUnitSelectComponent,
|
||||
DateFormatSelectComponent,
|
||||
DateFormatSettingsPanelComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@ -495,7 +503,10 @@ import {
|
||||
FontSettingsComponent,
|
||||
FontSettingsPanelComponent,
|
||||
ColorSettingsComponent,
|
||||
ColorSettingsPanelComponent
|
||||
ColorSettingsPanelComponent,
|
||||
CssUnitSelectComponent,
|
||||
DateFormatSelectComponent,
|
||||
DateFormatSettingsPanelComponent
|
||||
]
|
||||
})
|
||||
export class WidgetSettingsModule {
|
||||
|
||||
@ -24,6 +24,7 @@ import {
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { share } from 'rxjs/operators';
|
||||
import { HelpService } from '@core/services/help.service';
|
||||
import { coerceBoolean } from '@shared/decorators/coercion';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-help-markdown',
|
||||
@ -36,7 +37,9 @@ export class HelpMarkdownComponent implements OnDestroy, OnInit, OnChanges {
|
||||
|
||||
@Input() helpContent: string;
|
||||
|
||||
@Input() visible: boolean;
|
||||
@Input()
|
||||
@coerceBoolean()
|
||||
visible: boolean;
|
||||
|
||||
@Input() style: { [klass: string]: any } = {};
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<mat-form-field appearance="outline" class="tb-inline-field tb-suffix-show-on-hover" subscriptSizing="dynamic">
|
||||
<mat-form-field appearance="outline" class="tb-inline-field tb-suffix-show-on-hover" subscriptSizing="dynamic" style="width: 100%;">
|
||||
<input matInput #unitInput [formControl]="unitsFormControl"
|
||||
placeholder="{{ 'widget-config.set' | translate }}"
|
||||
(focusin)="onFocus()"
|
||||
|
||||
@ -37,19 +37,21 @@ export class DateAgoPipe implements PipeTransform {
|
||||
|
||||
}
|
||||
|
||||
transform(value: number, args?: any): string {
|
||||
transform(value: string| number | Date, args?: any): string {
|
||||
if (value) {
|
||||
const applyAgo = !!args?.applyAgo;
|
||||
const short = !!args?.short;
|
||||
const textPart = !!args?.textPart;
|
||||
const ms = Math.floor((+new Date() - +new Date(value)));
|
||||
if (ms < 29 * SECOND) { // less than 30 seconds ago will show as 'Just now'
|
||||
return this.translate.instant('timewindow.just-now');
|
||||
return this.translate.instant(textPart ? 'timewindow.just-now-lower' : 'timewindow.just-now');
|
||||
}
|
||||
let counter;
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const i in intervals) {
|
||||
counter = Math.floor(ms / intervals[i]);
|
||||
if (counter > 0) {
|
||||
let res = this.translate.instant(`timewindow.${i}`, {[i]: counter});
|
||||
let res = this.translate.instant(`timewindow.${i+(short ? '-short' : '')}`, {[i]: counter});
|
||||
if (applyAgo) {
|
||||
res += ' ' + this.translate.instant('timewindow.ago');
|
||||
}
|
||||
|
||||
88
ui-ngx/src/assets/help/en_US/date/date-format.md
Normal file
88
ui-ngx/src/assets/help/en_US/date/date-format.md
Normal file
@ -0,0 +1,88 @@
|
||||
#### Pre-defined format options
|
||||
|
||||
| Option | Equivalent to | Examples (given in `en-US` locale) |
|
||||
|------------|---------------------------------|-----------------------------------------------|
|
||||
| short | M/d/yy, h:mm a | 6/15/15, 9:03 AM |
|
||||
| medium | MMM d, y, h:mm:ss a | Jun 15, 2015, 9:03:01 AM |
|
||||
| long | MMMM d, y, h:mm:ss a z | June 15, 2015 at 9:03:01 AM GMT+1 |
|
||||
| full | EEEE, MMMM d, y, h:mm:ss a zzzz | Monday, June 15, 2015 at 9:03:01 AM GMT+01:00 |
|
||||
| shortDate | M/d/yy | 6/15/15 |
|
||||
| mediumDate | MMM d, y | Jun 15, 2015 |
|
||||
| longDate | MMMM d, y | June 15, 2015 |
|
||||
| fullDate | EEEE, MMMM d, y | Monday, June 15, 2015 |
|
||||
| shortTime | h:mm a | 9:03 AM |
|
||||
| mediumTime | h:mm:ss a | 9:03:01 AM |
|
||||
| longTime | h:mm:ss a z | 9:03:01 AM GMT+1 |
|
||||
| fullTime | h:mm:ss a zzzz | 9:03:01 AM GMT+01:00 |
|
||||
|
||||
#### Custom format options
|
||||
|
||||
You can construct a format string using symbols to specify the components
|
||||
of a date-time value, as described in the following table.
|
||||
Format details depend on the locale.
|
||||
Fields marked with (*) are only available in the extra data set for the given locale.
|
||||
|
||||
| Field type | Format | Description | Example Value |
|
||||
|---------------------|-------------|--------------------------------------------------------------|------------------------------------------------------------|
|
||||
| Era | G, GG & GGG | Abbreviated | AD |
|
||||
| | GGGG | Wide | Anno Domini |
|
||||
| | GGGGG | Narrow | A |
|
||||
| Year | y | Numeric: minimum digits | 2, 20, 201, 2017, 20173 |
|
||||
| | yy | Numeric: 2 digits + zero padded | 02, 20, 01, 17, 73 |
|
||||
| | yyy | Numeric: 3 digits + zero padded | 002, 020, 201, 2017, 20173 |
|
||||
| | yyyy | Numeric: 4 digits or more + zero padded | 0002, 0020, 0201, 2017, 20173 |
|
||||
| Week-numbering year | Y | Numeric: minimum digits | 2, 20, 201, 2017, 20173 |
|
||||
| | YY | Numeric: 2 digits + zero padded | 02, 20, 01, 17, 73 |
|
||||
| | YYY | Numeric: 3 digits + zero padded | 002, 020, 201, 2017, 20173 |
|
||||
| | YYYY | Numeric: 4 digits or more + zero padded | 0002, 0020, 0201, 2017, 20173 |
|
||||
| Month | M | Numeric: 1 digit | 9, 12 |
|
||||
| | MM | Numeric: 2 digits + zero padded | 09, 12 |
|
||||
| | MMM | Abbreviated | Sep |
|
||||
| | MMMM | Wide | September |
|
||||
| | MMMMM | Narrow | S |
|
||||
| Month standalone | L | Numeric: 1 digit | 9, 12 |
|
||||
| | LL | Numeric: 2 digits + zero padded | 09, 12 |
|
||||
| | LLL | Abbreviated | Sep |
|
||||
| | LLLL | Wide | September |
|
||||
| | LLLLL | Narrow | S |
|
||||
| Week of year | w | Numeric: minimum digits | 1... 53 |
|
||||
| | ww | Numeric: 2 digits + zero padded | 01... 53 |
|
||||
| Week of month | W | Numeric: 1 digit | 1... 5 |
|
||||
| Day of month | d | Numeric: minimum digits | 1 |
|
||||
| | dd | Numeric: 2 digits + zero padded | 01 |
|
||||
| Week day | E, EE & EEE | Abbreviated | Tue |
|
||||
| | EEEE | Wide | Tuesday |
|
||||
| | EEEEE | Narrow | T |
|
||||
| | EEEEEE | Short | Tu |
|
||||
| Week day standalone | c, cc | Numeric: 1 digit | 2 |
|
||||
| | ccc | Abbreviated | Tue |
|
||||
| | cccc | Wide | Tuesday |
|
||||
| | ccccc | Narrow | T |
|
||||
| | cccccc | Short | Tu |
|
||||
| Period | a, aa & aaa | Abbreviated | am/pm or AM/PM |
|
||||
| | aaaa | Wide (fallback to `a` when missing) | ante meridiem/post meridiem |
|
||||
| | aaaaa | Narrow | a/p |
|
||||
| Period* | B, BB & BBB | Abbreviated | mid. |
|
||||
| | BBBB | Wide | am, pm, midnight, noon, morning, afternoon, evening, night |
|
||||
| | BBBBB | Narrow | md |
|
||||
| Period standalone* | b, bb & bbb | Abbreviated | mid. |
|
||||
| | bbbb | Wide | am, pm, midnight, noon, morning, afternoon, evening, night |
|
||||
| | bbbbb | Narrow | md |
|
||||
| Hour 1-12 | h | Numeric: minimum digits | 1, 12 |
|
||||
| | hh | Numeric: 2 digits + zero padded | 01, 12 |
|
||||
| Hour 0-23 | H | Numeric: minimum digits | 0, 23 |
|
||||
| | HH | Numeric: 2 digits + zero padded | 00, 23 |
|
||||
| Minute | m | Numeric: minimum digits | 8, 59 |
|
||||
| | mm | Numeric: 2 digits + zero padded | 08, 59 |
|
||||
| Second | s | Numeric: minimum digits | 0... 59 |
|
||||
| | ss | Numeric: 2 digits + zero padded | 00... 59 |
|
||||
| Fractional seconds | S | Numeric: 1 digit | 0... 9 |
|
||||
| | SS | Numeric: 2 digits + zero padded | 00... 99 |
|
||||
| | SSS | Numeric: 3 digits + zero padded (= milliseconds) | 000... 999 |
|
||||
| Zone | z, zz & zzz | Short specific non location format (fallback to O) | GMT-8 |
|
||||
| | zzzz | Long specific non location format (fallback to OOOO) | GMT-08:00 |
|
||||
| | Z, ZZ & ZZZ | ISO8601 basic format | -0800 |
|
||||
| | ZZZZ | Long localized GMT format | GMT-8:00 |
|
||||
| | ZZZZZ | ISO8601 extended format + Z indicator for offset 0 (= XXXXX) | -08:00 |
|
||||
| | O, OO & OOO | Short localized GMT format | GMT-8 |
|
||||
| | OOOO | Long localized GMT format | GMT-08:00 |
|
||||
@ -940,6 +940,13 @@
|
||||
"edges": "Customer edge instances",
|
||||
"manage-edges": "Manage edges"
|
||||
},
|
||||
"date": {
|
||||
"last-update-n-ago": "Last update N ago",
|
||||
"last-update-n-ago-text": "Last update {{ agoText }}",
|
||||
"custom-date": "Custom date",
|
||||
"format": "Format",
|
||||
"preview": "Preview"
|
||||
},
|
||||
"datetime": {
|
||||
"date-from": "Date from",
|
||||
"time-from": "Time from",
|
||||
@ -3832,15 +3839,22 @@
|
||||
"timewindow": {
|
||||
"timewindow": "Timewindow",
|
||||
"years": "{ years, plural, =1 { year } other {# years } }",
|
||||
"years-short": "{{ years }}y",
|
||||
"months": "{ months, plural, =1 { month } other {# months } }",
|
||||
"months-short": "{{ months }}M",
|
||||
"weeks": "{ weeks, plural, =1 { week } other {# weeks } }",
|
||||
"weeks-short": "{{ weeks }}w",
|
||||
"days": "{ days, plural, =1 { day } other {# days } }",
|
||||
"days-short": "{{ days }}d",
|
||||
"hours": "{ hours, plural, =0 { hour } =1 {1 hour } other {# hours } }",
|
||||
"hr": "{{ hr }} hr",
|
||||
"hr-short": "{{ hr }}h",
|
||||
"minutes": "{ minutes, plural, =0 { minute } =1 {1 minute } other {# minutes } }",
|
||||
"min": "{{ min }} min",
|
||||
"min-short": "{{ min }}m",
|
||||
"seconds": "{ seconds, plural, =0 { second } =1 {1 second } other {# seconds } }",
|
||||
"sec": "{{ sec }} sec",
|
||||
"sec-short": "{{ sec }}s",
|
||||
"short": {
|
||||
"days": "{ days, plural, =1 {1 day } other {# days } }",
|
||||
"hours": "{ hours, plural, =1 {1 hour } other {# hours } }",
|
||||
@ -3859,6 +3873,7 @@
|
||||
"hide": "Hide",
|
||||
"interval": "Interval",
|
||||
"just-now": "Just now",
|
||||
"just-now-lower": "just now",
|
||||
"ago": "ago"
|
||||
},
|
||||
"unit": {
|
||||
@ -4208,6 +4223,7 @@
|
||||
"decimals": "Number of digits after floating point",
|
||||
"units-short": "Units",
|
||||
"decimals-short": "Decimals",
|
||||
"decimals-suffix": "decimals",
|
||||
"timewindow": "Timewindow",
|
||||
"use-dashboard-timewindow": "Use dashboard timewindow",
|
||||
"use-widget-timewindow": "Use widget timewindow",
|
||||
@ -5235,7 +5251,10 @@
|
||||
"layout-simplified": "Simplified",
|
||||
"layout-horizontal": "Horizontal",
|
||||
"layout-horizontal-reversed": "Horizontal reversed",
|
||||
"label": "Label"
|
||||
"label": "Label",
|
||||
"icon": "Icon",
|
||||
"value": "Value",
|
||||
"date": "Date"
|
||||
},
|
||||
"table": {
|
||||
"common-table-settings": "Common Table Settings",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user