diff --git a/src/i18n-keysets/wizard/en.json b/src/i18n-keysets/wizard/en.json index b2232d2afd..1c66ac32c7 100644 --- a/src/i18n-keysets/wizard/en.json +++ b/src/i18n-keysets/wizard/en.json @@ -117,9 +117,12 @@ "label_default-period": "Default period", "label_delete-all-filters-action": "Delete all filters in section", "label_dialog-column-info-text": "The total width of the table always occupies 100% of the available space, regardless of the specified widths of the individual columns.", + "label_dialog-line-width": "Line width", "label_dialog-radius": "Point size", "label_dialog-radius-max": "Maximal point size", "label_dialog-radius-min": "Minimal point size", + "label_dialog-shapes-chart-settings-tab": "General settings", + "label_dialog-shapes-line-settings-tab": "Lines", "label_direction": "Direction", "label_disabled": "Disabled", "label_discrete": "Discrete", @@ -188,6 +191,7 @@ "label_limit": "Limit", "label_limit-input-error": "Enter a value from {{min}} to {{max}}", "label_limit-message": "Only first 1000 values shown", + "label_line-width-auto-value": "Automatically", "label_linear": "Linear", "label_link-failed": "This dimension is not part of dataset links", "label_links": "Links", diff --git a/src/i18n-keysets/wizard/ru.json b/src/i18n-keysets/wizard/ru.json index 19cadcc5c3..c4174d7613 100644 --- a/src/i18n-keysets/wizard/ru.json +++ b/src/i18n-keysets/wizard/ru.json @@ -117,9 +117,12 @@ "label_default-period": "Период по умолчанию", "label_delete-all-filters-action": "Удалить все фильтры в секции", "label_dialog-column-info-text": "Общая ширина таблицы всегда занимает 100% доступного пространства вне зависимости от указанной ширины отдельных столбцов.", + "label_dialog-line-width": "Толщина линии", "label_dialog-radius": "Размер точек", "label_dialog-radius-max": "Максимальный размер точек", "label_dialog-radius-min": "Минимальный размер точек", + "label_dialog-shapes-chart-settings-tab": "Общие настройки", + "label_dialog-shapes-line-settings-tab": "Линии", "label_direction": "Направление", "label_disabled": "Выключена", "label_discrete": "Дискретный", @@ -188,6 +191,7 @@ "label_limit": "Лимит", "label_limit-input-error": "Введите значение от {{min}} до {{max}}", "label_limit-message": "Выводится только первая тысяча значений", + "label_line-width-auto-value": "Автоматически", "label_linear": "Линейная", "label_link-failed": "Измерение не участвует в связи датасетов", "label_links": "Связи", diff --git a/src/server/modes/charts/plugins/datalens/preparers/line/gravity-charts.ts b/src/server/modes/charts/plugins/datalens/preparers/line/gravity-charts.ts index f40085b704..922a33039b 100644 --- a/src/server/modes/charts/plugins/datalens/preparers/line/gravity-charts.ts +++ b/src/server/modes/charts/plugins/datalens/preparers/line/gravity-charts.ts @@ -207,6 +207,7 @@ export function prepareGravityChartLine(args: PrepareFunctionArgs) { shapeValue: graph.shapeValue, }, rangeSlider, + lineWidth: graph.lineWidth, }; }); diff --git a/src/server/modes/charts/plugins/datalens/utils/shape-helpers.ts b/src/server/modes/charts/plugins/datalens/utils/shape-helpers.ts index 8dc32eb858..b384ecfb0c 100644 --- a/src/server/modes/charts/plugins/datalens/utils/shape-helpers.ts +++ b/src/server/modes/charts/plugins/datalens/utils/shape-helpers.ts @@ -47,5 +47,25 @@ export const mapAndShapeGraph = ({ graph.dashStyle = SHAPES_IN_ORDER[shapeIndex % SHAPES_IN_ORDER.length]; } + + // Determine line width: use individual line width if set, otherwise fall back to chart-level width + let lineWidth: number | undefined; + + if (title && shapesConfig?.mountedShapesLineWidths?.[title]) { + // Individual line has a specific width set + lineWidth = shapesConfig.mountedShapesLineWidths[title]; + } else if (shapesConfig?.lineWidth !== undefined) { + // Fall back to chart-level line width + lineWidth = shapesConfig.lineWidth; + } + + if (lineWidth !== undefined) { + graph.lineWidth = lineWidth; + graph.states = { + hover: { + lineWidth: lineWidth + 2, + }, + }; + } }); }; diff --git a/src/shared/constants/qa/wizard.ts b/src/shared/constants/qa/wizard.ts index 3e1ab0e78d..b0aea7c0b0 100644 --- a/src/shared/constants/qa/wizard.ts +++ b/src/shared/constants/qa/wizard.ts @@ -195,3 +195,12 @@ export const enum ChartSettingsDialogQA { IndicatorTitleMode = 'indicator-title-mode', PreserveWhiteSpace = 'preserve-white-space', } + +export const enum DialogShapeSettings { + LineSettingsGraphScopeTabPanel = 'line-settings-graph-scope-tab-panel', + LineSettingsChartScopeTabPanel = 'line-settings-chart-scope-tab-panel', + LineSettingsGraphScopeTab = 'line-settings-graph-scope-tab', + LineSettingsChartScopeTab = 'line-settings-chart-scope-tab', + LineWidthSelectControl = 'line-width-select-control', + LineWidthSelectOption = 'line-width-select-option', +} diff --git a/src/shared/types/config/wizard/v15.ts b/src/shared/types/config/wizard/v15.ts index babea5c56c..3ddb7bc999 100644 --- a/src/shared/types/config/wizard/v15.ts +++ b/src/shared/types/config/wizard/v15.ts @@ -311,6 +311,8 @@ export type V15ColorsConfig = { export type V15ShapesConfig = { mountedShapes?: Record; + mountedShapesLineWidths?: Record; + lineWidth?: number; fieldGuid?: string; }; diff --git a/src/shared/types/wizard/index.ts b/src/shared/types/wizard/index.ts index 11fdecd475..abb8339d25 100644 --- a/src/shared/types/wizard/index.ts +++ b/src/shared/types/wizard/index.ts @@ -510,6 +510,8 @@ export type Sort = Field & { export interface ShapesConfig { mountedShapes?: Record; + mountedShapesLineWidths?: Record; + lineWidth?: number; fieldGuid?: string; } diff --git a/src/ui/components/NumberFormatSettings/NumberFormatSettings.tsx b/src/ui/components/NumberFormatSettings/NumberFormatSettings.tsx index 211503188e..eb24317005 100644 --- a/src/ui/components/NumberFormatSettings/NumberFormatSettings.tsx +++ b/src/ui/components/NumberFormatSettings/NumberFormatSettings.tsx @@ -12,7 +12,7 @@ import { NumberFormatUnit, } from 'shared'; -import NumberInput from './NumberInput/NumberInput'; +import NumberInput from '../NumberInput/NumberInput'; type Props = { dataType: DATASET_FIELD_TYPES; diff --git a/src/ui/components/NumberFormatSettings/NumberInput/NumberInput.tsx b/src/ui/components/NumberFormatSettings/NumberInput/NumberInput.tsx deleted file mode 100644 index c077336b63..0000000000 --- a/src/ui/components/NumberFormatSettings/NumberInput/NumberInput.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react'; - -import {Button, TextInput} from '@gravity-ui/uikit'; -import block from 'bem-cn-lite'; - -import './NumberInput.scss'; - -interface NumberInputProps { - value: number; - max?: number; - min?: number; - onChange: (value: number) => void; - qa?: string; -} - -const b = block('wizard-number-input'); -const DEFAULT_VALUE = 0; - -const NumberInput: React.FC = ({ - value, - onChange, - max = Infinity, - min = -Infinity, - qa, -}) => { - const onBlur = React.useCallback(() => { - if (Number.isNaN(value)) { - onChange(DEFAULT_VALUE); - } - }, [onChange, value]); - - const onInput = React.useCallback((newValue) => onChange(parseInt(newValue, 10)), [onChange]); - - const onPlus = React.useCallback(() => { - const newValue = Number.isNaN(value) ? DEFAULT_VALUE : Math.min(value + 1, max); - onChange(newValue); - }, [max, onChange, value]); - - const onMinus = React.useCallback(() => { - const newValue = Number.isNaN(value) ? DEFAULT_VALUE : Math.max(value - 1, min); - onChange(newValue); - }, [min, onChange, value]); - - const memorizedInputAttrs = React.useMemo(() => ({min, max}), [min, max]); - - return ( -
- - - -
- ); -}; - -export default NumberInput; diff --git a/src/ui/components/NumberFormatSettings/NumberInput/NumberInput.scss b/src/ui/components/NumberInput/NumberInput.scss similarity index 68% rename from src/ui/components/NumberFormatSettings/NumberInput/NumberInput.scss rename to src/ui/components/NumberInput/NumberInput.scss index b7d95be36f..c300dbc43b 100644 --- a/src/ui/components/NumberFormatSettings/NumberInput/NumberInput.scss +++ b/src/ui/components/NumberInput/NumberInput.scss @@ -1,6 +1,11 @@ -.wizard-number-input { +.number-input { display: flex; - max-width: 120px; + max-width: 136px; + width: 136px; + + &__input-button { + width: 45px; + } // This beautiful crutch is already here // https://github.com/gravity-ui/chartkit/blob/master/src/components/Widget/Table/Paginator/Paginator.scss#L25 @@ -14,5 +19,12 @@ & input[type='number'] { -moz-appearance: textfield; } + &__text-input { + width: 46px; + } /* stylelint-enable */ + &__text-input > .g-text-input__content { + border-left: none; + border-right: none; + } } diff --git a/src/ui/components/NumberInput/NumberInput.tsx b/src/ui/components/NumberInput/NumberInput.tsx new file mode 100644 index 0000000000..b7dc93089a --- /dev/null +++ b/src/ui/components/NumberInput/NumberInput.tsx @@ -0,0 +1,121 @@ +import React from 'react'; + +import {TextInput} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; + +import {STEP_BUTTON_DIRECTION, StepButton} from './StepButton'; + +import './NumberInput.scss'; + +const b = block('number-input'); + +interface NumberInputProps { + value: number; + max?: number; + min?: number; + onChange: (value: number) => void; + qa?: string; +} + +const NumberInput: React.FC = ({ + value, + onChange, + max = Infinity, + min = -Infinity, + qa, +}) => { + const [internalValue, setInternalValue] = React.useState(String(value)); + + React.useEffect(() => { + setInternalValue(String(value)); + }, [value]); + + const commitValue = React.useCallback( + (newValue: number) => { + setInternalValue(String(newValue)); + onChange(newValue); + }, + [onChange], + ); + + const clampAndCommit = React.useCallback( + (delta = 0) => { + const parsed = parseInt(internalValue, 10); + const baseValue = Number.isNaN(parsed) ? value : parsed; + const newValue = baseValue + delta; + + commitValue(Math.max(min, Math.min(max, newValue))); + }, + [internalValue, min, max, commitValue, value], + ); + + const onBlur = React.useCallback( + (e: React.FocusEvent) => { + const relatedTarget = e.relatedTarget as HTMLElement | null; + if (relatedTarget?.closest(`.${b('input-button')}`)) { + return; + } + + clampAndCommit(); + }, + [clampAndCommit], + ); + + const onKeyDown = React.useCallback( + (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + clampAndCommit(); + } + }, + [clampAndCommit], + ); + + const onInput = React.useCallback((newValue: string) => { + setInternalValue(newValue); + }, []); + + const onPlus = React.useCallback(() => clampAndCommit(1), [clampAndCommit]); + + const onMinus = React.useCallback(() => clampAndCommit(-1), [clampAndCommit]); + + const memorizedInputAttrs: React.InputHTMLAttributes = React.useMemo( + () => ({ + min: Number.isFinite(min) ? min : undefined, + max: Number.isFinite(max) ? max : undefined, + style: {textAlign: 'center'}, + }), + [min, max], + ); + + const parsedInternal = parseInt(internalValue, 10); + const isMinusDisabled = !Number.isNaN(parsedInternal) && parsedInternal <= min; + const isPlusDisabled = !Number.isNaN(parsedInternal) && parsedInternal >= max; + + return ( +
+ + + +
+ ); +}; + +export default NumberInput; diff --git a/src/ui/components/NumberInput/StepButton.tsx b/src/ui/components/NumberInput/StepButton.tsx new file mode 100644 index 0000000000..0dc5c2d143 --- /dev/null +++ b/src/ui/components/NumberInput/StepButton.tsx @@ -0,0 +1,43 @@ +import React from 'react'; + +import {Minus, Plus} from '@gravity-ui/icons'; +import {Button, Icon} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +import type {ValueOf} from 'shared'; + +import './NumberInput.scss'; + +const b = block('number-input'); + +export const STEP_BUTTON_DIRECTION = { + Plus: '+', + Minus: '-', +} as const; + +export type StepButtonDirection = ValueOf; + +interface StepButtonProps { + direction: StepButtonDirection; + disabled: boolean; + onClick: () => void; +} + +const DIRECTION_CONFIG: Record< + StepButtonDirection, + {icon: typeof Plus | typeof Minus; pin: 'brick-round' | 'round-brick'} +> = { + [STEP_BUTTON_DIRECTION.Plus]: {icon: Plus, pin: 'brick-round'}, + [STEP_BUTTON_DIRECTION.Minus]: {icon: Minus, pin: 'round-brick'}, +}; + +export const StepButton: React.FC = ({direction, disabled, onClick}) => { + const {icon, pin} = DIRECTION_CONFIG[direction]; + + return ( +
+ +
+ ); +}; diff --git a/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogLineWidth/DialogLineWidth.tsx b/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogLineWidth/DialogLineWidth.tsx new file mode 100644 index 0000000000..395e3f0197 --- /dev/null +++ b/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogLineWidth/DialogLineWidth.tsx @@ -0,0 +1,71 @@ +import React from 'react'; + +import {Flex, Text} from '@gravity-ui/uikit'; +import {i18n} from 'i18n'; +import NumberInput from 'ui/components/NumberInput/NumberInput'; +import { + LINE_WIDTH_AUTO_VALUE, + LINE_WIDTH_MAX_VALUE, + LINE_WIDTH_MIN_VALUE, +} from 'ui/units/wizard/constants/shapes'; + +import {LineWidthSelect} from '../../../LineWidthSelect/LineWidthSelect'; + +interface DialogLineWidthProps { + value: string; + onChange: (lineWidth: string) => void; + style?: React.CSSProperties; + allowDefault?: boolean; +} + +export const DialogLineWidth = React.memo( + ({value, onChange, allowDefault, style}: DialogLineWidthProps) => { + const handleNumberInputChange = React.useCallback( + (nextValue: number) => { + return onChange(nextValue.toString()); + }, + [onChange], + ); + + const isAutoValue = value === LINE_WIDTH_AUTO_VALUE; + + const numberInput = React.useMemo(() => { + if (isAutoValue) { + return null; + } + + const valueNumber = Number.parseInt(value, 10); + + if (Number.isNaN(valueNumber)) { + return null; + } + + return ( + + + + ); + }, [handleNumberInputChange, value, isAutoValue]); + + return ( + + + {i18n('wizard', 'label_dialog-line-width')} + + + {numberInput} + + ); + }, +); + +DialogLineWidth.displayName = 'DialogLineWidth'; diff --git a/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogShapes.scss b/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogShapes.scss index 6db55a2115..4e0f21068f 100644 --- a/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogShapes.scss +++ b/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogShapes.scss @@ -15,7 +15,7 @@ justify-content: center; } - & .g-dialog-body { + &_other-shapes .g-dialog-body { display: flex; padding: 0; overflow-y: auto; @@ -23,4 +23,9 @@ border-top: 1px solid var(--g-color-line-generic); border-bottom: 1px solid var(--g-color-line-generic); } + + &_lines-shapes .g-dialog-body { + padding: 0; + border-bottom: 1px solid var(--g-color-line-generic); + } } diff --git a/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogShapes.tsx b/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogShapes.tsx index a33073e56a..a53ec358fa 100644 --- a/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogShapes.tsx +++ b/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogShapes.tsx @@ -1,15 +1,26 @@ import React from 'react'; import {Shapes4} from '@gravity-ui/icons'; -import {Button, Dialog, Icon} from '@gravity-ui/uikit'; +import {Button, Dialog, Flex, Icon, Tab, TabList, TabPanel, TabProvider} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; import DialogManager from 'components/DialogManager/DialogManager'; import {i18n} from 'i18n'; -import type {DatasetOptions, Field, FilterField, ShapesConfig, Update} from 'shared'; +import { + type DatasetOptions, + DialogShapeSettings, + type Field, + type FilterField, + type ShapesConfig, + type Update, + type ValueOf, +} from 'shared'; +import {getColorsConfigKey} from 'shared/modules/colors/common-helpers'; +import {LINE_WIDTH_AUTO_VALUE, LINE_WIDTH_DEFAULT_VALUE} from 'ui/units/wizard/constants/shapes'; -import type {PaletteTypes} from '../../../constants'; +import {PaletteTypes} from '../../../constants'; -import DialogShapesPalette from './DialogShapesPalette/DialogShapesPalette'; +import {DialogShapesChartSettingsTab} from './tabs/DialogShapesChartSettingsTab'; +import {DialogShapesGraphSettingsTab} from './tabs/DialogShapesGraphSettingsTab'; import './DialogShapes.scss'; @@ -40,9 +51,80 @@ export type OpenDialogShapesArgs = { export type ShapesState = { mountedShapes: Record; + mountedShapesLineWidths: Record; + lineWidth: string; selectedValue: string | null; }; +const SETTINGS_SCOPE = { + GRAPH: 'graph-shapes-settings', + CHART: 'chart-shapes-settings', +} as const; + +const SETTINGS_SCOPE_TABS = [ + { + title: i18n('wizard', 'label_dialog-shapes-line-settings-tab'), + value: SETTINGS_SCOPE.GRAPH, + qa: DialogShapeSettings.LineSettingsGraphScopeTab, + }, + { + title: i18n('wizard', 'label_dialog-shapes-chart-settings-tab'), + value: SETTINGS_SCOPE.CHART, + qa: DialogShapeSettings.LineSettingsChartScopeTab, + }, +]; + +type GetInitialShapesStateArgs = { + shapesConfig: ShapesConfig; + paletteType: PaletteTypes; + items?: Field[]; +}; + +function getInitialShapesState({ + shapesConfig, + paletteType, + items, +}: GetInitialShapesStateArgs): ShapesState { + const mountedShapes = shapesConfig.mountedShapes ?? {}; + + const allLineIds = + items?.map( + (lineItem) => getColorsConfigKey(lineItem, items, {isMeasureNames: true}) as string, + ) ?? []; + + const savedLineWidths = + paletteType === PaletteTypes.Lines && shapesConfig.mountedShapesLineWidths + ? Object.fromEntries( + Object.entries(shapesConfig.mountedShapesLineWidths).map(([key, value]) => [ + key, + String(value), + ]), + ) + : {}; + + const mountedShapesLineWidths = + paletteType === PaletteTypes.Lines + ? Object.fromEntries( + allLineIds.map((lineId) => [ + lineId, + savedLineWidths[lineId] ?? LINE_WIDTH_AUTO_VALUE, + ]), + ) + : {}; + + const lineWidth = + shapesConfig.lineWidth === undefined + ? LINE_WIDTH_DEFAULT_VALUE + : String(shapesConfig.lineWidth); + + return { + selectedValue: null, + mountedShapes, + mountedShapesLineWidths, + lineWidth, + }; +} + const DialogShapes: React.FC = ({ item, items, @@ -58,13 +140,12 @@ const DialogShapes: React.FC = ({ onCancel, paletteType, }: Props) => { - const [shapesState, setShapesState] = React.useState({ - selectedValue: null, - mountedShapes: - shapesConfig.mountedShapes && Object.keys(shapesConfig.mountedShapes) - ? shapesConfig.mountedShapes - : {}, - }); + const [activeTab, setActiveTab] = React.useState>( + SETTINGS_SCOPE.GRAPH, + ); + const [shapesState, setShapesState] = React.useState(() => + getInitialShapesState({shapesConfig, paletteType, items}), + ); const onPaletteItemClick = React.useCallback( (shape: string) => { @@ -75,22 +156,62 @@ const DialogShapes: React.FC = ({ } const mountedShapes = {...shapesState.mountedShapes}; + const mountedShapesLineWidths = {...shapesState.mountedShapesLineWidths}; const isDefaultValue = shape === 'auto'; if (isDefaultValue) { delete mountedShapes[selectedValue]; } else { mountedShapes[selectedValue] = shape; + mountedShapesLineWidths[selectedValue] = + mountedShapesLineWidths[selectedValue] || LINE_WIDTH_AUTO_VALUE; } - setShapesState((prevState) => ({...prevState, mountedShapes})); + setShapesState((prevState) => ({ + ...prevState, + mountedShapes, + ...(paletteType === PaletteTypes.Lines ? {mountedShapesLineWidths} : {}), + })); }, - [shapesState], + [paletteType, shapesState], ); + const onLineWidthChange = React.useCallback((nextLineWidth: string) => { + setShapesState((prevState) => ({ + ...prevState, + mountedShapesLineWidths: { + ...prevState.mountedShapesLineWidths, + ...(prevState.selectedValue ? {[prevState.selectedValue]: nextLineWidth} : {}), + }, + })); + }, []); + + const onChartLineWidthChange = React.useCallback((nextLineWidth: string) => { + setShapesState((prevState) => ({ + ...prevState, + lineWidth: nextLineWidth, + })); + }, []); + const onReset = React.useCallback(() => { - setShapesState((prevState) => ({...prevState, mountedShapes: {}})); + setShapesState((prevState) => ({ + ...prevState, + mountedShapes: {}, + mountedShapesLineWidths: {}, + lineWidth: LINE_WIDTH_DEFAULT_VALUE, + })); }, []); + const isResetDisabled = React.useMemo(() => { + const hasNoMountedShapes = + shapesState.mountedShapes && !Object.keys(shapesState.mountedShapes).length; + const hasNoMountedShapesLineWidths = + shapesState.mountedShapesLineWidths && + !Object.keys(shapesState.mountedShapesLineWidths).length; + const isChartLineWidthDefault = shapesState.lineWidth === LINE_WIDTH_DEFAULT_VALUE; + + return hasNoMountedShapes && hasNoMountedShapesLineWidths && isChartLineWidthDefault; + }, [shapesState.mountedShapes, shapesState.mountedShapesLineWidths, shapesState.lineWidth]); + const onClose = React.useCallback(() => { onCancel(); }, [onCancel]); @@ -100,17 +221,160 @@ const DialogShapes: React.FC = ({ }, [onClose]); const onApplyButtonClick = React.useCallback(() => { - const shapesConfig: ShapesConfig = { + // Filter out lines with LINE_WIDTH_AUTO_VALUE - they will inherit from lineWidth on the server + const filteredMountedShapesLineWidths = Object.entries( + shapesState.mountedShapesLineWidths, + ).reduce>((acc, [key, value]) => { + if (value !== LINE_WIDTH_AUTO_VALUE) { + acc[key] = Number(value); + } + return acc; + }, {}); + + const nextShapesConfig: ShapesConfig = { mountedShapes: shapesState.mountedShapes, + lineWidth: Number(shapesState.lineWidth), + ...(paletteType === PaletteTypes.Lines + ? { + mountedShapesLineWidths: filteredMountedShapesLineWidths, + } + : {}), fieldGuid: item.guid, }; - onApply(shapesConfig); + onApply(nextShapesConfig); onClose(); - }, [item.guid, onApply, onClose, shapesState.mountedShapes]); + }, [ + shapesState.mountedShapes, + shapesState.mountedShapesLineWidths, + shapesState.lineWidth, + paletteType, + item.guid, + onApply, + onClose, + ]); + + const linesShapesDialogBody = React.useMemo(() => { + return ( + setActiveTab(value as ValueOf)} + > + + + {SETTINGS_SCOPE_TABS.map(({title, value, qa}) => ( + + {title} + + ))} + + + + + setShapesState((prevState) => ({ + ...prevState, + ...state, + })) + } + onPaletteItemClick={onPaletteItemClick} + onLineWidthChange={onLineWidthChange} + /> + + + + + + + + ); + }, [ + activeTab, + dashboardParameters, + datasetId, + distincts, + filters, + item, + items, + onChartLineWidthChange, + onLineWidthChange, + onPaletteItemClick, + options, + paletteType, + parameters, + shapesState, + updates, + ]); + + const otherShapesDialogBody = React.useMemo(() => { + return ( + + setShapesState((prevState) => ({ + ...prevState, + ...state, + })) + } + onPaletteItemClick={onPaletteItemClick} + onLineWidthChange={onLineWidthChange} + /> + ); + }, [ + dashboardParameters, + datasetId, + distincts, + filters, + item, + items, + onLineWidthChange, + onPaletteItemClick, + options, + paletteType, + parameters, + shapesState, + updates, + ]); return ( -
+
@@ -120,23 +384,9 @@ const DialogShapes: React.FC = ({ caption={i18n('wizard', 'label_shapes-settings')} /> - - setShapesState((prevState) => ({...prevState, ...state})) - } - onPaletteItemClick={onPaletteItemClick} - distincts={distincts} - items={items} - item={item} - datasetId={datasetId} - updates={updates} - filters={filters} - options={options} - paletteType={paletteType} - /> + {paletteType === PaletteTypes.Lines + ? linesShapesDialogBody + : otherShapesDialogBody} = ({ textButtonApply={i18n('wizard', 'button_apply')} textButtonCancel={i18n('wizard', 'button_cancel')} > - diff --git a/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogShapesPalette/DialogShapesPalette.scss b/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogShapesPalette/DialogShapesPalette.scss index 300c66b675..f3d0ac2801 100644 --- a/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogShapesPalette/DialogShapesPalette.scss +++ b/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogShapesPalette/DialogShapesPalette.scss @@ -7,11 +7,10 @@ &__palette { width: 340px; - padding: 25px; &_type_lines { - grid-template-columns: repeat(2, 118px); - grid-gap: 20px; + grid-template-columns: repeat(3, 105px); + grid-gap: 12px; } &_type_points { @@ -29,7 +28,7 @@ &_type_lines.palette-item { &:not(.palette-item_selected):hover::before { - @include palette-item-border(var(--g-color-line-generic), -4px, -4px, 124px, 42px); + @include palette-item-border(var(--g-color-line-generic), -4px, -4px, 111px, 42px); } &.palette-item_selected::before { @@ -37,7 +36,7 @@ var(--g-color-base-generic-medium), -4px, -4px, - 124px, + 111px, 42px ); } @@ -59,32 +58,4 @@ } } } - - &__value-shape { - margin-right: 12px; - border-radius: 2px; - display: flex; - - &_type_points { - width: 24px; - flex-shrink: 0; - justify-content: center; - - &#{$class}__value-shape_default { - width: 24px; - } - } - - &_default { - width: 40px; - background-color: var(--g-color-base-generic); - border: 1px solid var(--g-color-line-generic); - color: var(--g-color-text-hint); - font-size: 10px; - text-transform: uppercase; - display: flex; - justify-content: center; - align-items: center; - } - } } diff --git a/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogShapesPalette/DialogShapesPalette.tsx b/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogShapesPalette/DialogShapesPalette.tsx index 5d48bf19ec..927b38bab8 100644 --- a/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogShapesPalette/DialogShapesPalette.tsx +++ b/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogShapesPalette/DialogShapesPalette.tsx @@ -1,38 +1,17 @@ import React from 'react'; import block from 'bem-cn-lite'; -import type { - DatasetOptions, - Field, - FilterField, - LineShapeType, - PointsShapeType, - Update, -} from 'shared'; -import {POINT_SHAPES_IN_ORDER, SHAPES_PALETTE_ORDER} from 'shared'; -import {selectClientAvailableLineShapes} from 'ui'; -import IconRenderer from '../../../../../../libs/DatalensChartkit/ChartKit/components/IconRenderer/IconRenderer'; -import {PaletteTypes} from '../../../../constants'; +import type {PaletteTypes} from '../../../../constants'; import Palette from '../../../Palette/Palette'; -import ValuesList from '../../../ValuesList/ValuesList'; import type {ShapesState} from '../DialogShapes'; +import {useShapesPalette} from '../hooks/useShapesPalette'; import './DialogShapesPalette.scss'; type Props = { onPaletteItemClick: (shape: string) => void; shapesState: ShapesState; - parameters: Field[]; - dashboardParameters: Field[]; - setShapesState: (state: Partial) => void; - distincts?: Record; - items?: Field[]; - item: Field; - datasetId: string; - options: DatasetOptions; - updates: Update[]; - filters: FilterField[]; paletteType: PaletteTypes; }; @@ -42,31 +21,12 @@ const b = block('dialog-shapes-palette'); const DialogShapesPalette: React.FC = ({ shapesState, - setShapesState, - item, - items, - distincts, - datasetId, - options, - updates, - filters, onPaletteItemClick, - parameters, - dashboardParameters, paletteType, }: Props) => { const {mountedShapes, selectedValue} = shapesState; - const palette = React.useMemo(() => { - if (paletteType === PaletteTypes.Points) { - return [...POINT_SHAPES_IN_ORDER, 'auto']; - } - - const availableLineShapes = selectClientAvailableLineShapes(); - return availableLineShapes.sort( - (shape1, shape2) => SHAPES_PALETTE_ORDER[shape1] - SHAPES_PALETTE_ORDER[shape2], - ); - }, [paletteType]); + const palette = useShapesPalette(paletteType); const isDefaultItem = React.useCallback( (shape: string) => shape === DEFAULT_SHAPE || !shape, @@ -85,75 +45,16 @@ const DialogShapesPalette: React.FC = ({ }, [mountedShapes, selectedValue], ); - const renderValueIcon = React.useCallback( - (value) => { - const currentShape = mountedShapes[value]; - - const isDefault = !palette.includes(currentShape); - let content; - if (isDefault) { - content = 'A'; - } else { - switch (paletteType) { - case PaletteTypes.Lines: { - content = ( - - ); - break; - } - - case PaletteTypes.Points: { - content = ( - - ); - break; - } - } - } - - return ( -
- {content} -
- ); - }, - [mountedShapes, paletteType], - ); - return ( - - { - const state: Partial = {selectedValue: value}; - - if (shouldResetShapes) { - state.mountedShapes = {}; - } - - setShapesState(state); - }} - /> - - + ); }; diff --git a/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogValueList/DialogValueList.scss b/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogValueList/DialogValueList.scss new file mode 100644 index 0000000000..f864c74fab --- /dev/null +++ b/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogValueList/DialogValueList.scss @@ -0,0 +1,36 @@ +@import '../../../../styles/variables'; +@import '../../../../styles/mixins'; +@import '../../../../../../styles/mixins'; + +.dl-dialog-value-list { + $class: &; + + &__value-shape { + margin-right: 12px; + border-radius: 2px; + display: flex; + + &_type_points { + width: 24px; + flex-shrink: 0; + justify-content: center; + + &#{$class}__value-shape_default { + width: 24px; + } + } + + &_default { + width: 40px; + background-color: var(--g-color-base-generic); + border: 1px solid var(--g-color-line-generic); + color: var(--g-color-text-hint); + font-size: 10px; + text-transform: uppercase; + display: flex; + justify-content: center; + align-items: center; + flex-shrink: 0; + } + } +} diff --git a/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogValueList/DialogValueList.tsx b/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogValueList/DialogValueList.tsx new file mode 100644 index 0000000000..a82fd2fe8c --- /dev/null +++ b/src/ui/units/wizard/components/Dialogs/DialogShapes/DialogValueList/DialogValueList.tsx @@ -0,0 +1,115 @@ +import React from 'react'; + +import block from 'bem-cn-lite'; +import type { + DatasetOptions, + Field, + FilterField, + LineShapeType, + PointsShapeType, + Update, +} from 'shared'; +import IconRenderer from 'ui/libs/DatalensChartkit/ChartKit/components/IconRenderer/IconRenderer'; +import {PaletteTypes} from 'ui/units/wizard/constants'; + +import ValuesList from '../../../ValuesList/ValuesList'; +import type {ShapesState} from '../DialogShapes'; +import {useShapesPalette} from '../hooks/useShapesPalette'; + +import './DialogValueList.scss'; + +type Props = { + item: Field; + items?: Field[]; + distincts?: Record; + filters: FilterField[]; + parameters: Field[]; + dashboardParameters: Field[]; + updates: Update[]; + options: DatasetOptions; + datasetId: string; + shapesState: ShapesState; + paletteType: PaletteTypes; + setShapesState: (state: Partial) => void; +}; + +const b = block('dl-dialog-value-list'); + +export const DialogValueList: React.FC = ({ + item, + items, + distincts, + filters, + parameters, + dashboardParameters, + updates, + options, + datasetId, + shapesState, + paletteType, + setShapesState, +}: Props) => { + const palette = useShapesPalette(paletteType); + + const renderValueIcon = React.useCallback( + (value) => { + const {mountedShapes} = shapesState; + const currentShape = mountedShapes[value]; + + const isDefault = !palette.includes(currentShape); + let content; + if (isDefault) { + content = 'A'; + } else { + switch (paletteType) { + case PaletteTypes.Lines: { + content = ( + + ); + break; + } + + case PaletteTypes.Points: { + content = ( + + ); + break; + } + } + } + + return ( +
+ {content} +
+ ); + }, + [palette, paletteType, shapesState], + ); + + return ( + { + const state: Partial = {selectedValue: value}; + + if (shouldResetShapes) { + state.mountedShapes = {}; + state.mountedShapesLineWidths = {}; + } + + setShapesState(state); + }} + /> + ); +}; diff --git a/src/ui/units/wizard/components/Dialogs/DialogShapes/hooks/useShapesPalette.ts b/src/ui/units/wizard/components/Dialogs/DialogShapes/hooks/useShapesPalette.ts new file mode 100644 index 0000000000..fafeec68c8 --- /dev/null +++ b/src/ui/units/wizard/components/Dialogs/DialogShapes/hooks/useShapesPalette.ts @@ -0,0 +1,19 @@ +import React from 'react'; + +import {POINT_SHAPES_IN_ORDER, SHAPES_PALETTE_ORDER} from 'shared'; +import {selectClientAvailableLineShapes} from 'ui/constants'; + +import {PaletteTypes} from '../../../../constants'; + +export function useShapesPalette(paletteType: PaletteTypes): string[] { + return React.useMemo(() => { + if (paletteType === PaletteTypes.Points) { + return [...POINT_SHAPES_IN_ORDER, 'auto']; + } + + const availableLineShapes = selectClientAvailableLineShapes(); + return availableLineShapes.sort( + (shape1, shape2) => SHAPES_PALETTE_ORDER[shape1] - SHAPES_PALETTE_ORDER[shape2], + ); + }, [paletteType]); +} diff --git a/src/ui/units/wizard/components/Dialogs/DialogShapes/tabs/DialogShapesChartSettingsTab.tsx b/src/ui/units/wizard/components/Dialogs/DialogShapes/tabs/DialogShapesChartSettingsTab.tsx new file mode 100644 index 0000000000..331eb223bf --- /dev/null +++ b/src/ui/units/wizard/components/Dialogs/DialogShapes/tabs/DialogShapesChartSettingsTab.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +import {Flex} from '@gravity-ui/uikit'; + +import {DialogLineWidth} from '../DialogLineWidth/DialogLineWidth'; +import type {ShapesState} from '../DialogShapes'; + +type Props = { + shapesState: ShapesState; + onChartLineWidthChange: (nextLineWidth: string) => void; +}; + +export const DialogShapesChartSettingsTab = ({shapesState, onChartLineWidthChange}: Props) => { + return ( + + + + ); +}; diff --git a/src/ui/units/wizard/components/Dialogs/DialogShapes/tabs/DialogShapesGraphSettingsTab.tsx b/src/ui/units/wizard/components/Dialogs/DialogShapes/tabs/DialogShapesGraphSettingsTab.tsx new file mode 100644 index 0000000000..c0d6f1797a --- /dev/null +++ b/src/ui/units/wizard/components/Dialogs/DialogShapes/tabs/DialogShapesGraphSettingsTab.tsx @@ -0,0 +1,83 @@ +import React from 'react'; + +import {Flex} from '@gravity-ui/uikit'; +import type {DatasetOptions, Field, FilterField, Update} from 'shared'; +import {PaletteTypes} from 'ui/units/wizard/constants'; +import {LINE_WIDTH_AUTO_VALUE} from 'ui/units/wizard/constants/shapes'; + +import {DialogLineWidth} from '../DialogLineWidth/DialogLineWidth'; +import type {ShapesState} from '../DialogShapes'; +import DialogShapesPalette from '../DialogShapesPalette/DialogShapesPalette'; +import {DialogValueList} from '../DialogValueList/DialogValueList'; + +type Props = { + item: Field; + items?: Field[]; + distincts?: Record; + options: DatasetOptions; + parameters: Field[]; + dashboardParameters: Field[]; + datasetId: string; + updates: Update[]; + filters: FilterField[]; + shapesState: ShapesState; + paletteType: PaletteTypes; + setShapesState: (state: Partial) => void; + onPaletteItemClick: (shape: string) => void; + onLineWidthChange: (nextLineWidth: string) => void; +}; + +export const DialogShapesGraphSettingsTab: React.FC = ({ + item, + items, + distincts, + options, + parameters, + dashboardParameters, + datasetId, + updates, + filters, + shapesState, + paletteType, + setShapesState, + onPaletteItemClick, + onLineWidthChange, +}) => { + const selectedShapeLineWidth = + shapesState.selectedValue && shapesState.mountedShapesLineWidths[shapesState.selectedValue] + ? shapesState.mountedShapesLineWidths[shapesState.selectedValue] + : LINE_WIDTH_AUTO_VALUE; + + return ( + + + + {paletteType === PaletteTypes.Lines && ( + + )} + + + + ); +}; diff --git a/src/ui/units/wizard/components/LineWidthSelect/LineWidthSelect.scss b/src/ui/units/wizard/components/LineWidthSelect/LineWidthSelect.scss new file mode 100644 index 0000000000..d143a242ad --- /dev/null +++ b/src/ui/units/wizard/components/LineWidthSelect/LineWidthSelect.scss @@ -0,0 +1,25 @@ +.dl-line-width-select { + &__control { + height: 28px; + padding: 6px; + inset: 0; + border: 1px solid var(--g-color-line-generic); + border-radius: var(--g-border-radius-m); + transition: transform 0.1s ease-out; + } + + &__control:hover { + cursor: pointer; + background-color: var(--g-color-base-simple-hover); + border-color: var(--g-color-line-generic-hover); + } + + &__control:active { + transform: scale(0.96); + } + + &__option-line { + width: 100%; + background-color: var(--g-color-text-primary); + } +} diff --git a/src/ui/units/wizard/components/LineWidthSelect/LineWidthSelect.tsx b/src/ui/units/wizard/components/LineWidthSelect/LineWidthSelect.tsx new file mode 100644 index 0000000000..df8f44885c --- /dev/null +++ b/src/ui/units/wizard/components/LineWidthSelect/LineWidthSelect.tsx @@ -0,0 +1,140 @@ +import React from 'react'; + +import {ChevronDown} from '@gravity-ui/icons'; +import {Flex, Icon, Select, type SelectProps} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +import {i18n} from 'i18n'; +import {DialogShapeSettings} from 'shared'; + +import { + LINE_WIDTH_AUTO_VALUE, + LINE_WIDTH_MAX_VALUE, + LINE_WIDTH_MIN_VALUE, + LINE_WIDTH_VALUE_STEP, +} from '../../constants/shapes'; + +import './LineWidthSelect.scss'; + +const b = block('dl-line-width-select'); + +interface LineWidthSelectProps { + value: string; + onChange: (lineWidth: string) => void; + allowDefault?: boolean; +} + +const EMPTY_VALUE: string[] = []; +const SELECT_WIDTH = 136; + +export const LineWidthSelect = React.memo( + ({value, onChange, allowDefault}: LineWidthSelectProps) => { + const handleUpdate = React.useCallback( + (values: string[]) => { + const [nextLineWidth] = values; + + onChange(nextLineWidth); + }, + [onChange], + ); + + const options = React.useMemo(() => { + const result = []; + + if (allowDefault) { + result.push( + + + {i18n('wizard', 'label_line-width-auto-value')} + + , + ); + } + + for ( + let i = LINE_WIDTH_MIN_VALUE; + i <= LINE_WIDTH_MAX_VALUE; + i += LINE_WIDTH_VALUE_STEP + ) { + const optionValue = i.toString(); + + result.push( + + + + + , + ); + } + + return result; + }, [allowDefault]); + + const selectValue: string[] = React.useMemo(() => { + if (!value) { + return EMPTY_VALUE; + } + + return [value]; + }, [value]); + + const renderControl: SelectProps['renderControl'] = React.useCallback( + (props) => { + const isAutoValue = allowDefault && value === LINE_WIDTH_AUTO_VALUE; + + return ( + + {isAutoValue ? ( + {i18n('wizard', 'label_line-width-auto-value')} + ) : ( + + )} + + + ); + }, + [value, allowDefault], + ); + + const renderSelectedOption = React.useCallback((option) => { + return option.children; + }, []); + + return ( + + ); + }, +); + +LineWidthSelect.displayName = 'LineWidthSelect'; diff --git a/src/ui/units/wizard/constants/shapes.ts b/src/ui/units/wizard/constants/shapes.ts new file mode 100644 index 0000000000..afc6c87326 --- /dev/null +++ b/src/ui/units/wizard/constants/shapes.ts @@ -0,0 +1,5 @@ +export const LINE_WIDTH_MAX_VALUE = 12; +export const LINE_WIDTH_MIN_VALUE = 1; +export const LINE_WIDTH_VALUE_STEP = 1; +export const LINE_WIDTH_AUTO_VALUE = 'auto'; +export const LINE_WIDTH_DEFAULT_VALUE = '2'; diff --git a/tests/opensource-suites/wizard/visualizations/line/shapes.test.ts b/tests/opensource-suites/wizard/visualizations/line/shapes.test.ts index 314b7f5693..9fcc413cf6 100644 --- a/tests/opensource-suites/wizard/visualizations/line/shapes.test.ts +++ b/tests/opensource-suites/wizard/visualizations/line/shapes.test.ts @@ -6,6 +6,13 @@ import {WizardPageQa, WizardVisualizationId} from '../../../../../src/shared'; import WizardPage from '../../../../page-objects/wizard/WizardPage'; import {PlaceholderName} from '../../../../page-objects/wizard/SectionVisualization'; import {ColorValue} from '../../../../page-objects/wizard/ColorDialog'; +import {DOMNamedAttributes} from '../../../../page-objects/wizard/ChartKit'; +import { + LINE_WIDTH_AUTO_VALUE, + LINE_WIDTH_MAX_VALUE, + LINE_WIDTH_MIN_VALUE, + LINE_WIDTH_VALUE_STEP, +} from '../../../../../src/ui/units/wizard/constants/shapes'; datalensTest.describe('Wizard', () => { datalensTest.describe('Line chart', () => { @@ -70,5 +77,88 @@ datalensTest.describe('Wizard', () => { await expect(chartContainer).toHaveScreenshot(); }, ); + + datalensTest('User can change line width', async ({page}) => { + const wizardPage = new WizardPage({page}); + + await wizardPage.sectionVisualization.addFieldByClick(PlaceholderName.X, 'Category'); + await wizardPage.sectionVisualization.addFieldByClick(PlaceholderName.Y, 'Sales'); + + const newLineWidth = '5'; + const initialLineWidths = await wizardPage.chartkit.getAttributeFromLines( + DOMNamedAttributes.StrokeWidth, + ); + + expect(initialLineWidths.length).toEqual(1); + + await wizardPage.shapeDialog.open(); + + await wizardPage.shapeDialog.changeLineWidth(newLineWidth); + + await wizardPage.shapeDialog.apply(); + + await wizardPage.chartkit.waitUntilLoaderExists(); + + const updatedLineWidths = await wizardPage.chartkit.getAttributeFromLines( + DOMNamedAttributes.StrokeWidth, + ); + + expect(updatedLineWidths.length).toEqual(1); + expect(updatedLineWidths[0]).toEqual(newLineWidth); + }); + + datalensTest('User can only select line widths 1–12 and auto', async ({page}) => { + const wizardPage = new WizardPage({page}); + + await wizardPage.sectionVisualization.addFieldByClick(PlaceholderName.X, 'Category'); + await wizardPage.sectionVisualization.addFieldByClick(PlaceholderName.Y, 'Sales'); + + await wizardPage.shapeDialog.open(); + await wizardPage.shapeDialog.clickLineWidthSelectControl(); + + const optionElements = await wizardPage.shapeDialog.getLineWidthSelectOptions(); + + const expectedOptionsCount = LINE_WIDTH_MAX_VALUE + 1; + + expect(await optionElements.count()).toEqual(expectedOptionsCount); + + const autoOption = optionElements.nth(0); + await expect(autoOption.locator(`[data-qa="${LINE_WIDTH_AUTO_VALUE}"]`)).toBeVisible(); + + for ( + let i = LINE_WIDTH_MIN_VALUE; + i <= LINE_WIDTH_MAX_VALUE; + i += LINE_WIDTH_VALUE_STEP + ) { + const optionElement = optionElements.nth(i); + const optionValue = i.toString(); + + await expect(optionElement.locator(`[data-qa="${optionValue}"]`)).toBeVisible(); + } + }); + + datalensTest('User can set default line width', async ({page}) => { + const wizardPage = new WizardPage({page}); + + await wizardPage.sectionVisualization.addFieldByClick(PlaceholderName.X, 'Category'); + await wizardPage.sectionVisualization.addFieldByClick(PlaceholderName.Y, 'Sales'); + + const lineWidth = '7'; + + await wizardPage.shapeDialog.open(); + await wizardPage.shapeDialog.switchToChartSettingsTab(); + await wizardPage.shapeDialog.changeChartLineWidth(lineWidth); + await wizardPage.shapeDialog.switchToGraphSettingsTab(); + await wizardPage.shapeDialog.selectDefaultLineWidth(); + await wizardPage.shapeDialog.apply(); + await wizardPage.chartkit.waitUntilLoaderExists(); + + const updatedLineWidths = await wizardPage.chartkit.getAttributeFromLines( + DOMNamedAttributes.StrokeWidth, + ); + + expect(updatedLineWidths.length).toEqual(1); + expect(updatedLineWidths[0]).toEqual(lineWidth); + }); }); }); diff --git a/tests/page-objects/wizard/ChartKit.ts b/tests/page-objects/wizard/ChartKit.ts index 3958a4f2f2..50881ae092 100644 --- a/tests/page-objects/wizard/ChartKit.ts +++ b/tests/page-objects/wizard/ChartKit.ts @@ -7,6 +7,7 @@ import {COMMON_CHARTKIT_SELECTORS} from '../constants/chartkit'; export enum DOMNamedAttributes { StrokeDashArray = 'stroke-dasharray', + StrokeWidth = 'stroke-width', Stroke = 'stroke', } diff --git a/tests/page-objects/wizard/ShapeDialog.ts b/tests/page-objects/wizard/ShapeDialog.ts index d2950fad4a..2c3acf9dcb 100644 --- a/tests/page-objects/wizard/ShapeDialog.ts +++ b/tests/page-objects/wizard/ShapeDialog.ts @@ -1,5 +1,5 @@ import {Page} from '@playwright/test'; -import {LineShapeType} from '../../../src/shared/constants'; +import {DialogShapeSettings, LineShapeType} from '../../../src/shared/constants'; import {slct} from '../../utils'; @@ -41,6 +41,74 @@ export default class ShapeDialog { await this.page.click(slct(lineShape)); } + async switchToChartSettingsTab() { + await this.page.click(slct(DialogShapeSettings.LineSettingsChartScopeTab)); + } + + async switchToGraphSettingsTab() { + await this.page.click(slct(DialogShapeSettings.LineSettingsGraphScopeTab)); + } + + async clickLineWidthSelectControl() { + await this.page + .locator(slct(DialogShapeSettings.LineSettingsGraphScopeTabPanel)) + .locator(slct(DialogShapeSettings.LineWidthSelectControl)) + .click(); + } + + async clickChartLineWidthSelectControl() { + await this.page + .locator(slct(DialogShapeSettings.LineSettingsChartScopeTabPanel)) + .locator(slct(DialogShapeSettings.LineWidthSelectControl)) + .click(); + } + + async changeLineWidth(width: string) { + await this.clickLineWidthSelectControl(); + await this.page + .locator(slct(DialogShapeSettings.LineWidthSelectOption)) + .locator(`[data-qa="${width}"]`) + .click(); + } + + async changeChartLineWidth(width: string) { + await this.clickChartLineWidthSelectControl(); + await this.page + .locator(slct(DialogShapeSettings.LineWidthSelectOption)) + .locator(`[data-qa="${width}"]`) + .click(); + } + + async selectDefaultLineWidth() { + await this.clickLineWidthSelectControl(); + await this.page.locator(slct(DialogShapeSettings.LineWidthSelectOption)).first().click(); + } + + async getLineWidthSelectOptions() { + return this.page.locator(slct(DialogShapeSettings.LineWidthSelectOption)); + } + + async getLineWidthSelectControlText() { + const control = this.page + .locator(slct(DialogShapeSettings.LineSettingsGraphScopeTabPanel)) + .locator(slct(DialogShapeSettings.LineWidthSelectControl)); + return control.textContent(); + } + + getLineWidthSelectControlLine() { + return this.page + .locator(slct(DialogShapeSettings.LineSettingsGraphScopeTabPanel)) + .locator(slct(DialogShapeSettings.LineWidthSelectControl)) + .locator('.dl-line-width-select__option-line'); + } + + async getLineWidthSelectControlLineHeight() { + const line = this.getLineWidthSelectControlLine(); + const style = await line.getAttribute('style'); + const match = style?.match(/height:\s*(\d+)px/); + return match ? match[1] : null; + } + async selectValue(value: string) { await this.page.waitForSelector(this.valueLabelSelector);