[Maps] Design updates for draw shape mode and timeslider (#103493)

* Timeslider icons and styles

* Exit feature editing. Animations for toolbar and timeslider

* Removing unnecessary line

* Adding padding to tooltip field popover

* Adding pulse animation on open timeslider

* Adding isDraggable to timeslider

* More timeslider styles

* Better positioning of the exit edit mode button

* Enable edit mode when new vector layer added

* Review feedback. Update action name. Remove unneeded component state

* Minor updates. One more action for cancel. Type updates. Snapshot update

* fixing tests and eslint error

* Added new exit mode design. Renamed animations

* Features instead of feature to be consistent with popover

* fix type error

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Aaron Caldwell <aaron.caldwell@elastic.co>
This commit is contained in:
Elizabet Oliveira 2021-06-29 17:41:15 +01:00 committed by GitHub
parent 1d99cae32b
commit 8c0407a9f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 175 additions and 122 deletions

View file

@ -0,0 +1,39 @@
@keyframes mapAnimationHeadShake {
0% {
transform: translateX(0);
}
6.5% {
transform: translateX(-6px) rotateY(-9deg);
}
18.5% {
transform: translateX(5px) rotateY(7deg);
}
31.5% {
transform: translateX(-3px) rotateY(-5deg);
}
43.5% {
transform: translateX(2px) rotateY(3deg);
}
50% {
transform: translateX(0);
}
}
@keyframes mapAnimationPulse {
from {
transform: scale3d(1, 1, 1);
}
50% {
transform: scale3d(1.005, 1.005, 1.005);
}
to {
transform: scale3d(1, 1, 1);
}
}

View file

@ -13,3 +13,4 @@
@import 'connected_components/index';
@import 'components/index';
@import 'classes/index';
@import 'animations';

View file

@ -256,9 +256,10 @@ export function setSelectedLayer(layerId: string | null) {
}
if (layerId) {
dispatch(trackCurrentLayerState(layerId));
}
if (getDrawMode(getState()) !== DRAW_MODE.NONE) {
dispatch(setDrawMode(DRAW_MODE.NONE));
// Reset draw mode only if setting a new selected layer
if (getDrawMode(getState()) !== DRAW_MODE.NONE) {
dispatch(setDrawMode(DRAW_MODE.NONE));
}
}
dispatch({
type: SET_SELECTED_LAYER,

View file

@ -27,6 +27,7 @@ import {
getSearchSessionMapBuffer,
getLayerById,
getEditState,
getSelectedLayerId,
} from '../selectors/map_selectors';
import {
CLEAR_GOTO,
@ -342,6 +343,19 @@ export function updateEditShape(shapeToDraw: DRAW_SHAPE | null) {
};
}
export function setEditLayerToSelectedLayer() {
return async (
dispatch: ThunkDispatch<MapStoreState, void, AnyAction>,
getState: () => MapStoreState
) => {
const layerId = getSelectedLayerId(getState());
if (!layerId) {
return;
}
dispatch(updateEditLayer(layerId));
};
}
export function updateEditLayer(layerId: string | null) {
return (dispatch: Dispatch) => {
if (layerId !== null) {

View file

@ -38,6 +38,7 @@ export type LayerWizard = {
prerequisiteSteps?: Array<{ id: string; label: string }>;
renderWizard(renderWizardArguments: RenderWizardArguments): ReactElement<any>;
title: string;
showFeatureEditTools?: boolean;
};
export type LayerWizardWithMeta = LayerWizard & {

View file

@ -45,6 +45,7 @@ export const newVectorLayerWizardConfig: LayerWizard = {
renderWizard: (renderWizardArguments: RenderWizardArguments) => {
return <NewVectorLayerEditor {...renderWizardArguments} />;
},
showFeatureEditTools: true,
title: i18n.translate('xpack.maps.newVectorLayerWizard.title', {
defaultMessage: 'Create new layer',
}),

View file

@ -54,7 +54,9 @@ exports[`Should remove selected fields from selectable 1`] = `
<EuiSpacer
size="xs"
/>
<EuiPopoverFooter>
<EuiPopoverFooter
paddingSize="s"
>
<EuiTextAlign
textAlign="right"
>
@ -143,7 +145,9 @@ exports[`Should render 1`] = `
<EuiSpacer
size="xs"
/>
<EuiPopoverFooter>
<EuiPopoverFooter
paddingSize="s"
>
<EuiTextAlign
textAlign="right"
>

View file

@ -164,14 +164,14 @@ export class AddTooltipFieldPopover extends Component<Props, State> {
>
{(list, search) => (
<div style={{ width: '300px' }}>
<EuiPopoverTitle>{search}</EuiPopoverTitle>
<EuiPopoverTitle paddingSize="s">{search}</EuiPopoverTitle>
{list}
</div>
)}
</EuiSelectable>
<EuiSpacer size="xs" />
<EuiPopoverFooter>
<EuiPopoverFooter paddingSize="s">
<EuiTextAlign textAlign="right">
<EuiButton
fill

View file

@ -14,12 +14,15 @@ import {
addPreviewLayers,
promotePreviewLayers,
removePreviewLayers,
setDrawMode,
setFirstPreviewLayerToSelectedLayer,
setEditLayerToSelectedLayer,
updateFlyout,
} from '../../actions';
import { MapStoreState } from '../../reducers/store';
import { LayerDescriptor } from '../../../common/descriptor_types';
import { hasPreviewLayers, isLoadingPreviewLayers } from '../../selectors/map_selectors';
import { DRAW_MODE } from '../../../common';
function mapStateToProps(state: MapStoreState) {
return {
@ -42,6 +45,10 @@ function mapDispatchToProps(dispatch: ThunkDispatch<MapStoreState, void, AnyActi
dispatch(updateFlyout(FLYOUT_STATE.NONE));
dispatch(removePreviewLayers());
},
enableEditMode: () => {
dispatch(setEditLayerToSelectedLayer());
dispatch(setDrawMode(DRAW_MODE.DRAW_SHAPES));
},
};
}

View file

@ -33,6 +33,7 @@ export interface Props {
hasPreviewLayers: boolean;
isLoadingPreviewLayers: boolean;
promotePreviewLayers: () => void;
enableEditMode: () => void;
}
interface State {
@ -91,6 +92,9 @@ export class AddLayerPanel extends Component<Props, State> {
if (this.state.layerSteps.length - 1 === this.state.currentStepIndex) {
// last step
this.props.promotePreviewLayers();
if (this.state.layerWizard?.showFeatureEditTools) {
this.props.enableEditMode();
}
} else {
this.setState((prevState) => {
const nextIndex = prevState.currentStepIndex + 1;

View file

@ -32,6 +32,23 @@
.mapTocEntry-isInEditingMode {
background-color: tintOrShade($euiColorPrimary, 90%, 70%) !important;
font-size: $euiFontSizeXS;
font-weight: $euiFontWeightMedium;
&__row {
margin-left: $euiSizeL;
display: inline-flex;
align-items: center;
> * {
margin-right: $euiSizeXS;
}
}
&__editFeatureText {
line-height: 1;
padding-right: $euiSizeXS;
}
}
.mapTocEntry-isDragging {

View file

@ -29,7 +29,10 @@ import {
hideTOCDetails,
showTOCDetails,
toggleLayerVisible,
setDrawMode,
updateDrawState,
} from '../../../../../actions';
import { DRAW_MODE } from '../../../../../../common';
function mapStateToProps(state: MapStoreState, ownProps: OwnProps): ReduxStateProps {
const flyoutDisplay = getFlyoutDisplay(state);
@ -63,6 +66,10 @@ function mapDispatchToProps(dispatch: ThunkDispatch<MapStoreState, void, AnyActi
toggleVisible: (layerId: string) => {
dispatch(toggleLayerVisible(layerId));
},
cancelEditing: () => {
dispatch(updateDrawState(null));
dispatch(setDrawMode(DRAW_MODE.NONE));
},
};
}

View file

@ -63,6 +63,7 @@ const defaultProps = {
hideTOCDetails: () => {},
showTOCDetails: () => {},
editModeActiveForLayer: false,
cancelEditing: () => {},
};
describe('TOCEntry', () => {

View file

@ -8,8 +8,8 @@
import React, { Component } from 'react';
import classNames from 'classnames';
import type { DraggableProvidedDragHandleProps } from 'react-beautiful-dnd';
import { EuiIcon, EuiButtonIcon, EuiConfirmModal } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiIcon, EuiButtonIcon, EuiConfirmModal, EuiButtonEmpty } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { TOCEntryActionsPopover } from './toc_entry_actions_popover';
import {
@ -40,6 +40,7 @@ export interface ReduxDispatchProps {
hideTOCDetails: (layerId: string) => void;
showTOCDetails: (layerId: string) => void;
toggleVisible: (layerId: string) => void;
cancelEditing: () => void;
}
export interface OwnProps {
@ -332,6 +333,24 @@ export class TOCEntry extends Component<Props, State> {
{this._renderDetailsToggle()}
{this._renderCancelModal()}
{this.props.editModeActiveForLayer && (
<div className="mapTocEntry-isInEditingMode__row">
<EuiIcon type="vector" size="s" />
<span className="mapTocEntry-isInEditingMode__editFeatureText">
<FormattedMessage
id="xpack.maps.layerControl.tocEntry.EditFeatures"
defaultMessage="Edit features"
/>
</span>
<EuiButtonEmpty size="xs" flush="both" onClick={this.props.cancelEditing}>
<FormattedMessage
id="xpack.maps.layerControl.tocEntry.exitEditModeAriaLabel"
defaultMessage="Exit"
/>
</EuiButtonEmpty>
</div>
)}
</div>
);
}

View file

@ -1,53 +1,17 @@
$timesliderWidth: 585px;
.mapTimeslider {
@include euiBottomShadowLarge;
position: fixed;
left: 50%;
bottom: $euiSize;
transform: translateX(-50%);
min-width: 530px;
// we could center with translateX but this would impact the animation
margin-left: -($timesliderWidth / 2);
width: $timesliderWidth;
z-index: 99999;
background: $euiColorEmptyShade;
padding: $euiSizeL $euiSizeXL;
padding: $euiSizeL $euiSize;
border-radius: $euiSize;
.euiRangeTick {
overflow-x: hidden;
text-overflow: ellipsis;
font-size: $euiFontSizeXS;
position: relative;
padding-top: $euiSize;
color: $euiColorDarkShade;
&::before {
@include size($euiSizeXS);
width: 3px;
height: $euiSizeS;
content: '';
background-color: $euiColorLightShade;
position: absolute;
top: $euiSizeXS;
left: calc(50% - #{($euiSizeXS/2)});
}
&--isCustom {
position: absolute;
transform: translateX(-50%);
}
&:enabled:hover,
&:focus,
&--selected {
color: $euiColorPrimary;
}
&--selected {
font-weight: $euiFontWeightMedium;
}
&:disabled {
cursor: not-allowed;
}
}
}
.mapTimeslider__row {
@ -60,6 +24,10 @@
> * {
align-items: center;
}
&:first-child {
padding: 0 $euiSize;
}
}
.mapTimeslider__close {
@ -77,6 +45,10 @@
.mapTimeslider__controls {
margin-left: $euiSizeS;
.euiButtonIcon + .euiButtonIcon {
margin-left: $euiSizeXS;
}
}
.mapTimeslider__innerPanel {
@ -87,3 +59,11 @@
display: inline-flex;
align-items: center;
}
.mapTimeslider__playButton {
border-radius: 50%;
}
.mapTimeslider--animation {
animation: mapAnimationPulse .6s ease-in-out;
}

View file

@ -166,7 +166,7 @@ class KeyedTimeslider extends Component<Props, State> {
render() {
return (
<div className="mapTimeslider">
<div className="mapTimeslider mapTimeslider--animation">
<div className="mapTimeslider__row">
<EuiButtonIcon
onClick={this.props.closeTimeslider}
@ -186,24 +186,18 @@ class KeyedTimeslider extends Component<Props, State> {
<div className="mapTimeslider__controls">
<EuiButtonIcon
onClick={this._onPrevious}
iconType="arrowLeft"
iconType="framePrevious"
color="text"
aria-label={i18n.translate('xpack.maps.timeslider.previousTimeWindowLabel', {
defaultMessage: 'Previous time window',
})}
/>
<EuiButtonIcon
onClick={this._onNext}
iconType="arrowRight"
color="text"
aria-label={i18n.translate('xpack.maps.timeslider.nextTimeWindowLabel', {
defaultMessage: 'Next time window',
})}
/>
<EuiButtonIcon
className="mapTimeslider__playButton"
onClick={this.state.isPaused ? this._onPlay : this._onPause}
iconType={this.state.isPaused ? 'play' : 'pause'}
color="text"
iconType={this.state.isPaused ? 'playFilled' : 'pause'}
size="s"
display="fill"
aria-label={
this.state.isPaused
? i18n.translate('xpack.maps.timeslider.playLabel', {
@ -214,6 +208,14 @@ class KeyedTimeslider extends Component<Props, State> {
})
}
/>
<EuiButtonIcon
onClick={this._onNext}
iconType="frameNext"
color="text"
aria-label={i18n.translate('xpack.maps.timeslider.nextTimeWindowLabel', {
defaultMessage: 'Next time window',
})}
/>
</div>
</div>
</div>
@ -228,6 +230,7 @@ class KeyedTimeslider extends Component<Props, State> {
max={this.state.max}
step={1}
ticks={this.state.ticks}
isDraggable
/>
</div>
</div>

View file

@ -52,6 +52,10 @@
}
}
.mapToolbarOverlay__buttonGroupAnimated {
animation: mapAnimationHeadShake 1.2s ease-in-out;
}
.mapToolbarOverlay__button__exit {
.euiButtonIcon {
color: $euiColorDangerText !important;

View file

@ -19,7 +19,6 @@ export interface ReduxStateProps {
export interface ReduxDispatchProps {
setDrawShape: (shapeToDraw: DRAW_SHAPE) => void;
cancelEditing: () => void;
}
export interface OwnProps {
@ -37,7 +36,10 @@ export function FeatureEditTools(props: Props) {
const deleteSelected = props.drawShape === DRAW_SHAPE.DELETE;
return (
<EuiPanel paddingSize="none" className="mapToolbarOverlay__buttonGroup">
<EuiPanel
paddingSize="none"
className="mapToolbarOverlay__buttonGroup mapToolbarOverlay__buttonGroupAnimated"
>
{props.pointsOnly ? null : (
<>
<EuiButtonIcon
@ -136,19 +138,6 @@ export function FeatureEditTools(props: Props) {
isSelected={deleteSelected}
display={deleteSelected ? 'fill' : 'empty'}
/>
<EuiButtonIcon
key="exit"
size="s"
onClick={props.cancelEditing}
iconType="exit"
aria-label={i18n.translate('xpack.maps.toolbarOverlay.featureDraw.cancelDraw', {
defaultMessage: 'Exit feature editing',
})}
title={i18n.translate('xpack.maps.toolbarOverlay.featureDraw.cancelDrawTitle', {
defaultMessage: 'Exit feature editing',
})}
/>
</EuiPanel>
);
}

View file

@ -14,9 +14,9 @@ import {
ReduxStateProps,
OwnProps,
} from './feature_edit_tools';
import { setDrawMode, updateEditShape } from '../../../../actions';
import { updateEditShape } from '../../../../actions';
import { MapStoreState } from '../../../../reducers/store';
import { DRAW_MODE, DRAW_SHAPE } from '../../../../../common';
import { DRAW_SHAPE } from '../../../../../common';
import { getEditState } from '../../../../selectors/map_selectors';
function mapStateToProps(state: MapStoreState): ReduxStateProps {
@ -33,9 +33,6 @@ function mapDispatchToProps(
setDrawShape: (shapeToDraw: DRAW_SHAPE) => {
dispatch(updateEditShape(shapeToDraw));
},
cancelEditing: () => {
dispatch(setDrawMode(DRAW_MODE.NONE));
},
};
}

View file

@ -1,36 +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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { FunctionComponent } from 'react';
interface Props {
title?: string;
titleId?: string;
}
export const ClockPlayIcon: FunctionComponent<Props> = ({ title, titleId, ...props }) => (
<svg
width={16}
height={16}
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
aria-labelledby={titleId}
{...props}
>
{title ? <title id={titleId}>{title}</title> : null}
<path
fillRule="evenodd"
d="M8 13.923A5.93 5.93 0 0013.923 8 5.93 5.93 0 008 2.077 5.93 5.93 0 002.077 8 5.93 5.93 0 008 13.923zM8 1a7 7 0 110 14A7 7 0 018 1zM4.77 7.462h2.692v-3.77a.539.539 0 011.076 0V8A.539.539 0 018 8.538H4.77a.539.539 0 010-1.076z"
clipRule="evenodd"
/>
<circle cx="12" cy="12" r="4" />
<path
fill="#fff"
d="M13.998 11.922l-2.806-1.905a.099.099 0 00-.1-.006.095.095 0 00-.052.084v3.81c0 .035.02.068.052.084a.098.098 0 00.1-.006l2.806-1.905a.094.094 0 000-.156z"
/>
</svg>
);

View file

@ -8,7 +8,6 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButtonIcon, EuiPanel } from '@elastic/eui';
import { ClockPlayIcon } from './clock_play_icon';
export interface Props {
isTimesliderOpen: boolean;
@ -39,10 +38,11 @@ export function TimesliderToggleButton(props: Props) {
size="s"
onClick={onClick}
data-test-subj="timesliderToggleButton"
iconType={ClockPlayIcon}
color={props.isTimesliderOpen ? 'primary' : 'text'}
iconType="timeslider"
aria-label={label}
title={label}
isSelected={props.isTimesliderOpen}
display={props.isTimesliderOpen ? 'fill' : 'empty'}
/>
</EuiPanel>
);