mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Maps] fix custom icon palettes UI not being displayed (#71482)
* [Maps] fix custom icon palettes UI not being displayed * cleanup test * remove uneeded change to vector style defaults * fix jest tests * review feedback * fix jest tests
This commit is contained in:
parent
0c87aa506d
commit
34c54ed31b
23 changed files with 428 additions and 278 deletions
|
@ -95,7 +95,7 @@ export type ColorStylePropertyDescriptor =
|
|||
| ColorDynamicStylePropertyDescriptor;
|
||||
|
||||
export type IconDynamicOptions = {
|
||||
iconPaletteId?: string;
|
||||
iconPaletteId: string | null;
|
||||
customIconStops?: IconStop[];
|
||||
useCustomIconMap?: boolean;
|
||||
field?: StylePropertyField;
|
||||
|
|
|
@ -5,14 +5,9 @@
|
|||
*/
|
||||
|
||||
jest.mock('../../../../kibana_services', () => {
|
||||
const mockUiSettings = {
|
||||
get: () => {
|
||||
return undefined;
|
||||
},
|
||||
};
|
||||
return {
|
||||
getUiSettings: () => {
|
||||
return mockUiSettings;
|
||||
getIsDarkMode() {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -5,14 +5,9 @@
|
|||
*/
|
||||
|
||||
jest.mock('../../../../kibana_services', () => {
|
||||
const mockUiSettings = {
|
||||
get: () => {
|
||||
return undefined;
|
||||
},
|
||||
};
|
||||
return {
|
||||
getUiSettings: () => {
|
||||
return mockUiSettings;
|
||||
getIsDarkMode() {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -8,12 +8,8 @@ import sinon from 'sinon';
|
|||
|
||||
jest.mock('../../../kibana_services', () => {
|
||||
return {
|
||||
getUiSettings() {
|
||||
return {
|
||||
get() {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
getIsDarkMode() {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@ import { UpdateSourceEditor } from './update_source_editor';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { getDataSourceLabel } from '../../../../common/i18n_getters';
|
||||
import { SOURCE_TYPES } from '../../../../common/constants';
|
||||
import { getEmsTileLayerId, getUiSettings } from '../../../kibana_services';
|
||||
import { getEmsTileLayerId, getIsDarkMode } from '../../../kibana_services';
|
||||
import { registerSource } from '../source_registry';
|
||||
|
||||
export const sourceTitle = i18n.translate('xpack.maps.source.emsTileTitle', {
|
||||
|
@ -122,9 +122,8 @@ export class EMSTMSSource extends AbstractTMSSource {
|
|||
return this._descriptor.id;
|
||||
}
|
||||
|
||||
const isDarkMode = getUiSettings().get('theme:darkMode', false);
|
||||
const emsTileLayerId = getEmsTileLayerId();
|
||||
return isDarkMode ? emsTileLayerId.dark : emsTileLayerId.bright;
|
||||
return getIsDarkMode() ? emsTileLayerId.dark : emsTileLayerId.bright;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Component, Fragment } from 'react';
|
||||
|
||||
import { EuiSuperSelect, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
const CUSTOM_MAP = 'CUSTOM_MAP';
|
||||
|
||||
export class StyleMapSelect extends Component {
|
||||
state = {};
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
if (nextProps.customMapStops === prevState.prevPropsCustomMapStops) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
prevPropsCustomMapStops: nextProps.customMapStops, // reset tracker to latest value
|
||||
customMapStops: nextProps.customMapStops, // reset customMapStops to latest value
|
||||
};
|
||||
}
|
||||
|
||||
_onMapSelect = (selectedValue) => {
|
||||
const useCustomMap = selectedValue === CUSTOM_MAP;
|
||||
this.props.onChange({
|
||||
selectedMapId: useCustomMap ? null : selectedValue,
|
||||
useCustomMap,
|
||||
});
|
||||
};
|
||||
|
||||
_onCustomMapChange = ({ customMapStops, isInvalid }) => {
|
||||
// Manage invalid custom map in local state
|
||||
if (isInvalid) {
|
||||
this.setState({ customMapStops });
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.onChange({
|
||||
useCustomMap: true,
|
||||
customMapStops,
|
||||
});
|
||||
};
|
||||
|
||||
_renderCustomStopsInput() {
|
||||
return !this.props.isCustomOnly && !this.props.useCustomMap
|
||||
? null
|
||||
: this.props.renderCustomStopsInput(this._onCustomMapChange);
|
||||
}
|
||||
|
||||
_renderMapSelect() {
|
||||
if (this.props.isCustomOnly) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const mapOptionsWithCustom = [
|
||||
{
|
||||
value: CUSTOM_MAP,
|
||||
inputDisplay: this.props.customOptionLabel,
|
||||
},
|
||||
...this.props.options,
|
||||
];
|
||||
|
||||
let valueOfSelected;
|
||||
if (this.props.useCustomMap) {
|
||||
valueOfSelected = CUSTOM_MAP;
|
||||
} else {
|
||||
valueOfSelected = this.props.options.find(
|
||||
(option) => option.value === this.props.selectedMapId
|
||||
)
|
||||
? this.props.selectedMapId
|
||||
: '';
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiSuperSelect
|
||||
options={mapOptionsWithCustom}
|
||||
onChange={this._onMapSelect}
|
||||
valueOfSelected={valueOfSelected}
|
||||
hasDividers={true}
|
||||
compressed
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
{this._renderMapSelect()}
|
||||
{this._renderCustomStopsInput()}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Should not render icon map select when isCustomOnly 1`] = `
|
||||
<Fragment>
|
||||
<IconStops
|
||||
field={
|
||||
MockField {
|
||||
"_fieldName": "myField",
|
||||
"_origin": "source",
|
||||
}
|
||||
}
|
||||
getValueSuggestions={[Function]}
|
||||
iconStops={
|
||||
Array [
|
||||
Object {
|
||||
"icon": "circle",
|
||||
"stop": null,
|
||||
},
|
||||
Object {
|
||||
"icon": undefined,
|
||||
"stop": "",
|
||||
},
|
||||
]
|
||||
}
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`Should render custom stops input when useCustomIconMap 1`] = `
|
||||
<Fragment>
|
||||
<EuiSuperSelect
|
||||
compressed={true}
|
||||
fullWidth={false}
|
||||
hasDividers={true}
|
||||
isInvalid={false}
|
||||
isLoading={false}
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"inputDisplay": "Custom icon palette",
|
||||
"value": "CUSTOM_MAP_ID",
|
||||
},
|
||||
Object {
|
||||
"inputDisplay": <div>
|
||||
mock filledShapes option
|
||||
</div>,
|
||||
"value": "filledShapes",
|
||||
},
|
||||
Object {
|
||||
"inputDisplay": <div>
|
||||
mock hollowShapes option
|
||||
</div>,
|
||||
"value": "hollowShapes",
|
||||
},
|
||||
]
|
||||
}
|
||||
valueOfSelected="CUSTOM_MAP_ID"
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
<IconStops
|
||||
field={
|
||||
MockField {
|
||||
"_fieldName": "myField",
|
||||
"_origin": "source",
|
||||
}
|
||||
}
|
||||
getValueSuggestions={[Function]}
|
||||
iconStops={
|
||||
Array [
|
||||
Object {
|
||||
"icon": "circle",
|
||||
"stop": null,
|
||||
},
|
||||
Object {
|
||||
"icon": "marker",
|
||||
"stop": "value1",
|
||||
},
|
||||
]
|
||||
}
|
||||
onChange={[Function]}
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`Should render default props 1`] = `
|
||||
<Fragment>
|
||||
<EuiSuperSelect
|
||||
compressed={true}
|
||||
fullWidth={false}
|
||||
hasDividers={true}
|
||||
isInvalid={false}
|
||||
isLoading={false}
|
||||
onChange={[Function]}
|
||||
options={
|
||||
Array [
|
||||
Object {
|
||||
"inputDisplay": "Custom icon palette",
|
||||
"value": "CUSTOM_MAP_ID",
|
||||
},
|
||||
Object {
|
||||
"inputDisplay": <div>
|
||||
mock filledShapes option
|
||||
</div>,
|
||||
"value": "filledShapes",
|
||||
},
|
||||
Object {
|
||||
"inputDisplay": <div>
|
||||
mock hollowShapes option
|
||||
</div>,
|
||||
"value": "hollowShapes",
|
||||
},
|
||||
]
|
||||
}
|
||||
valueOfSelected="filledShapes"
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
|
@ -12,11 +12,9 @@ import { IconMapSelect } from './icon_map_select';
|
|||
|
||||
export function DynamicIconForm({
|
||||
fields,
|
||||
isDarkMode,
|
||||
onDynamicStyleChange,
|
||||
staticDynamicSelect,
|
||||
styleProperty,
|
||||
symbolOptions,
|
||||
}) {
|
||||
const styleOptions = styleProperty.getOptions();
|
||||
|
||||
|
@ -44,11 +42,8 @@ export function DynamicIconForm({
|
|||
return (
|
||||
<IconMapSelect
|
||||
{...styleOptions}
|
||||
useCustomIconMap={_.get(styleOptions, 'useCustomColorRamp', false)}
|
||||
styleProperty={styleProperty}
|
||||
onChange={onIconMapChange}
|
||||
isDarkMode={isDarkMode}
|
||||
symbolOptions={symbolOptions}
|
||||
isCustomOnly={!field.supportsAutoDomain()}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { StyleMapSelect } from '../style_map_select';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { IconStops } from './icon_stops';
|
||||
import { getIconPaletteOptions } from '../../symbol_utils';
|
||||
|
||||
export function IconMapSelect({
|
||||
customIconStops,
|
||||
iconPaletteId,
|
||||
isDarkMode,
|
||||
onChange,
|
||||
styleProperty,
|
||||
symbolOptions,
|
||||
useCustomIconMap,
|
||||
isCustomOnly,
|
||||
}) {
|
||||
function onMapSelectChange({ customMapStops, selectedMapId, useCustomMap }) {
|
||||
onChange({
|
||||
customIconStops: customMapStops,
|
||||
iconPaletteId: selectedMapId,
|
||||
useCustomIconMap: useCustomMap,
|
||||
});
|
||||
}
|
||||
|
||||
function renderCustomIconStopsInput(onCustomMapChange) {
|
||||
return (
|
||||
<IconStops
|
||||
field={styleProperty.getField()}
|
||||
getValueSuggestions={styleProperty.getValueSuggestions}
|
||||
iconStops={customIconStops}
|
||||
isDarkMode={isDarkMode}
|
||||
onChange={onCustomMapChange}
|
||||
symbolOptions={symbolOptions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyleMapSelect
|
||||
onChange={onMapSelectChange}
|
||||
customOptionLabel={i18n.translate('xpack.maps.styles.icon.customMapLabel', {
|
||||
defaultMessage: 'Custom icon palette',
|
||||
})}
|
||||
options={getIconPaletteOptions(isDarkMode)}
|
||||
customMapStops={customIconStops}
|
||||
useCustomMap={useCustomIconMap}
|
||||
selectedMapId={iconPaletteId}
|
||||
renderCustomStopsInput={renderCustomIconStopsInput}
|
||||
isCustomOnly={isCustomOnly}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
/* eslint-disable max-classes-per-file */
|
||||
|
||||
jest.mock('./icon_stops', () => ({
|
||||
IconStops: () => {
|
||||
return <div>mockIconStops</div>;
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../../symbol_utils', () => {
|
||||
return {
|
||||
getIconPaletteOptions: () => {
|
||||
return [
|
||||
{ value: 'filledShapes', inputDisplay: <div>mock filledShapes option</div> },
|
||||
{ value: 'hollowShapes', inputDisplay: <div>mock hollowShapes option</div> },
|
||||
];
|
||||
},
|
||||
PREFERRED_ICONS: ['circle'],
|
||||
};
|
||||
});
|
||||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { FIELD_ORIGIN } from '../../../../../../common/constants';
|
||||
import { AbstractField } from '../../../../fields/field';
|
||||
import { IDynamicStyleProperty } from '../../properties/dynamic_style_property';
|
||||
import { IconMapSelect } from './icon_map_select';
|
||||
|
||||
class MockField extends AbstractField {}
|
||||
|
||||
class MockDynamicStyleProperty {
|
||||
getField() {
|
||||
return new MockField({ fieldName: 'myField', origin: FIELD_ORIGIN.SOURCE });
|
||||
}
|
||||
|
||||
getValueSuggestions() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
iconPaletteId: 'filledShapes',
|
||||
onChange: () => {},
|
||||
styleProperty: (new MockDynamicStyleProperty() as unknown) as IDynamicStyleProperty,
|
||||
isCustomOnly: false,
|
||||
};
|
||||
|
||||
test('Should render default props', () => {
|
||||
const component = shallow(<IconMapSelect {...defaultProps} />);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('Should render custom stops input when useCustomIconMap', () => {
|
||||
const component = shallow(
|
||||
<IconMapSelect
|
||||
{...defaultProps}
|
||||
useCustomIconMap={true}
|
||||
customIconStops={[
|
||||
{ stop: null, icon: 'circle' },
|
||||
{ stop: 'value1', icon: 'marker' },
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('Should not render icon map select when isCustomOnly', () => {
|
||||
const component = shallow(<IconMapSelect {...defaultProps} isCustomOnly={true} />);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { EuiSuperSelect, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
// @ts-expect-error
|
||||
import { IconStops } from './icon_stops';
|
||||
// @ts-expect-error
|
||||
import { getIconPaletteOptions, PREFERRED_ICONS } from '../../symbol_utils';
|
||||
import { IconStop } from '../../../../../../common/descriptor_types';
|
||||
import { IDynamicStyleProperty } from '../../properties/dynamic_style_property';
|
||||
|
||||
const CUSTOM_MAP_ID = 'CUSTOM_MAP_ID';
|
||||
|
||||
const DEFAULT_ICON_STOPS = [
|
||||
{ stop: null, icon: PREFERRED_ICONS[0] }, // first stop is the "other" category
|
||||
{ stop: '', icon: PREFERRED_ICONS[1] },
|
||||
];
|
||||
|
||||
interface StyleOptionChanges {
|
||||
customIconStops?: IconStop[];
|
||||
iconPaletteId?: string | null;
|
||||
useCustomIconMap: boolean;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
customIconStops?: IconStop[];
|
||||
iconPaletteId: string | null;
|
||||
onChange: ({ customIconStops, iconPaletteId, useCustomIconMap }: StyleOptionChanges) => void;
|
||||
styleProperty: IDynamicStyleProperty;
|
||||
useCustomIconMap?: boolean;
|
||||
isCustomOnly: boolean;
|
||||
}
|
||||
|
||||
interface State {
|
||||
customIconStops: IconStop[];
|
||||
}
|
||||
|
||||
export class IconMapSelect extends Component<Props, State> {
|
||||
state = {
|
||||
customIconStops: this.props.customIconStops ? this.props.customIconStops : DEFAULT_ICON_STOPS,
|
||||
};
|
||||
|
||||
_onMapSelect = (selectedValue: string) => {
|
||||
const useCustomIconMap = selectedValue === CUSTOM_MAP_ID;
|
||||
const changes: StyleOptionChanges = {
|
||||
iconPaletteId: useCustomIconMap ? null : selectedValue,
|
||||
useCustomIconMap,
|
||||
};
|
||||
// edge case when custom palette is first enabled
|
||||
// customIconStops is undefined so need to update custom stops with default so icons are rendered.
|
||||
if (!this.props.customIconStops) {
|
||||
changes.customIconStops = DEFAULT_ICON_STOPS;
|
||||
}
|
||||
this.props.onChange(changes);
|
||||
};
|
||||
|
||||
_onCustomMapChange = ({
|
||||
customStops,
|
||||
isInvalid,
|
||||
}: {
|
||||
customStops: IconStop[];
|
||||
isInvalid: boolean;
|
||||
}) => {
|
||||
// Manage invalid custom map in local state
|
||||
this.setState({ customIconStops: customStops });
|
||||
|
||||
if (!isInvalid) {
|
||||
this.props.onChange({
|
||||
useCustomIconMap: true,
|
||||
customIconStops: customStops,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_renderCustomStopsInput() {
|
||||
return !this.props.isCustomOnly && !this.props.useCustomIconMap ? null : (
|
||||
<IconStops
|
||||
field={this.props.styleProperty.getField()}
|
||||
getValueSuggestions={this.props.styleProperty.getValueSuggestions}
|
||||
iconStops={this.state.customIconStops}
|
||||
onChange={this._onCustomMapChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
_renderMapSelect() {
|
||||
if (this.props.isCustomOnly) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const mapOptionsWithCustom = [
|
||||
{
|
||||
value: CUSTOM_MAP_ID,
|
||||
inputDisplay: i18n.translate('xpack.maps.styles.icon.customMapLabel', {
|
||||
defaultMessage: 'Custom icon palette',
|
||||
}),
|
||||
},
|
||||
...getIconPaletteOptions(),
|
||||
];
|
||||
|
||||
let valueOfSelected = '';
|
||||
if (this.props.useCustomIconMap) {
|
||||
valueOfSelected = CUSTOM_MAP_ID;
|
||||
} else if (this.props.iconPaletteId) {
|
||||
valueOfSelected = this.props.iconPaletteId;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiSuperSelect
|
||||
options={mapOptionsWithCustom}
|
||||
onChange={this._onMapSelect}
|
||||
valueOfSelected={valueOfSelected}
|
||||
hasDividers={true}
|
||||
compressed
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
{this._renderMapSelect()}
|
||||
{this._renderCustomStopsInput()}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -15,6 +15,8 @@ import {
|
|||
EuiSelectable,
|
||||
} from '@elastic/eui';
|
||||
import { SymbolIcon } from '../legend/symbol_icon';
|
||||
import { SYMBOL_OPTIONS } from '../../symbol_utils';
|
||||
import { getIsDarkMode } from '../../../../../kibana_services';
|
||||
|
||||
function isKeyboardEvent(event) {
|
||||
return typeof event === 'object' && 'keyCode' in event;
|
||||
|
@ -62,7 +64,6 @@ export class IconSelect extends Component {
|
|||
};
|
||||
|
||||
_renderPopoverButton() {
|
||||
const { isDarkMode, value } = this.props;
|
||||
return (
|
||||
<EuiFormControlLayout
|
||||
icon={{ type: 'arrowDown', side: 'right' }}
|
||||
|
@ -75,16 +76,16 @@ export class IconSelect extends Component {
|
|||
<EuiFieldText
|
||||
onClick={this._togglePopover}
|
||||
onKeyDown={this._handleKeyboardActivity}
|
||||
value={value}
|
||||
value={this.props.value}
|
||||
compressed
|
||||
readOnly
|
||||
fullWidth
|
||||
prepend={
|
||||
<SymbolIcon
|
||||
key={value}
|
||||
key={this.props.value}
|
||||
className="mapIconSelectSymbol__inputButton"
|
||||
symbolId={value}
|
||||
fill={isDarkMode ? 'rgb(223, 229, 239)' : 'rgb(52, 55, 65)'}
|
||||
symbolId={this.props.value}
|
||||
fill={getIsDarkMode() ? 'rgb(223, 229, 239)' : 'rgb(52, 55, 65)'}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
@ -93,8 +94,7 @@ export class IconSelect extends Component {
|
|||
}
|
||||
|
||||
_renderIconSelectable() {
|
||||
const { isDarkMode } = this.props;
|
||||
const options = this.props.symbolOptions.map(({ value, label }) => {
|
||||
const options = SYMBOL_OPTIONS.map(({ value, label }) => {
|
||||
return {
|
||||
value,
|
||||
label,
|
||||
|
@ -102,7 +102,7 @@ export class IconSelect extends Component {
|
|||
<SymbolIcon
|
||||
key={value}
|
||||
symbolId={value}
|
||||
fill={isDarkMode ? 'rgb(223, 229, 239)' : 'rgb(52, 55, 65)'}
|
||||
fill={getIsDarkMode() ? 'rgb(223, 229, 239)' : 'rgb(52, 55, 65)'}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
|
|
@ -4,25 +4,30 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
jest.mock('../../../../../kibana_services', () => {
|
||||
return {
|
||||
getIsDarkMode() {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../../symbol_utils', () => {
|
||||
return {
|
||||
SYMBOL_OPTIONS: [
|
||||
{ value: 'symbol1', label: 'symbol1' },
|
||||
{ value: 'symbol2', label: 'symbol2' },
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { IconSelect } from './icon_select';
|
||||
|
||||
const symbolOptions = [
|
||||
{ value: 'symbol1', label: 'symbol1' },
|
||||
{ value: 'symbol2', label: 'symbol2' },
|
||||
];
|
||||
|
||||
test('Should render icon select', () => {
|
||||
const component = shallow(
|
||||
<IconSelect
|
||||
value={symbolOptions[0].value}
|
||||
onChange={() => {}}
|
||||
symbolOptions={symbolOptions}
|
||||
isDarkMode={false}
|
||||
/>
|
||||
);
|
||||
const component = shallow(<IconSelect value={'symbol1'} onChange={() => {}} />);
|
||||
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
|
|
@ -11,7 +11,7 @@ import { getOtherCategoryLabel } from '../../style_util';
|
|||
import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiFieldText } from '@elastic/eui';
|
||||
import { IconSelect } from './icon_select';
|
||||
import { StopInput } from '../stop_input';
|
||||
import { PREFERRED_ICONS } from '../../symbol_utils';
|
||||
import { PREFERRED_ICONS, SYMBOL_OPTIONS } from '../../symbol_utils';
|
||||
|
||||
function isDuplicateStop(targetStop, iconStops) {
|
||||
const stops = iconStops.filter(({ stop }) => {
|
||||
|
@ -20,7 +20,7 @@ function isDuplicateStop(targetStop, iconStops) {
|
|||
return stops.length > 1;
|
||||
}
|
||||
|
||||
export function getFirstUnusedSymbol(symbolOptions, iconStops) {
|
||||
export function getFirstUnusedSymbol(iconStops) {
|
||||
const firstUnusedPreferredIconId = PREFERRED_ICONS.find((iconId) => {
|
||||
const isSymbolBeingUsed = iconStops.some(({ icon }) => {
|
||||
return icon === iconId;
|
||||
|
@ -32,7 +32,7 @@ export function getFirstUnusedSymbol(symbolOptions, iconStops) {
|
|||
return firstUnusedPreferredIconId;
|
||||
}
|
||||
|
||||
const firstUnusedSymbol = symbolOptions.find(({ value }) => {
|
||||
const firstUnusedSymbol = SYMBOL_OPTIONS.find(({ value }) => {
|
||||
const isSymbolBeingUsed = iconStops.some(({ icon }) => {
|
||||
return icon === value;
|
||||
});
|
||||
|
@ -42,19 +42,7 @@ export function getFirstUnusedSymbol(symbolOptions, iconStops) {
|
|||
return firstUnusedSymbol ? firstUnusedSymbol.value : DEFAULT_ICON;
|
||||
}
|
||||
|
||||
const DEFAULT_ICON_STOPS = [
|
||||
{ stop: null, icon: PREFERRED_ICONS[0] }, //first stop is the "other" color
|
||||
{ stop: '', icon: PREFERRED_ICONS[1] },
|
||||
];
|
||||
|
||||
export function IconStops({
|
||||
field,
|
||||
getValueSuggestions,
|
||||
iconStops = DEFAULT_ICON_STOPS,
|
||||
isDarkMode,
|
||||
onChange,
|
||||
symbolOptions,
|
||||
}) {
|
||||
export function IconStops({ field, getValueSuggestions, iconStops, onChange }) {
|
||||
return iconStops.map(({ stop, icon }, index) => {
|
||||
const onIconSelect = (selectedIconId) => {
|
||||
const newIconStops = [...iconStops];
|
||||
|
@ -62,7 +50,7 @@ export function IconStops({
|
|||
...iconStops[index],
|
||||
icon: selectedIconId,
|
||||
};
|
||||
onChange({ customMapStops: newIconStops });
|
||||
onChange({ customStops: newIconStops });
|
||||
};
|
||||
const onStopChange = (newStopValue) => {
|
||||
const newIconStops = [...iconStops];
|
||||
|
@ -71,17 +59,17 @@ export function IconStops({
|
|||
stop: newStopValue,
|
||||
};
|
||||
onChange({
|
||||
customMapStops: newIconStops,
|
||||
customStops: newIconStops,
|
||||
isInvalid: isDuplicateStop(newStopValue, iconStops),
|
||||
});
|
||||
};
|
||||
const onAdd = () => {
|
||||
onChange({
|
||||
customMapStops: [
|
||||
customStops: [
|
||||
...iconStops.slice(0, index + 1),
|
||||
{
|
||||
stop: '',
|
||||
icon: getFirstUnusedSymbol(symbolOptions, iconStops),
|
||||
icon: getFirstUnusedSymbol(iconStops),
|
||||
},
|
||||
...iconStops.slice(index + 1),
|
||||
],
|
||||
|
@ -89,7 +77,7 @@ export function IconStops({
|
|||
};
|
||||
const onRemove = () => {
|
||||
onChange({
|
||||
customMapStops: [...iconStops.slice(0, index), ...iconStops.slice(index + 1)],
|
||||
customStops: [...iconStops.slice(0, index), ...iconStops.slice(index + 1)],
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -157,13 +145,7 @@ export function IconStops({
|
|||
{stopInput}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<IconSelect
|
||||
isDarkMode={isDarkMode}
|
||||
onChange={onIconSelect}
|
||||
symbolOptions={symbolOptions}
|
||||
value={icon}
|
||||
append={iconStopButtons}
|
||||
/>
|
||||
<IconSelect onChange={onIconSelect} value={icon} append={iconStopButtons} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFormRow>
|
||||
|
|
|
@ -4,17 +4,41 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { getFirstUnusedSymbol } from './icon_stops';
|
||||
|
||||
describe('getFirstUnusedSymbol', () => {
|
||||
const symbolOptions = [{ value: 'icon1' }, { value: 'icon2' }];
|
||||
jest.mock('./icon_select', () => ({
|
||||
IconSelect: () => {
|
||||
return <div>mockIconSelect</div>;
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../../symbol_utils', () => {
|
||||
return {
|
||||
SYMBOL_OPTIONS: [{ value: 'icon1' }, { value: 'icon2' }],
|
||||
PREFERRED_ICONS: [
|
||||
'circle',
|
||||
'marker',
|
||||
'square',
|
||||
'star',
|
||||
'triangle',
|
||||
'hospital',
|
||||
'circle-stroked',
|
||||
'marker-stroked',
|
||||
'square-stroked',
|
||||
'star-stroked',
|
||||
'triangle-stroked',
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
describe('getFirstUnusedSymbol', () => {
|
||||
test('Should return first unused icon from PREFERRED_ICONS', () => {
|
||||
const iconStops = [
|
||||
{ stop: 'category1', icon: 'circle' },
|
||||
{ stop: 'category2', icon: 'marker' },
|
||||
];
|
||||
const nextIcon = getFirstUnusedSymbol(symbolOptions, iconStops);
|
||||
const nextIcon = getFirstUnusedSymbol(iconStops);
|
||||
expect(nextIcon).toBe('square');
|
||||
});
|
||||
|
||||
|
@ -33,7 +57,7 @@ describe('getFirstUnusedSymbol', () => {
|
|||
{ stop: 'category11', icon: 'triangle-stroked' },
|
||||
{ stop: 'category12', icon: 'icon1' },
|
||||
];
|
||||
const nextIcon = getFirstUnusedSymbol(symbolOptions, iconStops);
|
||||
const nextIcon = getFirstUnusedSymbol(iconStops);
|
||||
expect(nextIcon).toBe('icon2');
|
||||
});
|
||||
|
||||
|
@ -53,7 +77,7 @@ describe('getFirstUnusedSymbol', () => {
|
|||
{ stop: 'category12', icon: 'icon1' },
|
||||
{ stop: 'category13', icon: 'icon2' },
|
||||
];
|
||||
const nextIcon = getFirstUnusedSymbol(symbolOptions, iconStops);
|
||||
const nextIcon = getFirstUnusedSymbol(iconStops);
|
||||
expect(nextIcon).toBe('marker');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,13 +8,7 @@ import React from 'react';
|
|||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { IconSelect } from './icon_select';
|
||||
|
||||
export function StaticIconForm({
|
||||
isDarkMode,
|
||||
onStaticStyleChange,
|
||||
staticDynamicSelect,
|
||||
styleProperty,
|
||||
symbolOptions,
|
||||
}) {
|
||||
export function StaticIconForm({ onStaticStyleChange, staticDynamicSelect, styleProperty }) {
|
||||
const onChange = (selectedIconId) => {
|
||||
onStaticStyleChange(styleProperty.getStyleName(), { value: selectedIconId });
|
||||
};
|
||||
|
@ -25,12 +19,7 @@ export function StaticIconForm({
|
|||
{staticDynamicSelect}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<IconSelect
|
||||
isDarkMode={isDarkMode}
|
||||
onChange={onChange}
|
||||
symbolOptions={symbolOptions}
|
||||
value={styleProperty.getOptions().value}
|
||||
/>
|
||||
<IconSelect onChange={onChange} value={styleProperty.getOptions().value} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
|
|
@ -6,25 +6,15 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { getUiSettings } from '../../../../../kibana_services';
|
||||
import { StylePropEditor } from '../style_prop_editor';
|
||||
import { DynamicIconForm } from './dynamic_icon_form';
|
||||
import { StaticIconForm } from './static_icon_form';
|
||||
import { SYMBOL_OPTIONS } from '../../symbol_utils';
|
||||
|
||||
export function VectorStyleIconEditor(props) {
|
||||
const iconForm = props.styleProperty.isDynamic() ? (
|
||||
<DynamicIconForm
|
||||
{...props}
|
||||
isDarkMode={getUiSettings().get('theme:darkMode', false)}
|
||||
symbolOptions={SYMBOL_OPTIONS}
|
||||
/>
|
||||
<DynamicIconForm {...props} />
|
||||
) : (
|
||||
<StaticIconForm
|
||||
{...props}
|
||||
isDarkMode={getUiSettings().get('theme:darkMode', false)}
|
||||
symbolOptions={SYMBOL_OPTIONS}
|
||||
/>
|
||||
<StaticIconForm {...props} />
|
||||
);
|
||||
|
||||
return <StylePropEditor {...props}>{iconForm}</StylePropEditor>;
|
||||
|
|
|
@ -33,4 +33,5 @@ export interface IDynamicStyleProperty extends IStyleProperty {
|
|||
pluckCategoricalStyleMetaFromFeatures(features: unknown[]): CategoryFieldMeta;
|
||||
pluckOrdinalStyleMetaFromFieldMetaData(fieldMetaData: unknown): RangeFieldMeta;
|
||||
pluckCategoricalStyleMetaFromFieldMetaData(fieldMetaData: unknown): CategoryFieldMeta;
|
||||
getValueSuggestions(query: string): string[];
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import maki from '@elastic/maki';
|
|||
import xml2js from 'xml2js';
|
||||
import { parseXmlString } from '../../../../common/parse_xml_string';
|
||||
import { SymbolIcon } from './components/legend/symbol_icon';
|
||||
import { getIsDarkMode } from '../../../kibana_services';
|
||||
|
||||
export const LARGE_MAKI_ICON_SIZE = 15;
|
||||
const LARGE_MAKI_ICON_SIZE_AS_STRING = LARGE_MAKI_ICON_SIZE.toString();
|
||||
|
@ -111,7 +112,8 @@ ICON_PALETTES.forEach((iconPalette) => {
|
|||
});
|
||||
});
|
||||
|
||||
export function getIconPaletteOptions(isDarkMode) {
|
||||
export function getIconPaletteOptions() {
|
||||
const isDarkMode = getIsDarkMode();
|
||||
return ICON_PALETTES.map(({ id, icons }) => {
|
||||
const iconsDisplay = icons.map((iconId) => {
|
||||
const style = {
|
||||
|
|
|
@ -5,14 +5,9 @@
|
|||
*/
|
||||
|
||||
jest.mock('../../../kibana_services', () => {
|
||||
const mockUiSettings = {
|
||||
get: () => {
|
||||
return undefined;
|
||||
},
|
||||
};
|
||||
return {
|
||||
getUiSettings: () => {
|
||||
return mockUiSettings;
|
||||
getIsDarkMode() {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -18,8 +18,7 @@ import {
|
|||
CATEGORICAL_COLOR_PALETTES,
|
||||
} from '../color_palettes';
|
||||
import { VectorStylePropertiesDescriptor } from '../../../../common/descriptor_types';
|
||||
// @ts-ignore
|
||||
import { getUiSettings } from '../../../kibana_services';
|
||||
import { getIsDarkMode } from '../../../kibana_services';
|
||||
|
||||
export const MIN_SIZE = 1;
|
||||
export const MAX_SIZE = 64;
|
||||
|
@ -67,7 +66,7 @@ export function getDefaultStaticProperties(
|
|||
const nextFillColor = DEFAULT_FILL_COLORS[nextColorIndex];
|
||||
const nextLineColor = DEFAULT_LINE_COLORS[nextColorIndex];
|
||||
|
||||
const isDarkMode = getUiSettings().get('theme:darkMode', false);
|
||||
const isDarkMode = getIsDarkMode();
|
||||
|
||||
return {
|
||||
[VECTOR_STYLES.ICON]: {
|
||||
|
|
|
@ -24,6 +24,7 @@ export function getVisualizations(): any;
|
|||
export function getDocLinks(): any;
|
||||
export function getCoreChrome(): any;
|
||||
export function getUiSettings(): any;
|
||||
export function getIsDarkMode(): boolean;
|
||||
export function getCoreOverlays(): any;
|
||||
export function getData(): any;
|
||||
export function getUiActions(): any;
|
||||
|
|
|
@ -40,6 +40,9 @@ export const getFileUploadComponent = () => {
|
|||
let uiSettings;
|
||||
export const setUiSettings = (coreUiSettings) => (uiSettings = coreUiSettings);
|
||||
export const getUiSettings = () => uiSettings;
|
||||
export const getIsDarkMode = () => {
|
||||
return getUiSettings().get('theme:darkMode', false);
|
||||
};
|
||||
|
||||
let indexPatternSelectComponent;
|
||||
export const setIndexPatternSelect = (indexPatternSelect) =>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue