[Maps] clean up icon category UI (#61116)

* [Maps] clean up icon category UI

* fix jest tests

* add unit test for getFirstUnusedSymbol

* remove duplicate icon stop values

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Nathan Reese 2020-03-24 21:24:37 -06:00 committed by GitHub
parent aa73e2aee3
commit 29a3f55985
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 128 additions and 41 deletions

View file

@ -120,7 +120,7 @@ describe('migrateSymbolStyleDescriptor', () => {
},
icon: {
type: STYLE_TYPE.STATIC,
options: { value: 'airfield' },
options: { value: 'marker' },
},
},
},

View file

@ -4,15 +4,5 @@
& + & {
margin-top: $euiSizeS;
}
&:hover,
&:focus {
.mapColorStop__icons {
visibility: visible;
opacity: 1;
display: block;
animation: mapColorStopBecomeVisible $euiAnimSpeedFast $euiAnimSlightResistance;
}
}
}

View file

@ -12,7 +12,7 @@ import { EuiButtonIcon, EuiColorPicker, EuiFlexGroup, EuiFlexItem, EuiFormRow }
function getColorStopRow({ index, errors, stopInput, onColorChange, color, deleteButton, onAdd }) {
const colorPickerButtons = (
<div className="mapColorStop__icons">
<div>
{deleteButton}
<EuiButtonIcon
iconType="plusInCircle"

View file

@ -70,6 +70,7 @@ export class IconSelect extends Component {
fullWidth
compressed
onKeyDown={this._handleKeyboardActivity}
append={this.props.append}
>
<EuiFieldText
onClick={this._togglePopover}

View file

@ -11,6 +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';
function isDuplicateStop(targetStop, iconStops) {
const stops = iconStops.filter(({ stop }) => {
@ -19,9 +20,31 @@ function isDuplicateStop(targetStop, iconStops) {
return stops.length > 1;
}
export function getFirstUnusedSymbol(symbolOptions, iconStops) {
const firstUnusedPreferredIconId = PREFERRED_ICONS.find(iconId => {
const isSymbolBeingUsed = iconStops.some(({ icon }) => {
return icon === iconId;
});
return !isSymbolBeingUsed;
});
if (firstUnusedPreferredIconId) {
return firstUnusedPreferredIconId;
}
const firstUnusedSymbol = symbolOptions.find(({ value }) => {
const isSymbolBeingUsed = iconStops.some(({ icon }) => {
return icon === value;
});
return !isSymbolBeingUsed;
});
return firstUnusedSymbol ? firstUnusedSymbol.value : DEFAULT_ICON;
}
const DEFAULT_ICON_STOPS = [
{ stop: null, icon: DEFAULT_ICON }, //first stop is the "other" color
{ stop: '', icon: DEFAULT_ICON },
{ stop: null, icon: PREFERRED_ICONS[0] }, //first stop is the "other" color
{ stop: '', icon: PREFERRED_ICONS[1] },
];
export function IconStops({
@ -58,7 +81,7 @@ export function IconStops({
...iconStops.slice(0, index + 1),
{
stop: '',
icon: DEFAULT_ICON,
icon: getFirstUnusedSymbol(symbolOptions, iconStops),
},
...iconStops.slice(index + 1),
],
@ -66,12 +89,12 @@ export function IconStops({
};
const onRemove = () => {
onChange({
iconStops: [...iconStops.slice(0, index), ...iconStops.slice(index + 1)],
customMapStops: [...iconStops.slice(0, index), ...iconStops.slice(index + 1)],
});
};
let deleteButton;
if (index > 0) {
if (iconStops.length > 2 && index !== 0) {
deleteButton = (
<EuiButtonIcon
iconType="trash"
@ -87,6 +110,19 @@ export function IconStops({
);
}
const iconStopButtons = (
<div>
{deleteButton}
<EuiButtonIcon
iconType="plusInCircle"
color="primary"
aria-label="Add"
title="Add"
onClick={onAdd}
/>
</div>
);
const errors = [];
// TODO check for duplicate values and add error messages here
@ -116,29 +152,20 @@ export function IconStops({
error={errors}
display="rowCompressed"
>
<div>
<EuiFlexGroup responsive={false} alignItems="center" gutterSize="xs">
<EuiFlexItem>{stopInput}</EuiFlexItem>
<EuiFlexItem>
<IconSelect
isDarkMode={isDarkMode}
onChange={onIconSelect}
symbolOptions={symbolOptions}
value={icon}
/>
</EuiFlexItem>
</EuiFlexGroup>
<div className="mapColorStop__icons">
{deleteButton}
<EuiButtonIcon
iconType="plusInCircle"
color="primary"
aria-label="Add"
title="Add"
onClick={onAdd}
<EuiFlexGroup alignItems="center" gutterSize="xs">
<EuiFlexItem grow={false} className="mapStyleSettings__fixedBox">
{stopInput}
</EuiFlexItem>
<EuiFlexItem>
<IconSelect
isDarkMode={isDarkMode}
onChange={onIconSelect}
symbolOptions={symbolOptions}
value={icon}
append={iconStopButtons}
/>
</div>
</div>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFormRow>
);
});

View file

@ -0,0 +1,59 @@
/*
* 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 { getFirstUnusedSymbol } from './icon_stops';
describe('getFirstUnusedSymbol', () => {
const symbolOptions = [{ value: 'icon1' }, { value: 'icon2' }];
test('Should return first unused icon from PREFERRED_ICONS', () => {
const iconStops = [
{ stop: 'category1', icon: 'circle' },
{ stop: 'category2', icon: 'marker' },
];
const nextIcon = getFirstUnusedSymbol(symbolOptions, iconStops);
expect(nextIcon).toBe('square');
});
test('Should fallback to first unused general icons when all PREFERRED_ICONS are used', () => {
const iconStops = [
{ stop: 'category1', icon: 'circle' },
{ stop: 'category2', icon: 'marker' },
{ stop: 'category3', icon: 'square' },
{ stop: 'category4', icon: 'star' },
{ stop: 'category5', icon: 'triangle' },
{ stop: 'category6', icon: 'hospital' },
{ stop: 'category7', icon: 'circle-stroked' },
{ stop: 'category8', icon: 'marker-stroked' },
{ stop: 'category9', icon: 'square-stroked' },
{ stop: 'category10', icon: 'star-stroked' },
{ stop: 'category11', icon: 'triangle-stroked' },
{ stop: 'category12', icon: 'icon1' },
];
const nextIcon = getFirstUnusedSymbol(symbolOptions, iconStops);
expect(nextIcon).toBe('icon2');
});
test('Should fallback to default icon when all icons are used', () => {
const iconStops = [
{ stop: 'category1', icon: 'circle' },
{ stop: 'category2', icon: 'marker' },
{ stop: 'category3', icon: 'square' },
{ stop: 'category4', icon: 'star' },
{ stop: 'category5', icon: 'triangle' },
{ stop: 'category6', icon: 'hospital' },
{ stop: 'category7', icon: 'circle-stroked' },
{ stop: 'category8', icon: 'marker-stroked' },
{ stop: 'category9', icon: 'square-stroked' },
{ stop: 'category10', icon: 'star-stroked' },
{ stop: 'category11', icon: 'triangle-stroked' },
{ stop: 'category12', icon: 'icon1' },
{ stop: 'category13', icon: 'icon2' },
];
const nextIcon = getFirstUnusedSymbol(symbolOptions, iconStops);
expect(nextIcon).toBe('marker');
});
});

View file

@ -101,6 +101,16 @@ const ICON_PALETTES = [
},
];
// PREFERRED_ICONS is used to provide less random default icon values for forms that need default icon values
export const PREFERRED_ICONS = [];
ICON_PALETTES.forEach(iconPalette => {
iconPalette.icons.forEach(iconId => {
if (!PREFERRED_ICONS.includes(iconId)) {
PREFERRED_ICONS.push(iconId);
}
});
});
export function getIconPaletteOptions(isDarkMode) {
return ICON_PALETTES.map(({ id, icons }) => {
const iconsDisplay = icons.map(iconId => {

View file

@ -96,7 +96,7 @@ describe('getDescriptorWithMissingStylePropsRemoved', () => {
},
icon: {
options: {
value: 'airfield',
value: 'marker',
},
type: 'STATIC',
},

View file

@ -188,7 +188,7 @@ export enum LABEL_BORDER_SIZES {
LARGE = 'LARGE',
}
export const DEFAULT_ICON = 'airfield';
export const DEFAULT_ICON = 'marker';
export enum VECTOR_STYLES {
SYMBOLIZE_AS = 'symbolizeAs',