Skip to content

Commit

Permalink
[Maps] convert vector style component to typescript round 1 (#81961) (#…
Browse files Browse the repository at this point in the history
…82200)

* [Maps] convert vector style component to typescript round 1

* clean up

* review feedback

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
nreese and kibanamachine committed Oct 30, 2020
1 parent 8998e8f commit f8a0026
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,23 @@

import React from 'react';

import { StylePropEditor } from '../style_prop_editor';
import { i18n } from '@kbn/i18n';
import { Props, StylePropEditor } from '../style_prop_editor';
// @ts-expect-error
import { DynamicColorForm } from './dynamic_color_form';
// @ts-expect-error
import { StaticColorForm } from './static_color_form';
import { i18n } from '@kbn/i18n';
import { ColorDynamicOptions, ColorStaticOptions } from '../../../../../../common/descriptor_types';

export function VectorStyleColorEditor(props) {
export function VectorStyleColorEditor(props: Props<ColorStaticOptions, ColorDynamicOptions>) {
const colorForm = props.styleProperty.isDynamic() ? (
<DynamicColorForm {...props} />
) : (
<StaticColorForm {...props} />
);

return (
<StylePropEditor
<StylePropEditor<ColorStaticOptions, ColorDynamicOptions>
{...props}
customStaticOptionLabel={i18n.translate(
'xpack.maps.styles.color.staticDynamicSelect.staticLabel',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,50 @@
* you may not use this file except in compliance with the Elastic License.
*/

import PropTypes from 'prop-types';
import React from 'react';

import { EuiComboBox, EuiHighlight, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { FIELD_ORIGIN } from '../../../../../common/constants';
import {
EuiComboBox,
EuiComboBoxProps,
EuiComboBoxOptionOption,
EuiHighlight,
EuiFlexGroup,
EuiFlexItem,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FIELD_ORIGIN, VECTOR_STYLES } from '../../../../../common/constants';
import { FieldIcon } from '../../../../../../../../src/plugins/kibana_react/public';
import { StyleField } from '../style_fields_helper';

function renderOption(option, searchValue, contentClassName) {
function renderOption(
option: EuiComboBoxOptionOption<StyleField>,
searchValue: string,
contentClassName: string
) {
const fieldIcon = option.value ? <FieldIcon type={option.value.type} fill="none" /> : null;
return (
<EuiFlexGroup className={contentClassName} gutterSize="s" alignItems="center">
<EuiFlexItem grow={null}>
<FieldIcon type={option.value.type} fill="none" />
</EuiFlexItem>
<EuiFlexItem grow={null}>{fieldIcon}</EuiFlexItem>
<EuiFlexItem>
<EuiHighlight search={searchValue}>{option.label}</EuiHighlight>
</EuiFlexItem>
</EuiFlexGroup>
);
}

function groupFieldsByOrigin(fields) {
const fieldsByOriginMap = new Map();
function groupFieldsByOrigin(fields: StyleField[]) {
const fieldsByOriginMap = new Map<FIELD_ORIGIN, StyleField[]>();
fields.forEach((field) => {
if (fieldsByOriginMap.has(field.origin)) {
const fieldsList = fieldsByOriginMap.get(field.origin);
const fieldsList = fieldsByOriginMap.get(field.origin)!;
fieldsList.push(field);
fieldsByOriginMap.set(field.origin, fieldsList);
} else {
fieldsByOriginMap.set(field.origin, [field]);
}
});

function fieldsListToOptions(fieldsList) {
function fieldsListToOptions(fieldsList: StyleField[]) {
return fieldsList
.map((field) => {
return { value: field, label: field.label };
Expand All @@ -50,11 +60,14 @@ function groupFieldsByOrigin(fields) {
if (fieldsByOriginMap.size === 1) {
// do not show origin group if all fields are from same origin
const onlyOriginKey = fieldsByOriginMap.keys().next().value;
const fieldsList = fieldsByOriginMap.get(onlyOriginKey);
const fieldsList = fieldsByOriginMap.get(onlyOriginKey)!;
return fieldsListToOptions(fieldsList);
}

const optionGroups = [];
const optionGroups: Array<{
label: string;
options: Array<EuiComboBoxOptionOption<StyleField>>;
}> = [];
fieldsByOriginMap.forEach((fieldsList, fieldOrigin) => {
optionGroups.push({
label: i18n.translate('xpack.maps.style.fieldSelect.OriginLabel', {
Expand All @@ -65,29 +78,46 @@ function groupFieldsByOrigin(fields) {
});
});

optionGroups.sort((a, b) => {
return a.label.toLowerCase().localeCompare(b.label.toLowerCase());
});
optionGroups.sort(
(a: EuiComboBoxOptionOption<StyleField>, b: EuiComboBoxOptionOption<StyleField>) => {
return a.label.toLowerCase().localeCompare(b.label.toLowerCase());
}
);

return optionGroups;
}

export function FieldSelect({ fields, selectedFieldName, onChange, styleName, ...rest }) {
const onFieldChange = (selectedFields) => {
type Props = {
fields: StyleField[];
selectedFieldName: string;
onChange: ({ field }: { field: StyleField | null }) => void;
styleName: VECTOR_STYLES;
} & Omit<
EuiComboBoxProps<StyleField>,
| 'selectedOptions'
| 'options'
| 'onChange'
| 'singleSelection'
| 'isClearable'
| 'fullWidth'
| 'renderOption'
>;

export function FieldSelect({ fields, selectedFieldName, onChange, styleName, ...rest }: Props) {
const onFieldChange = (selectedFields: Array<EuiComboBoxOptionOption<StyleField>>) => {
onChange({
field: selectedFields.length > 0 ? selectedFields[0].value : null,
field: selectedFields.length > 0 && selectedFields[0].value ? selectedFields[0].value : null,
});
};

let selectedOption;
if (selectedFieldName) {
const field = fields.find((field) => {
return field.name === selectedFieldName;
const field = fields.find((f) => {
return f.name === selectedFieldName;
});
//Do not spread in all the other unused values (e.g. type, supportsAutoDomain etc...)
if (field) {
selectedOption = {
value: field.value,
value: field,
label: field.label,
};
}
Expand All @@ -110,15 +140,3 @@ export function FieldSelect({ fields, selectedFieldName, onChange, styleName, ..
/>
);
}

export const fieldShape = PropTypes.shape({
name: PropTypes.string.isRequired,
origin: PropTypes.oneOf(Object.values(FIELD_ORIGIN)).isRequired,
type: PropTypes.string.isRequired,
});

FieldSelect.propTypes = {
selectedFieldName: PropTypes.string,
fields: PropTypes.arrayOf(fieldShape).isRequired,
onChange: PropTypes.func.isRequired,
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import { i18n } from '@kbn/i18n';

import { VECTOR_STYLES } from '../../../../../common/constants';

export function getDisabledByMessage(styleName) {
export function getDisabledByMessage(styleName: VECTOR_STYLES) {
return i18n.translate('xpack.maps.styles.vector.disabledByMessage', {
defaultMessage: `Set '{styleLabel}' to enable`,
values: { styleLabel: getVectorStyleLabel(styleName) },
});
}

export function getVectorStyleLabel(styleName) {
export function getVectorStyleLabel(styleName: VECTOR_STYLES) {
switch (styleName) {
case VECTOR_STYLES.FILL_COLOR:
return i18n.translate('xpack.maps.styles.vector.fillColorLabel', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,37 @@
*/

import _ from 'lodash';
import React, { Component } from 'react';
import React, { ChangeEvent, Component } from 'react';
import { EuiComboBox, EuiComboBoxOptionOption, EuiFieldText } from '@elastic/eui';
import { IField } from '../../../fields/field';

interface Props {
dataTestSubj: string;
field: IField;
getValueSuggestions: (query: string) => Promise<string[]>;
onChange: (value: string) => void;
value: string;
}

interface State {
suggestions: string[];
isLoadingSuggestions: boolean;
hasPrevFocus: boolean;
fieldDataType: string | null;
localFieldTextValue: string;
searchValue?: string;
}

import { EuiComboBox, EuiFieldText } from '@elastic/eui';
export class StopInput extends Component<Props, State> {
private _isMounted: boolean = false;

export class StopInput extends Component {
constructor(props) {
constructor(props: Props) {
super(props);
this.state = {
suggestions: [],
isLoadingSuggestions: false,
hasPrevFocus: false,
fieldDataType: undefined,
fieldDataType: null,
localFieldTextValue: props.value,
};
}
Expand Down Expand Up @@ -45,15 +64,15 @@ export class StopInput extends Component {
}
};

_onChange = (selectedOptions) => {
_onChange = (selectedOptions: Array<EuiComboBoxOptionOption<string>>) => {
this.props.onChange(_.get(selectedOptions, '[0].label', ''));
};

_onCreateOption = (newValue) => {
_onCreateOption = (newValue: string) => {
this.props.onChange(newValue);
};

_onSearchChange = async (searchValue) => {
_onSearchChange = async (searchValue: string) => {
this.setState(
{
isLoadingSuggestions: true,
Expand All @@ -65,8 +84,8 @@ export class StopInput extends Component {
);
};

_loadSuggestions = _.debounce(async (searchValue) => {
let suggestions = [];
_loadSuggestions = _.debounce(async (searchValue: string) => {
let suggestions: string[] = [];
try {
suggestions = await this.props.getValueSuggestions(searchValue);
} catch (error) {
Expand All @@ -81,7 +100,7 @@ export class StopInput extends Component {
}
}, 300);

_onFieldTextChange = (event) => {
_onFieldTextChange = (event: ChangeEvent<HTMLInputElement>) => {
this.setState({ localFieldTextValue: event.target.value });
// onChange can cause UI lag, ensure smooth input typing by debouncing onChange
this._debouncedOnFieldTextChange();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React, { Component, Fragment } from 'react';
import { getVectorStyleLabel, getDisabledByMessage } from './get_vector_style_label';
import React, { Component, Fragment, ReactElement } from 'react';
import {
EuiFormRow,
EuiSelect,
Expand All @@ -14,12 +13,31 @@ import {
EuiFieldText,
EuiToolTip,
} from '@elastic/eui';
import { STYLE_TYPE } from '../../../../../common/constants';
import { i18n } from '@kbn/i18n';
import { getVectorStyleLabel, getDisabledByMessage } from './get_vector_style_label';
import { STYLE_TYPE, VECTOR_STYLES } from '../../../../../common/constants';
import { FieldMetaOptions } from '../../../../../common/descriptor_types';
import { IStyleProperty } from '../properties/style_property';
import { StyleField } from '../style_fields_helper';

export interface Props<StaticOptions, DynamicOptions> {
children: ReactElement<any>;
customStaticOptionLabel?: string;
defaultStaticStyleOptions: StaticOptions;
defaultDynamicStyleOptions: DynamicOptions;
disabled: boolean;
disabledBy?: VECTOR_STYLES;
fields: StyleField[];
onDynamicStyleChange: (propertyName: VECTOR_STYLES, options: DynamicOptions) => void;
onStaticStyleChange: (propertyName: VECTOR_STYLES, options: StaticOptions) => void;
styleProperty: IStyleProperty<any>;
}

export class StylePropEditor extends Component {
_prevStaticStyleOptions = this.props.defaultStaticStyleOptions;
_prevDynamicStyleOptions = this.props.defaultDynamicStyleOptions;
export class StylePropEditor<StaticOptions, DynamicOptions> extends Component<
Props<StaticOptions, DynamicOptions>
> {
private _prevStaticStyleOptions = this.props.defaultStaticStyleOptions;
private _prevDynamicStyleOptions = this.props.defaultDynamicStyleOptions;

_onTypeToggle = () => {
if (this.props.styleProperty.isDynamic()) {
Expand All @@ -41,7 +59,7 @@ export class StylePropEditor extends Component {
}
};

_onFieldMetaOptionsChange = (fieldMetaOptions) => {
_onFieldMetaOptionsChange = (fieldMetaOptions: FieldMetaOptions) => {
const options = {
...this.props.styleProperty.getOptions(),
fieldMetaOptions,
Expand Down Expand Up @@ -89,28 +107,29 @@ export class StylePropEditor extends Component {

const staticDynamicSelect = this.renderStaticDynamicSelect();

const stylePropertyForm = this.props.disabled ? (
<EuiToolTip
anchorClassName="mapStyleFormDisabledTooltip"
content={getDisabledByMessage(this.props.disabledBy)}
>
<EuiFlexGroup gutterSize="xs">
<EuiFlexItem grow={false} className="mapStyleSettings__fixedBox">
{staticDynamicSelect}
</EuiFlexItem>
<EuiFlexItem>
<EuiFieldText compressed disabled />
</EuiFlexItem>
</EuiFlexGroup>
</EuiToolTip>
) : (
<Fragment>
{React.cloneElement(this.props.children, {
staticDynamicSelect,
})}
{fieldMetaOptionsPopover}
</Fragment>
);
const stylePropertyForm =
this.props.disabled && this.props.disabledBy ? (
<EuiToolTip
anchorClassName="mapStyleFormDisabledTooltip"
content={getDisabledByMessage(this.props.disabledBy)}
>
<EuiFlexGroup gutterSize="xs">
<EuiFlexItem grow={false} className="mapStyleSettings__fixedBox">
{staticDynamicSelect}
</EuiFlexItem>
<EuiFlexItem>
<EuiFieldText compressed disabled />
</EuiFlexItem>
</EuiFlexGroup>
</EuiToolTip>
) : (
<Fragment>
{React.cloneElement(this.props.children, {
staticDynamicSelect,
})}
{fieldMetaOptionsPopover}
</Fragment>
);

return (
<EuiFormRow
Expand Down

0 comments on commit f8a0026

Please sign in to comment.