Browse Source

Merge remote-tracking branch 'upstream/master' into fix/apiUsageDashboard

pull/10326/head
Vladyslav_Prykhodko 3 years ago
parent
commit
3c85d8ff14
  1. 31
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html
  2. 31
      ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts
  3. 16
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts
  4. 4
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss
  5. 62
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts
  6. 3
      ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts
  7. 33
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html
  8. 38
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts
  9. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts
  10. 41
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html
  11. 21
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts
  12. 75
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component.html
  13. 147
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component.ts
  14. 5
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts
  15. 16
      ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.html
  16. 33
      ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.ts
  17. 2
      ui-ngx/src/app/shared/models/widget.models.ts
  18. 7
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  19. 6
      ui-ngx/src/form.scss

31
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.html

@ -114,33 +114,10 @@
axisType="xAxis">
</tb-time-series-chart-axis-settings>
</div>
<div *ngIf="chartType === TimeSeriesChartType.default || chartType === TimeSeriesChartType.bar"
class="tb-form-panel" formGroupName="noAggregationBarWidthSettings">
<div fxLayout="row" fxLayoutAlign="space-between center">
<div fxFlex translate>widgets.time-series-chart.no-aggregation-bar-width-strategy</div>
<tb-toggle-select formControlName="strategy" selectMediaBreakpoint="xs">
<tb-toggle-option *ngFor="let strategy of timeSeriesChartNoAggregationBarWidthStrategies" [value]="strategy">
{{ timeSeriesChartNoAggregationBarWidthStrategyTranslations.get(strategy) | translate }}
</tb-toggle-option>
</tb-toggle-select>
</div>
<div *ngIf="timeSeriesChartWidgetConfigForm.get('noAggregationBarWidthSettings.strategy').value === TimeSeriesChartNoAggregationBarWidthStrategy.group"
class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.bar-group-interval-width</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="groupIntervalWidth" min="100" step="1" placeholder="{{ 'widget-config.set' | translate }}"/>
<span matSuffix>ms</span>
</mat-form-field>
</div>
<div *ngIf="timeSeriesChartWidgetConfigForm.get('noAggregationBarWidthSettings.strategy').value === TimeSeriesChartNoAggregationBarWidthStrategy.separate"
class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.separate-bar-width</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="separateBarWidth" min="100" step="1" placeholder="{{ 'widget-config.set' | translate }}"/>
<span matSuffix>ms</span>
</mat-form-field>
</div>
</div>
<tb-time-series-no-aggregation-bar-width-settings
*ngIf="chartType === TimeSeriesChartType.default || chartType === TimeSeriesChartType.bar"
formControlName="noAggregationBarWidthSettings">
</tb-time-series-no-aggregation-bar-width-settings>
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="timeSeriesChartWidgetConfigForm.get('showLegend').value"
[disabled]="!timeSeriesChartWidgetConfigForm.get('showLegend').value">

31
ui-ngx/src/app/modules/home/components/widget/config/basic/chart/time-series-chart-basic-config.component.ts

@ -45,12 +45,7 @@ import {
TimeSeriesChartWidgetSettings
} from '@home/components/widget/lib/chart/time-series-chart-widget.models';
import { EChartsTooltipTrigger } from '@home/components/widget/lib/chart/echarts-widget.models';
import {
timeSeriesChartNoAggregationBarWidthStrategies,
TimeSeriesChartNoAggregationBarWidthStrategy,
timeSeriesChartNoAggregationBarWidthStrategyTranslations,
TimeSeriesChartType
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { TimeSeriesChartType } from '@home/components/widget/lib/chart/time-series-chart.models';
@Component({
selector: 'tb-time-series-chart-basic-config',
@ -76,12 +71,6 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
legendPositionTranslationMap = legendPositionTranslationMap;
TimeSeriesChartNoAggregationBarWidthStrategy = TimeSeriesChartNoAggregationBarWidthStrategy;
timeSeriesChartNoAggregationBarWidthStrategies = timeSeriesChartNoAggregationBarWidthStrategies;
timeSeriesChartNoAggregationBarWidthStrategyTranslations = timeSeriesChartNoAggregationBarWidthStrategyTranslations;
timeSeriesChartWidgetConfigForm: UntypedFormGroup;
tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this);
@ -140,11 +129,7 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
yAxis: [settings.yAxis, []],
xAxis: [settings.xAxis, []],
noAggregationBarWidthSettings: this.fb.group({
strategy: [settings.noAggregationBarWidthSettings.strategy, []],
groupIntervalWidth: [settings.noAggregationBarWidthSettings.groupIntervalWidth, [Validators.min(100)]],
separateBarWidth: [settings.noAggregationBarWidthSettings.separateBarWidth, [Validators.min(100)]],
}),
noAggregationBarWidthSettings: [settings.noAggregationBarWidthSettings, []],
showLegend: [settings.showLegend, []],
legendLabelFont: [settings.legendLabelFont, []],
@ -227,7 +212,7 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
}
protected validatorTriggers(): string[] {
return ['showTitle', 'showIcon', 'showLegend', 'showTooltip', 'tooltipShowDate', 'noAggregationBarWidthSettings.strategy'];
return ['showTitle', 'showIcon', 'showLegend', 'showTooltip', 'tooltipShowDate'];
}
protected updateValidators(emitEvent: boolean, trigger?: string) {
@ -236,8 +221,6 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
const showLegend: boolean = this.timeSeriesChartWidgetConfigForm.get('showLegend').value;
const showTooltip: boolean = this.timeSeriesChartWidgetConfigForm.get('showTooltip').value;
const tooltipShowDate: boolean = this.timeSeriesChartWidgetConfigForm.get('tooltipShowDate').value;
const noAggregationBarWidthSettingsStrategy: TimeSeriesChartNoAggregationBarWidthStrategy =
this.timeSeriesChartWidgetConfigForm.get('noAggregationBarWidthSettings').get('strategy').value;
if (showTitle) {
this.timeSeriesChartWidgetConfigForm.get('title').enable();
@ -266,14 +249,6 @@ export class TimeSeriesChartBasicConfigComponent extends BasicWidgetConfigCompon
this.timeSeriesChartWidgetConfigForm.get('iconColor').disable();
}
if (noAggregationBarWidthSettingsStrategy === TimeSeriesChartNoAggregationBarWidthStrategy.group) {
this.timeSeriesChartWidgetConfigForm.get('noAggregationBarWidthSettings').get('groupIntervalWidth').enable();
this.timeSeriesChartWidgetConfigForm.get('noAggregationBarWidthSettings').get('separateBarWidth').disable();
} else if (noAggregationBarWidthSettingsStrategy === TimeSeriesChartNoAggregationBarWidthStrategy.separate) {
this.timeSeriesChartWidgetConfigForm.get('noAggregationBarWidthSettings').get('groupIntervalWidth').disable();
this.timeSeriesChartWidgetConfigForm.get('noAggregationBarWidthSettings').get('separateBarWidth').enable();
}
if (showLegend) {
this.timeSeriesChartWidgetConfigForm.get('legendLabelFont').enable();
this.timeSeriesChartWidgetConfigForm.get('legendLabelColor').enable();

16
ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts

@ -19,7 +19,6 @@ import { Interval, IntervalMath } from '@shared/models/time/time.models';
import { LabelFormatterCallback, SeriesLabelOption } from 'echarts/types/src/util/types';
import {
TimeSeriesChartDataItem,
TimeSeriesChartNoAggregationBarWidthSettings,
TimeSeriesChartNoAggregationBarWidthStrategy
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { CustomSeriesRenderItemParams } from 'echarts';
@ -38,7 +37,9 @@ export interface BarRenderContext {
barsCount?: number;
barIndex?: number;
noAggregation: boolean;
noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings;
noAggregationBarWidthStrategy: TimeSeriesChartNoAggregationBarWidthStrategy;
noAggregationWidthRelative?: boolean;
noAggregationWidth?: number;
timeInterval?: Interval;
visualSettings?: BarVisualSettings;
labelOption?: SeriesLabelOption;
@ -54,16 +55,15 @@ export const renderTimeSeriesBar = (params: CustomSeriesRenderItemParams, api: C
let interval = end - start;
const ts = start ? start : time;
const noAggregationGroup = renderCtx.noAggregation &&
renderCtx.noAggregationBarWidthSettings.strategy === TimeSeriesChartNoAggregationBarWidthStrategy.group;
const separateBar = renderCtx.noAggregation &&
renderCtx.noAggregationBarWidthSettings.strategy === TimeSeriesChartNoAggregationBarWidthStrategy.separate;
renderCtx.noAggregationBarWidthStrategy === TimeSeriesChartNoAggregationBarWidthStrategy.separate;
if (renderCtx.noAggregation) {
if (noAggregationGroup) {
interval = renderCtx.noAggregationBarWidthSettings.groupIntervalWidth;
if (renderCtx.noAggregationWidthRelative) {
const scaleWidth = api.getWidth() / api.size([1,0])[0];
interval = scaleWidth * (renderCtx.noAggregationWidth / 100);
} else {
interval = renderCtx.noAggregationBarWidthSettings.separateBarWidth;
interval = renderCtx.noAggregationWidth;
}
start = time - interval / 2;
end = time + interval / 2;

4
ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-widget.component.scss

@ -23,7 +23,7 @@ $maxLegendHeight: 35%;
position: relative;
display: flex;
flex-direction: column;
gap: 16px;
gap: 8px;
padding: 20px 24px 24px 24px;
> div:not(.tb-time-series-chart-overlay) {
z-index: 1;
@ -44,7 +44,7 @@ $maxLegendHeight: 35%;
min-height: 0;
display: flex;
flex-direction: column;
gap: 16px;
gap: 8px;
&.legend-top {
flex-direction: column-reverse;
}

62
ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.models.ts

@ -25,7 +25,7 @@ import {
import { ComponentStyle, Font, simpleDateFormat, textStyle } from '@shared/models/widget-settings.models';
import { XAXisOption, YAXisOption } from 'echarts/types/dist/shared';
import { CustomSeriesOption, LineSeriesOption } from 'echarts/charts';
import { formatValue, isDefinedAndNotNull, isUndefinedOrNull, parseFunction } from '@core/utils';
import { formatValue, isDefinedAndNotNull, isUndefined, isUndefinedOrNull, parseFunction } from '@core/utils';
import { LinearGradientObject } from 'zrender/lib/graphic/LinearGradient';
import tinycolor from 'tinycolor2';
import Axis2D from 'echarts/types/src/coord/cartesian/Axis2D';
@ -293,6 +293,7 @@ export interface TimeSeriesChartYAxisSettings extends TimeSeriesChartAxisSetting
min?: number | string;
max?: number | string;
intervalCalculator?: string;
ticksFormatter?: string;
}
export interface TimeSeriesChartThreshold {
@ -391,10 +392,16 @@ export const timeSeriesChartNoAggregationBarWidthStrategyTranslations = new Map<
]
);
export interface TimeSeriesChartBarWidth {
relative?: boolean;
relativeWidth?: number;
absoluteWidth?: number;
}
export interface TimeSeriesChartNoAggregationBarWidthSettings {
strategy: TimeSeriesChartNoAggregationBarWidthStrategy;
groupIntervalWidth?: number;
separateBarWidth?: number;
groupWidth?: TimeSeriesChartBarWidth;
barWidth?: TimeSeriesChartBarWidth;
}
export interface TimeSeriesChartSettings extends EChartsTooltipWidgetSettings {
@ -435,6 +442,7 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
lineHeight: '1'
},
tickLabelColor: timeSeriesChartColorScheme['axis.tickLabel'].light,
ticksFormatter: null,
showTicks: true,
ticksColor: timeSeriesChartColorScheme['axis.ticks'].light,
showLine: true,
@ -474,8 +482,16 @@ export const timeSeriesChartDefaultSettings: TimeSeriesChartSettings = {
},
noAggregationBarWidthSettings: {
strategy: TimeSeriesChartNoAggregationBarWidthStrategy.group,
groupIntervalWidth: 1000,
separateBarWidth: 1000
groupWidth: {
relative: true,
relativeWidth: 2,
absoluteWidth: 1000
},
barWidth: {
relative: true,
relativeWidth: 2,
absoluteWidth: 1000
}
},
showTooltip: true,
tooltipTrigger: EChartsTooltipTrigger.axis,
@ -633,6 +649,7 @@ export interface TimeSeriesChartYAxis {
units: string;
option: YAXisOption & ValueAxisBaseOption;
intervalCalculator?: (axis: Axis2D) => number;
ticksFormatter?: (value: any) => string;
}
export const createTimeSeriesYAxis = (axisId: string, units: string,
@ -642,6 +659,10 @@ export const createTimeSeriesYAxis = (axisId: string, units: string,
settings.tickLabelColor, darkMode, 'axis.tickLabel');
const yAxisNameStyle = createChartTextStyle(settings.labelFont,
settings.labelColor, darkMode, 'axis.label');
let ticksFormatter: (value: any) => string;
if (settings.ticksFormatter && settings.ticksFormatter.length) {
ticksFormatter = parseFunction(settings.ticksFormatter, ['value']);
}
const yAxis: TimeSeriesChartYAxis = {
id: axisId,
units,
@ -685,7 +706,18 @@ export const createTimeSeriesYAxis = (axisId: string, units: string,
fontWeight: yAxisTickLabelStyle.fontWeight,
fontFamily: yAxisTickLabelStyle.fontFamily,
fontSize: yAxisTickLabelStyle.fontSize,
formatter: (value: any) => formatValue(value, decimals, units, false)
formatter: (value: any) => {
let result: string;
if (ticksFormatter) {
try {
result = ticksFormatter(value);
} catch (_e) {}
}
if (isUndefined(result)) {
result = formatValue(value, decimals, units, false);
}
return result;
}
},
splitLine: {
show: settings.showSplitLines,
@ -757,12 +789,12 @@ export const createTimeSeriesXAxisOption = (settings: TimeSeriesChartAxisSetting
export const generateChartData = (dataItems: TimeSeriesChartDataItem[],
thresholdItems: TimeSeriesChartThresholdItem[],
timeInterval: Interval,
stack: boolean,
noAggregation: boolean,
noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings,
stack: boolean,
darkMode: boolean): Array<LineSeriesOption | CustomSeriesOption> => {
let series = generateChartSeries(dataItems, timeInterval,
noAggregation, noAggregationBarWidthSettings, stack, darkMode);
stack, noAggregation, noAggregationBarWidthSettings, darkMode);
if (thresholdItems.length) {
const thresholds = generateChartThresholds(thresholdItems, darkMode);
series = series.concat(thresholds);
@ -874,9 +906,9 @@ const createThresholdData = (val: string | number, item: TimeSeriesChartThreshol
const generateChartSeries = (dataItems: TimeSeriesChartDataItem[],
timeInterval: Interval,
stack: boolean,
noAggregation: boolean,
noAggregationBarWidthSettings: TimeSeriesChartNoAggregationBarWidthSettings,
stack: boolean,
darkMode: boolean): Array<LineSeriesOption | CustomSeriesOption> => {
const series: Array<LineSeriesOption | CustomSeriesOption> = [];
const enabledDataItems = dataItems.filter(d => d.enabled);
@ -895,7 +927,17 @@ const generateChartSeries = (dataItems: TimeSeriesChartDataItem[],
for (const item of enabledDataItems) {
if (item.dataKey.settings.type === TimeSeriesChartSeriesType.bar) {
if (!item.barRenderContext) {
item.barRenderContext = {noAggregation, noAggregationBarWidthSettings};
item.barRenderContext = {noAggregation,
noAggregationBarWidthStrategy: noAggregationBarWidthSettings.strategy};
const targetWidth = noAggregationBarWidthSettings.strategy === TimeSeriesChartNoAggregationBarWidthStrategy.group ?
noAggregationBarWidthSettings.groupWidth : noAggregationBarWidthSettings.barWidth;
if (targetWidth.relative) {
item.barRenderContext.noAggregationWidthRelative = true;
item.barRenderContext.noAggregationWidth = targetWidth.relativeWidth;
} else {
item.barRenderContext.noAggregationWidthRelative = false;
item.barRenderContext.noAggregationWidth = targetWidth.absoluteWidth;
}
}
item.barRenderContext.noAggregation = noAggregation;
item.barRenderContext.barsCount = barsCount;

3
ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart.ts

@ -492,8 +492,9 @@ export class TbTimeSeriesChart {
private updateSeries(): Array<LineSeriesOption | CustomSeriesOption> {
return generateChartData(this.dataItems, this.thresholdItems,
this.ctx.timeWindow.interval,
this.settings.stack,
this.noAggregation,
this.settings.noAggregationBarWidthSettings, this.settings.stack, this.darkMode);
this.settings.noAggregationBarWidthSettings, this.darkMode);
}
private updateAxes() {

33
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.html

@ -41,40 +41,19 @@
<div class="tb-form-panel-title" translate>widgets.time-series-chart.axis.axes</div>
<tb-time-series-chart-axis-settings
formControlName="yAxis"
advanced
axisType="yAxis">
</tb-time-series-chart-axis-settings>
<tb-time-series-chart-axis-settings
formControlName="xAxis"
advanced
axisType="xAxis">
</tb-time-series-chart-axis-settings>
</div>
<div *ngIf="chartType === TimeSeriesChartType.default || chartType === TimeSeriesChartType.bar"
class="tb-form-panel" formGroupName="noAggregationBarWidthSettings">
<div fxLayout="row" fxLayoutAlign="space-between center">
<div fxFlex translate>widgets.time-series-chart.no-aggregation-bar-width-strategy</div>
<tb-toggle-select formControlName="strategy" selectMediaBreakpoint="xs">
<tb-toggle-option *ngFor="let strategy of timeSeriesChartNoAggregationBarWidthStrategies" [value]="strategy">
{{ timeSeriesChartNoAggregationBarWidthStrategyTranslations.get(strategy) | translate }}
</tb-toggle-option>
</tb-toggle-select>
</div>
<div *ngIf="timeSeriesChartWidgetSettingsForm.get('noAggregationBarWidthSettings.strategy').value === TimeSeriesChartNoAggregationBarWidthStrategy.group"
class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.bar-group-interval-width</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="groupIntervalWidth" min="100" step="1" placeholder="{{ 'widget-config.set' | translate }}"/>
<span matSuffix>ms</span>
</mat-form-field>
</div>
<div *ngIf="timeSeriesChartWidgetSettingsForm.get('noAggregationBarWidthSettings.strategy').value === TimeSeriesChartNoAggregationBarWidthStrategy.separate"
class="tb-form-row space-between column-xs">
<div translate>widgets.time-series-chart.separate-bar-width</div>
<mat-form-field class="medium-width number" appearance="outline" subscriptSizing="dynamic">
<input matInput type="number" formControlName="separateBarWidth" min="100" step="1" placeholder="{{ 'widget-config.set' | translate }}"/>
<span matSuffix>ms</span>
</mat-form-field>
</div>
</div>
<tb-time-series-no-aggregation-bar-width-settings
*ngIf="chartType === TimeSeriesChartType.default || chartType === TimeSeriesChartType.bar"
formControlName="noAggregationBarWidthSettings">
</tb-time-series-no-aggregation-bar-width-settings>
<div class="tb-form-panel tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="timeSeriesChartWidgetSettingsForm.get('showLegend').value"
[disabled]="!timeSeriesChartWidgetSettingsForm.get('showLegend').value">

38
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component.ts

@ -22,23 +22,17 @@ import {
WidgetSettings,
WidgetSettingsComponent
} from '@shared/models/widget.models';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { formatValue, isDefinedAndNotNull, mergeDeep } from '@core/utils';
import { DateFormatProcessor, DateFormatSettings } from '@shared/models/widget-settings.models';
import {
barChartWithLabelsDefaultSettings
} from '@home/components/widget/lib/chart/bar-chart-with-labels-widget.models';
import { EChartsTooltipTrigger } from '../../chart/echarts-widget.models';
import {
timeSeriesChartWidgetDefaultSettings, TimeSeriesChartWidgetSettings
timeSeriesChartWidgetDefaultSettings,
TimeSeriesChartWidgetSettings
} from '@home/components/widget/lib/chart/time-series-chart-widget.models';
import {
timeSeriesChartNoAggregationBarWidthStrategies,
TimeSeriesChartNoAggregationBarWidthStrategy,
timeSeriesChartNoAggregationBarWidthStrategyTranslations, TimeSeriesChartType
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { TimeSeriesChartType } from '@home/components/widget/lib/chart/time-series-chart.models';
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
@Component({
@ -65,12 +59,6 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon
legendPositionTranslationMap = legendPositionTranslationMap;
TimeSeriesChartNoAggregationBarWidthStrategy = TimeSeriesChartNoAggregationBarWidthStrategy;
timeSeriesChartNoAggregationBarWidthStrategies = timeSeriesChartNoAggregationBarWidthStrategies;
timeSeriesChartNoAggregationBarWidthStrategyTranslations = timeSeriesChartNoAggregationBarWidthStrategyTranslations;
timeSeriesChartWidgetSettingsForm: UntypedFormGroup;
tooltipValuePreviewFn = this._tooltipValuePreviewFn.bind(this);
@ -111,11 +99,7 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon
yAxis: [settings.yAxis, []],
xAxis: [settings.xAxis, []],
noAggregationBarWidthSettings: this.fb.group({
strategy: [settings.noAggregationBarWidthSettings.strategy, []],
groupIntervalWidth: [settings.noAggregationBarWidthSettings.groupIntervalWidth, [Validators.min(100)]],
separateBarWidth: [settings.noAggregationBarWidthSettings.separateBarWidth, [Validators.min(100)]],
}),
noAggregationBarWidthSettings: [settings.noAggregationBarWidthSettings, []],
showLegend: [settings.showLegend, []],
legendLabelFont: [settings.legendLabelFont, []],
@ -140,23 +124,13 @@ export class TimeSeriesChartWidgetSettingsComponent extends WidgetSettingsCompon
}
protected validatorTriggers(): string[] {
return ['showLegend', 'showTooltip', 'tooltipShowDate', 'noAggregationBarWidthSettings.strategy'];
return ['showLegend', 'showTooltip', 'tooltipShowDate'];
}
protected updateValidators(emitEvent: boolean) {
const showLegend: boolean = this.timeSeriesChartWidgetSettingsForm.get('showLegend').value;
const showTooltip: boolean = this.timeSeriesChartWidgetSettingsForm.get('showTooltip').value;
const tooltipShowDate: boolean = this.timeSeriesChartWidgetSettingsForm.get('tooltipShowDate').value;
const noAggregationBarWidthSettingsStrategy: TimeSeriesChartNoAggregationBarWidthStrategy =
this.timeSeriesChartWidgetSettingsForm.get('noAggregationBarWidthSettings').get('strategy').value;
if (noAggregationBarWidthSettingsStrategy === TimeSeriesChartNoAggregationBarWidthStrategy.group) {
this.timeSeriesChartWidgetSettingsForm.get('noAggregationBarWidthSettings').get('groupIntervalWidth').enable();
this.timeSeriesChartWidgetSettingsForm.get('noAggregationBarWidthSettings').get('separateBarWidth').disable();
} else if (noAggregationBarWidthSettingsStrategy === TimeSeriesChartNoAggregationBarWidthStrategy.separate) {
this.timeSeriesChartWidgetSettingsForm.get('noAggregationBarWidthSettings').get('groupIntervalWidth').disable();
this.timeSeriesChartWidgetSettingsForm.get('noAggregationBarWidthSettings').get('separateBarWidth').enable();
}
if (showLegend) {
this.timeSeriesChartWidgetSettingsForm.get('legendLabelFont').enable();

3
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/widget-action.component.ts

@ -28,6 +28,7 @@ import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@an
import {
WidgetAction,
WidgetActionType,
widgetActionTypes,
widgetActionTypeTranslationMap,
widgetType
} from '@shared/models/widget.models';
@ -88,7 +89,7 @@ export class WidgetActionComponent implements ControlValueAccessor, OnInit, Vali
@Input()
callbacks: WidgetActionCallbacks;
widgetActionTypes = Object.keys(WidgetActionType);
widgetActionTypes = widgetActionTypes;
widgetActionTypeTranslations = widgetActionTypeTranslationMap;
widgetActionType = WidgetActionType;

41
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.html

@ -56,21 +56,32 @@
</mat-select>
</mat-form-field>
</div>
<div class="tb-form-row space-between column-xs">
<mat-slide-toggle class="mat-slide" formControlName="showTickLabels">
<div translate>widgets.time-series-chart.axis.tick-labels</div>
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="tickLabelFont"
clearButton
disabledLineHeight
forceSizeUnit="px"
previewText="100">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="tickLabelColor">
</tb-color-input>
<div class="tb-form-panel no-padding stroked">
<div class="tb-form-row no-border space-between column-xs">
<mat-slide-toggle class="mat-slide" formControlName="showTickLabels">
<div translate>widgets.time-series-chart.axis.tick-labels</div>
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-font-settings formControlName="tickLabelFont"
clearButton
disabledLineHeight
forceSizeUnit="px"
previewText="100">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="tickLabelColor">
</tb-color-input>
</div>
</div>
<div *ngIf="axisType === 'yAxis' && advanced" class="tb-form-row no-border">
<tb-js-func fxFlex
formControlName="ticksFormatter"
[globalVariables]="functionScopeVariables"
[functionArgs]="['value']"
functionTitle="{{ 'widgets.time-series-chart.axis.ticks-formatter-function' | translate }}"
helpId="widget/lib/flot/ticks_formatter_fn">
</tb-js-func>
</div>
</div>
<div class="tb-form-row space-between">

21
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-chart-axis-settings.component.ts

@ -22,9 +22,9 @@ import {
TimeSeriesChartAxisSettings,
TimeSeriesChartYAxisSettings
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { merge } from 'rxjs';
import { coerceBoolean } from '@shared/decorators/coercion';
import { WidgetService } from '@core/http/widget.service';
@Component({
selector: 'tb-time-series-chart-axis-settings',
@ -48,20 +48,26 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu
timeSeriesAxisPositionTranslations = timeSeriesAxisPositionTranslations;
functionScopeVariables = this.widgetService.getWidgetScopeVariables();
@Input()
disabled: boolean;
@Input()
axisType: 'xAxis' | 'yAxis' = 'xAxis';
@Input()
@coerceBoolean()
advanced = false;
private modelValue: TimeSeriesChartAxisSettings | TimeSeriesChartYAxisSettings;
private propagateChange = null;
public axisSettingsFormGroup: UntypedFormGroup;
constructor(protected store: Store<AppState>,
private fb: UntypedFormBuilder) {
constructor(private fb: UntypedFormBuilder,
private widgetService: WidgetService,) {
}
ngOnInit(): void {
@ -88,6 +94,7 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu
splitLinesColor: [null, []]
});
if (this.axisType === 'yAxis') {
this.axisSettingsFormGroup.addControl('ticksFormatter', this.fb.control(null, []));
this.axisSettingsFormGroup.addControl('min', this.fb.control(null, []));
this.axisSettingsFormGroup.addControl('max', this.fb.control(null, []));
}
@ -143,9 +150,15 @@ export class TimeSeriesChartAxisSettingsComponent implements OnInit, ControlValu
if (showTickLabels) {
this.axisSettingsFormGroup.get('tickLabelFont').enable({emitEvent: false});
this.axisSettingsFormGroup.get('tickLabelColor').enable({emitEvent: false});
if (this.axisType === 'yAxis') {
this.axisSettingsFormGroup.get('ticksFormatter').enable({emitEvent: false});
}
} else {
this.axisSettingsFormGroup.get('tickLabelFont').disable({emitEvent: false});
this.axisSettingsFormGroup.get('tickLabelColor').disable({emitEvent: false});
if (this.axisType === 'yAxis') {
this.axisSettingsFormGroup.get('ticksFormatter').disable({emitEvent: false});
}
}
if (showTicks) {
this.axisSettingsFormGroup.get('ticksColor').enable({emitEvent: false});

75
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component.html

@ -0,0 +1,75 @@
<!--
Copyright © 2016-2024 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-form-panel" [formGroup]="barWidthSettingsFormGroup">
<div fxLayout="row" fxLayoutAlign="space-between center">
<div fxFlex translate>widgets.time-series-chart.no-aggregation-bar-width-strategy</div>
<tb-toggle-select formControlName="strategy" selectMediaBreakpoint="xs">
<tb-toggle-option *ngFor="let strategy of timeSeriesChartNoAggregationBarWidthStrategies" [value]="strategy">
{{ timeSeriesChartNoAggregationBarWidthStrategyTranslations.get(strategy) | translate }}
</tb-toggle-option>
</tb-toggle-select>
</div>
<ng-container *ngIf="barWidthSettingsFormGroup.get('strategy').value === TimeSeriesChartNoAggregationBarWidthStrategy.group">
<ng-container
*ngTemplateOutlet="barWidth;
context: {
formGroup: barWidthSettingsFormGroup.get('groupWidth'),
label: 'widgets.time-series-chart.bar-group-width'
}">
</ng-container>
</ng-container>
<ng-container *ngIf="barWidthSettingsFormGroup.get('strategy').value === TimeSeriesChartNoAggregationBarWidthStrategy.separate">
<ng-container
*ngTemplateOutlet="barWidth;
context: {
formGroup: barWidthSettingsFormGroup.get('barWidth'),
label: 'widgets.time-series-chart.bar-width'
}">
</ng-container>
</ng-container>
</div>
<ng-template #barWidth let-label="label" let-formGroup="formGroup">
<div class="tb-form-row space-between column-lt-md" [formGroup]="formGroup">
<div translate>{{ label }}</div>
<div fxLayout="row" fxFlex.lt-md fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="medium-width" fxFlex.lt-md appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="relative">
<mat-select-trigger>
{{ (formGroup.get('relative').value ?
'widgets.time-series-chart.bar-width-relative' :
'widgets.time-series-chart.bar-width-absolute') | translate }}
</mat-select-trigger>
<mat-option [value]="true">
{{ 'widgets.time-series-chart.bar-width-relative' | translate }}
</mat-option>
<mat-option [value]="false">
{{ 'widgets.time-series-chart.bar-width-absolute' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field *ngIf="formGroup.get('relative').value" class="number fixed-width" appearance="outline" subscriptSizing="dynamic">
<input required matInput type="number" formControlName="relativeWidth" min="0.1" max="100" placeholder="{{ 'widget-config.set' | translate }}"/>
<span matSuffix>%</span>
</mat-form-field>
<mat-form-field *ngIf="!formGroup.get('relative').value" class="number fixed-width" appearance="outline" subscriptSizing="dynamic">
<input required matInput type="number" formControlName="absoluteWidth" min="100" step="1" placeholder="{{ 'widget-config.set' | translate }}"/>
<span matSuffix>ms</span>
</mat-form-field>
</div>
</div>
</ng-template>

147
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component.ts

@ -0,0 +1,147 @@
///
/// Copyright © 2016-2024 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,
UntypedFormBuilder,
UntypedFormGroup,
Validators
} from '@angular/forms';
import {
TimeSeriesChartNoAggregationBarWidthSettings,
timeSeriesChartNoAggregationBarWidthStrategies,
TimeSeriesChartNoAggregationBarWidthStrategy,
timeSeriesChartNoAggregationBarWidthStrategyTranslations
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { merge } from 'rxjs';
@Component({
selector: 'tb-time-series-no-aggregation-bar-width-settings',
templateUrl: './time-series-no-aggregation-bar-width-settings.component.html',
styleUrls: ['./../../widget-settings.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TimeSeriesNoAggregationBarWidthSettingsComponent),
multi: true
}
]
})
export class TimeSeriesNoAggregationBarWidthSettingsComponent implements OnInit, ControlValueAccessor {
TimeSeriesChartNoAggregationBarWidthStrategy = TimeSeriesChartNoAggregationBarWidthStrategy;
timeSeriesChartNoAggregationBarWidthStrategies = timeSeriesChartNoAggregationBarWidthStrategies;
timeSeriesChartNoAggregationBarWidthStrategyTranslations = timeSeriesChartNoAggregationBarWidthStrategyTranslations;
@Input()
disabled: boolean;
private modelValue: TimeSeriesChartNoAggregationBarWidthSettings;
private propagateChange = null;
public barWidthSettingsFormGroup: UntypedFormGroup;
constructor(private fb: UntypedFormBuilder) {
}
ngOnInit(): void {
this.barWidthSettingsFormGroup = this.fb.group({
strategy: [null, []],
groupWidth: this.fb.group({
relative: [null, []],
relativeWidth: [null, [Validators.required, Validators.min(0.1), Validators.max(100)]],
absoluteWidth: [null, [Validators.required, Validators.min(100)]]
}),
barWidth: this.fb.group({
relative: [null, []],
relativeWidth: [null, [Validators.required, Validators.min(0.1), Validators.max(100)]],
absoluteWidth: [null, [Validators.required, Validators.min(100)]]
})
});
this.barWidthSettingsFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
merge(this.barWidthSettingsFormGroup.get('strategy').valueChanges,
this.barWidthSettingsFormGroup.get('groupWidth.relative').valueChanges,
this.barWidthSettingsFormGroup.get('barWidth.relative').valueChanges)
.subscribe(() => {
this.updateValidators();
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(_fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.barWidthSettingsFormGroup.disable({emitEvent: false});
} else {
this.barWidthSettingsFormGroup.enable({emitEvent: false});
this.updateValidators();
}
}
writeValue(value: TimeSeriesChartNoAggregationBarWidthSettings): void {
this.modelValue = value;
this.barWidthSettingsFormGroup.patchValue(
value, {emitEvent: false}
);
this.updateValidators();
}
private updateValidators() {
const strategy: TimeSeriesChartNoAggregationBarWidthStrategy =
this.barWidthSettingsFormGroup.get('strategy').value;
const groupWidthRelative: boolean = this.barWidthSettingsFormGroup.get('groupWidth.relative').value;
const barWidthRelative: boolean = this.barWidthSettingsFormGroup.get('barWidth.relative').value;
if (strategy === TimeSeriesChartNoAggregationBarWidthStrategy.group) {
this.barWidthSettingsFormGroup.get('groupWidth').enable({emitEvent: false});
this.barWidthSettingsFormGroup.get('barWidth').disable({emitEvent: false});
if (groupWidthRelative) {
this.barWidthSettingsFormGroup.get('groupWidth').get('relativeWidth').enable({emitEvent: false});
this.barWidthSettingsFormGroup.get('groupWidth').get('absoluteWidth').disable({emitEvent: false});
} else {
this.barWidthSettingsFormGroup.get('groupWidth').get('relativeWidth').disable({emitEvent: false});
this.barWidthSettingsFormGroup.get('groupWidth').get('absoluteWidth').enable({emitEvent: false});
}
} else if (strategy === TimeSeriesChartNoAggregationBarWidthStrategy.separate) {
this.barWidthSettingsFormGroup.get('groupWidth').disable({emitEvent: false});
this.barWidthSettingsFormGroup.get('barWidth').enable({emitEvent: false});
if (barWidthRelative) {
this.barWidthSettingsFormGroup.get('barWidth').get('relativeWidth').enable({emitEvent: false});
this.barWidthSettingsFormGroup.get('barWidth').get('absoluteWidth').disable({emitEvent: false});
} else {
this.barWidthSettingsFormGroup.get('barWidth').get('relativeWidth').disable({emitEvent: false});
this.barWidthSettingsFormGroup.get('barWidth').get('absoluteWidth').enable({emitEvent: false});
}
}
}
private updateModel() {
this.modelValue = this.barWidthSettingsFormGroup.getRawValue();
this.propagateChange(this.modelValue);
}
}

5
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/widget-settings-common.module.ts

@ -106,6 +106,9 @@ import { EntityAliasInputComponent } from '@home/components/widget/lib/settings/
import {
TimeSeriesChartThresholdSettingsPanelComponent
} from '@home/components/widget/lib/settings/common/chart/time-series-chart-threshold-settings-panel.component';
import {
TimeSeriesNoAggregationBarWidthSettingsComponent
} from '@home/components/widget/lib/settings/common/chart/time-series-no-aggregation-bar-width-settings.component';
@NgModule({
declarations: [
@ -146,6 +149,7 @@ import {
TimeSeriesChartThresholdsPanelComponent,
TimeSeriesChartThresholdRowComponent,
TimeSeriesChartThresholdSettingsPanelComponent,
TimeSeriesNoAggregationBarWidthSettingsComponent,
DataKeyInputComponent,
EntityAliasInputComponent
],
@ -192,6 +196,7 @@ import {
TimeSeriesChartThresholdsPanelComponent,
TimeSeriesChartThresholdRowComponent,
TimeSeriesChartThresholdSettingsPanelComponent,
TimeSeriesNoAggregationBarWidthSettingsComponent,
DataKeyInputComponent,
EntityAliasInputComponent
],

16
ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.html

@ -120,7 +120,21 @@
}}
</mat-error>
</mat-form-field>
<div formGroupName="additionalConfig">
<div formGroupName="additionalConfig" class="tb-form-panel no-padding no-border">
<div class="tb-form-row space-between" formGroupName="icon">
<mat-slide-toggle formControlName="enabled" class="mat-slide">
{{ 'icon.icon' | translate }}
</mat-slide-toggle>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-material-icon-select asBoxInput
[color]="templateConfigurationForm.get('MOBILE_APP.additionalConfig.icon.color').value"
formControlName="icon">
</tb-material-icon-select>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
</div>
<tb-notification-action-button-configuration
formControlName="onClick"
hideButtonText

33
ui-ngx/src/app/modules/home/pages/notification/template/configuration/notification-template-configuration.component.ts

@ -34,7 +34,7 @@ import {
} from '@shared/models/notification.models';
import { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { isDefinedAndNotNull } from '@core/utils';
import { deepClone, isDefinedAndNotNull } from '@core/utils';
import { coerceBoolean } from '@shared/decorators/coercion';
import { TranslateService } from '@ngx-translate/core';
@ -112,8 +112,16 @@ export class NotificationTemplateConfigurationComponent implements OnDestroy, Co
this.destroy$.complete();
}
writeValue(value: any) {
this.templateConfigurationForm.patchValue(value, {emitEvent: false});
writeValue(value: Partial<DeliveryMethodsTemplates>) {
const settings = deepClone(value);
if (isDefinedAndNotNull(settings)) {
for (const method of Object.values(NotificationDeliveryMethod)) {
if (isDefinedAndNotNull(settings[method]?.enabled)) {
delete settings[method].enabled;
}
}
}
this.templateConfigurationForm.patchValue(settings, {emitEvent: false});
}
registerOnChange(fn: any): void {
@ -160,6 +168,9 @@ export class NotificationTemplateConfigurationComponent implements OnDestroy, Co
case NotificationDeliveryMethod.WEB:
form.get('additionalConfig.icon.enabled').updateValueAndValidity({onlySelf: true});
break;
case NotificationDeliveryMethod.MOBILE_APP:
form.get('additionalConfig.icon.enabled').updateValueAndValidity({onlySelf: true});
break;
}
}
});
@ -225,9 +236,25 @@ export class NotificationTemplateConfigurationComponent implements OnDestroy, Co
subject: ['', [Validators.required, Validators.maxLength(50)]],
body: ['', [Validators.required, Validators.maxLength(150)]],
additionalConfig: this.fb.group({
icon: this.fb.group({
enabled: [false],
icon: [{value: 'notifications', disabled: true}, Validators.required],
color: [{value: '#757575', disabled: true}]
}),
onClick: [null]
})
});
deliveryMethodForm.get('additionalConfig.icon.enabled').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((value) => {
if (value) {
deliveryMethodForm.get('additionalConfig.icon.icon').enable({emitEvent: false});
deliveryMethodForm.get('additionalConfig.icon.color').enable({emitEvent: false});
} else {
deliveryMethodForm.get('additionalConfig.icon.icon').disable({emitEvent: false});
deliveryMethodForm.get('additionalConfig.icon.color').disable({emitEvent: false});
}
});
break;
case NotificationDeliveryMethod.MICROSOFT_TEAMS:
deliveryMethodForm = this.fb.group({

2
ui-ngx/src/app/shared/models/widget.models.ts

@ -555,6 +555,8 @@ export enum WidgetMobileActionType {
takeScreenshot = 'takeScreenshot'
}
export const widgetActionTypes = Object.keys(WidgetActionType) as WidgetActionType[];
export const widgetActionTypeTranslationMap = new Map<WidgetActionType, string>(
[
[ WidgetActionType.openDashboardState, 'widget-action.open-dashboard-state' ],

7
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -6632,8 +6632,10 @@
"no-aggregation-bar-width-strategy": "Bar width strategy for non-aggregated data",
"no-aggregation-bar-width-strategy-group": "Group",
"no-aggregation-bar-width-strategy-separate": "Separate",
"bar-group-interval-width": "Bar group interval width",
"separate-bar-width": "Separate bar width",
"bar-group-width": "Bar group width",
"bar-width": "Bar width",
"bar-width-relative": "Percentage of time window",
"bar-width-absolute": "Absolute (ms)",
"threshold": {
"thresholds": "Thresholds",
"source": "Source",
@ -6678,6 +6680,7 @@
"position-top": "Top",
"position-bottom": "Bottom",
"tick-labels": "Tick labels",
"ticks-formatter-function": "Ticks formatter function",
"show-ticks": "Show ticks",
"show-line": "Show line",
"show-split-lines": "Show split lines",

6
ui-ngx/src/form.scss

@ -29,9 +29,11 @@
@media #{$breakpoint} {
@include form-row-column;
.mat-mdc-form-field, tb-unit-input {
width: auto;
&.medium-width {
&:not(.fixed-width) {
width: auto;
&.medium-width {
width: auto;
}
}
}
}

Loading…
Cancel
Save