[Vis: Default editor] EUIficate ranges param editor (#38531) (#38798)

* EUIficate ranges control

* Remove unused translations

* Fix screenreader issue

* Add an initial case for safety

* Remove labels, add icon between ranges

* Add title for delete btn

# Conflicts:
#	x-pack/plugins/translations/translations/zh-CN.json
This commit is contained in:
Daniil Suleiman 2019-06-13 10:49:57 +03:00 committed by GitHub
parent 000da86fc6
commit 704d9b75cb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 172 additions and 86 deletions

View file

@ -21,7 +21,7 @@ import { BucketAggType } from './_bucket_agg_type';
import { createFilterRange } from './create_filter/range';
import { FieldFormat } from '../../../field_formats/field_format';
import { RangeKey } from './range_key';
import rangesTemplate from '../controls/ranges.html';
import { RangesParamEditor } from '../controls/ranges';
import { i18n } from '@kbn/i18n';
const keyCaches = new WeakMap();
@ -91,7 +91,7 @@ export const rangeBucketAgg = new BucketAggType({
{ from: 0, to: 1000 },
{ from: 1000, to: 2000 }
],
editor: rangesTemplate,
editorComponent: RangesParamEditor,
write: function (aggConfig, output) {
output.params.ranges = aggConfig.params.ranges;
output.params.keyed = true;

View file

@ -18,7 +18,7 @@
*/
import React from 'react';
import { EuiFieldText, EuiFlexItem } from '@elastic/eui';
import { EuiFieldText, EuiFlexItem, EuiIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import Ipv4Address from '../../../utils/ipv4_address';
import { InputList, InputListConfig, InputModel, InputObject, InputItem } from './input_list';
@ -95,6 +95,9 @@ function FromToList({ showValidation, onBlur, ...rest }: FromToListProps) {
onBlur={onBlur}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiIcon type="sortRight" color="subdued" />
</EuiFlexItem>
<EuiFlexItem>
<EuiFieldText
aria-label={i18n.translate('common.ui.aggTypes.ipRanges.ipRangeToAriaLabel', {

View file

@ -1,71 +0,0 @@
<table class="visEditorAgg__rangesTable form-group" ng-show="agg.params.ranges.length">
<tr>
<th scope="col">
<label
id="visEditorRangeFrom{{agg.id}}"
i18n-id="common.ui.aggTypes.ranges.fromColumnLabel"
i18n-default-message="From"
></label>
</th>
<th scope="col" colspan="2">
<label
id="visEditorRangeTo{{agg.id}}"
i18n-id="common.ui.aggTypes.ranges.toColumnLabel"
i18n-default-message="To"
></label>
</th>
</tr>
<tr
ng-repeat="range in agg.params.ranges track by $index">
<td>
<input
aria-labelledby="visEditorRangeFrom{{agg.id}}"
ng-model="range.from"
type="number"
class="form-control"
name="range.from"
step="any" />
</td>
<td>
<input
aria-labelledby="visEditorRangeTo{{agg.id}}"
ng-model="range.to"
type="number"
class="form-control"
name="range.to"
step="any" />
</td>
<td>
<button
type="button"
aria-label="{{ ::'common.ui.aggTypes.ranges.removeRangeButtonAriaLabel' | i18n: { defaultMessage: 'Remove this range' } }}"
ng-click="agg.params.ranges.splice($index, 1)"
class="kuiButton kuiButton--danger kuiButton--small">
<i class="fa fa-times"></i>
</button>
</td>
</tr>
</table>
<input ng-model="agg.params.ranges.length" name="rangeLength" required min="1" type="number" class="ng-hide" />
<div class="hintbox" ng-show="aggForm.rangeLength.$invalid">
<p>
<i class="fa fa-danger text-danger"></i>
<strong
i18n-id="common.ui.aggTypes.ranges.requiredRangeTitle"
i18n-default-message="Required:"
></strong>
<span
i18n-id="common.ui.aggTypes.ranges.requiredRangeDescription"
i18n-default-message="You must specify at least one range."
></span>
</p>
</div>
<button
ng-click="agg.params.ranges.push({})"
class="kuiButton kuiButton--primary kuiButton--fullWidth"
i18n-id="common.ui.aggTypes.ranges.addRangeButtonLabel"
i18n-default-message="Add Range"
></button>

View file

@ -0,0 +1,166 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you 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 React, { Fragment, useState, useEffect } from 'react';
import {
htmlIdGenerator,
EuiButtonIcon,
EuiFieldNumber,
EuiFlexItem,
EuiFlexGroup,
EuiIcon,
EuiSpacer,
EuiButtonEmpty,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { isEqual, omit } from 'lodash';
import { AggParamEditorProps } from 'ui/vis/editors/default';
const FROM_PLACEHOLDER = '\u2212\u221E';
const TO_PLACEHOLDER = '+\u221E';
const generateId = htmlIdGenerator();
const isEmpty = (value: any) => value === undefined || value === null;
interface RangeValues {
from?: number;
to?: number;
}
interface RangeValuesModel extends RangeValues {
id: string;
}
function RangesParamEditor({ agg, value = [], setValue }: AggParamEditorProps<RangeValues[]>) {
const [ranges, setRanges] = useState(() => value.map(range => ({ ...range, id: generateId() })));
// set up an initial range when there is no default range
useEffect(() => {
if (!value.length) {
onAddRange();
}
}, []);
useEffect(
() => {
// responsible for discarding changes
if (
value.length !== ranges.length ||
value.some((range, index) => !isEqual(range, omit(ranges[index], 'id')))
) {
setRanges(value.map(range => ({ ...range, id: generateId() })));
}
},
[value]
);
const updateRanges = (rangeValues: RangeValuesModel[]) => {
// do not set internal id parameter into saved object
setValue(rangeValues.map(range => omit(range, 'id')));
setRanges(rangeValues);
};
const onAddRange = () => updateRanges([...ranges, { id: generateId() }]);
const onRemoveRange = (id: string) => updateRanges(ranges.filter(range => range.id !== id));
const onChangeRange = (id: string, key: string, newValue: string) =>
updateRanges(
ranges.map(range =>
range.id === id
? {
...range,
[key]: newValue === '' ? undefined : parseFloat(newValue),
}
: range
)
);
return (
<>
{ranges.map(({ from, to, id }) => {
const deleteBtnTitle = i18n.translate(
'common.ui.aggTypes.ranges.removeRangeButtonAriaLabel',
{
defaultMessage: 'Remove the range of {from} to {to}',
values: {
from: isEmpty(from) ? FROM_PLACEHOLDER : from,
to: isEmpty(to) ? TO_PLACEHOLDER : to,
},
}
);
return (
<Fragment key={id}>
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem>
<EuiFieldNumber
aria-label={i18n.translate('common.ui.aggTypes.ranges.fromLabel', {
defaultMessage: 'From',
})}
value={isEmpty(from) ? '' : from}
placeholder={FROM_PLACEHOLDER}
onChange={ev => onChangeRange(id, 'from', ev.target.value)}
fullWidth={true}
compressed={true}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiIcon type="sortRight" color="subdued" />
</EuiFlexItem>
<EuiFlexItem>
<EuiFieldNumber
aria-label={i18n.translate('common.ui.aggTypes.ranges.toLabel', {
defaultMessage: 'To',
})}
value={isEmpty(to) ? '' : to}
placeholder={TO_PLACEHOLDER}
onChange={ev => onChangeRange(id, 'to', ev.target.value)}
fullWidth={true}
compressed={true}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonIcon
title={deleteBtnTitle}
aria-label={deleteBtnTitle}
disabled={value.length === 1}
color="danger"
iconType="trash"
onClick={() => onRemoveRange(id)}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="xs" />
</Fragment>
);
})}
<EuiSpacer size="s" />
<EuiFlexItem>
<EuiButtonEmpty iconType="plusInCircleFilled" onClick={onAddRange} size="xs">
<FormattedMessage
id="common.ui.aggTypes.ranges.addRangeButtonLabel"
defaultMessage="Add range"
/>
</EuiButtonEmpty>
</EuiFlexItem>
</>
);
}
export { RangesParamEditor };

View file

@ -229,12 +229,6 @@
"common.ui.aggTypes.paramTypes.field.requiredFieldParameterErrorMessage": "{fieldParameter} は必須パラメーターです",
"common.ui.aggTypes.placeMarkersOffGridLabel": "グリッド外にマーカーを配置 (ジオセントロイドを使用)",
"common.ui.aggTypes.precisionLabel": "精度",
"common.ui.aggTypes.ranges.addRangeButtonLabel": "範囲を追加",
"common.ui.aggTypes.ranges.fromColumnLabel": "From",
"common.ui.aggTypes.ranges.removeRangeButtonAriaLabel": "この範囲を削除",
"common.ui.aggTypes.ranges.requiredRangeDescription": "範囲を最低 1 つ指定する必要があります。",
"common.ui.aggTypes.ranges.requiredRangeTitle": "必須:",
"common.ui.aggTypes.ranges.toColumnLabel": "To",
"common.ui.aggTypes.showEmptyBucketsLabel": "空のバケットを表示",
"common.ui.aggTypes.showEmptyBucketsTooltip": "結果のあるバケットだけでなくすべてのバケットを表示します",
"common.ui.aggTypes.sizeLabel": "サイズ",

View file

@ -228,12 +228,6 @@
"common.ui.aggTypes.paramTypes.field.requiredFieldParameterErrorMessage": "{fieldParameter} 是必需字段",
"common.ui.aggTypes.placeMarkersOffGridLabel": "将标记置于网格外(使用 geocentroid",
"common.ui.aggTypes.precisionLabel": "精确度",
"common.ui.aggTypes.ranges.addRangeButtonLabel": "添加范围",
"common.ui.aggTypes.ranges.fromColumnLabel": "从",
"common.ui.aggTypes.ranges.removeRangeButtonAriaLabel": "移除此范围",
"common.ui.aggTypes.ranges.requiredRangeDescription": "必须指定至少一个范围。",
"common.ui.aggTypes.ranges.requiredRangeTitle": "必需:",
"common.ui.aggTypes.ranges.toColumnLabel": "到",
"common.ui.aggTypes.showEmptyBucketsLabel": "显示空存储桶",
"common.ui.aggTypes.showEmptyBucketsTooltip": "显示所有存储桶,不仅仅有结果的存储桶",
"common.ui.aggTypes.sizeLabel": "大小",