[Maps] LayerControl read only mode (#31318)

* hide add layer button in read only mode

* update TOCEntry for read only mode

* make LayerTOC read only

* default isReadOnly to false

* remove action creator until its beig used
This commit is contained in:
Nathan Reese 2019-02-20 12:26:52 -07:00 committed by GitHub
parent e29aa096ef
commit 425539e122
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 526 additions and 83 deletions

View file

@ -0,0 +1,108 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LayerControl is rendered 1`] = `
<EuiPanel
className="mapWidgetControl mapWidgetControl-hasShadow"
grow={false}
hasShadow={false}
paddingSize="none"
>
<EuiFlexItem
className="mapWidgetControl__header"
component="div"
grow={false}
>
<EuiFlexGroup
alignItems="center"
component="div"
direction="row"
gutterSize="none"
justifyContent="spaceBetween"
responsive={false}
wrap={false}
>
<EuiFlexItem
component="div"
grow={true}
>
<EuiTitle
size="xs"
textTransform="none"
>
<h2>
Layers
</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={false}
>
<EuiButtonEmpty
color="primary"
flush="right"
iconSide="left"
onClick={[Function]}
size="xs"
type="button"
>
Add layer
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem
className="mapLayerControl"
component="div"
grow={true}
>
<LayerTOC />
</EuiFlexItem>
</EuiPanel>
`;
exports[`LayerControl props isReadOnly 1`] = `
<EuiPanel
className="mapWidgetControl mapWidgetControl-hasShadow"
grow={false}
hasShadow={false}
paddingSize="none"
>
<EuiFlexItem
className="mapWidgetControl__header"
component="div"
grow={false}
>
<EuiFlexGroup
alignItems="center"
component="div"
direction="row"
gutterSize="none"
justifyContent="spaceBetween"
responsive={false}
wrap={false}
>
<EuiFlexItem
component="div"
grow={true}
>
<EuiTitle
size="xs"
textTransform="none"
>
<h2>
Layers
</h2>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem
className="mapLayerControl"
component="div"
grow={true}
>
<LayerTOC />
</EuiFlexItem>
</EuiPanel>
`;

View file

@ -7,10 +7,17 @@
import { connect } from 'react-redux';
import { LayerControl } from './view';
import {
getIsReadOnly,
updateFlyout,
FLYOUT_STATE
FLYOUT_STATE,
} from '../../../store/ui';
function mapStateToProps(state = {}) {
return {
isReadOnly: getIsReadOnly(state),
};
}
function mapDispatchToProps(dispatch) {
return {
showAddLayerWizard: () => {
@ -19,5 +26,5 @@ function mapDispatchToProps(dispatch) {
};
}
const connectedLayerControl = connect(null, mapDispatchToProps)(LayerControl);
const connectedLayerControl = connect(mapStateToProps, mapDispatchToProps)(LayerControl);
export { connectedLayerControl as LayerControl };

View file

@ -8,6 +8,7 @@ import { connect } from 'react-redux';
import { LayerTOC } from './view';
import { updateLayerOrder } from '../../../../actions/store_actions';
import { getLayerList } from '../../../../selectors/map_selectors';
import { getIsReadOnly } from '../../../../store/ui';
const mapDispatchToProps = {
updateLayerOrder: newOrder => updateLayerOrder(newOrder)
@ -15,6 +16,7 @@ const mapDispatchToProps = {
function mapStateToProps(state = {}) {
return {
isReadOnly: getIsReadOnly(state),
layerList: getLayerList(state)
};
}

View file

@ -0,0 +1,160 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TOCEntry is rendered 1`] = `
<div
className="mapTocEntry"
data-layerid="1"
id="1"
>
<EuiFlexGroup
alignItems="center"
className="mapTocEntry-visible"
component="div"
direction="row"
gutterSize="none"
justifyContent="flexStart"
responsive={false}
wrap={false}
>
<EuiFlexItem
component="div"
grow={false}
>
<LayerTocActions
displayName="layer 1"
fitToBounds={[Function]}
layer={
Object {
"getDisplayName": [Function],
"getId": [Function],
"getTOCDetails": [Function],
"hasErrors": [Function],
"isVisible": [Function],
"showAtZoomLevel": [Function],
}
}
toggleVisible={[Function]}
zoom={0}
/>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={true}
>
<EuiLink
color="text"
data-test-subj="mapOpenLayerButtonlayer 1"
onClick={[Function]}
type="button"
>
<div
className="eui-textTruncate eui-textLeft"
style={
Object {
"width": 180,
}
}
>
layer 1
</div>
</EuiLink>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={false}
>
<span
className="mapTocEntry__grab"
>
<EuiIcon
size="m"
type="grab"
/>
</span>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexItem
component="div"
grow={true}
>
<EuiSpacer
size="s"
/>
<div>
TOC details mock
</div>
</EuiFlexItem>
</div>
`;
exports[`TOCEntry props isReadOnly 1`] = `
<div
className="mapTocEntry"
data-layerid="1"
id="1"
>
<EuiFlexGroup
alignItems="center"
className="mapTocEntry-visible"
component="div"
direction="row"
gutterSize="none"
justifyContent="flexStart"
responsive={false}
wrap={false}
>
<EuiFlexItem
component="div"
grow={false}
>
<LayerTocActions
displayName="layer 1"
fitToBounds={[Function]}
layer={
Object {
"getDisplayName": [Function],
"getId": [Function],
"getTOCDetails": [Function],
"hasErrors": [Function],
"isVisible": [Function],
"showAtZoomLevel": [Function],
}
}
toggleVisible={[Function]}
zoom={0}
/>
</EuiFlexItem>
<EuiFlexItem
component="div"
grow={true}
>
<EuiText
grow={true}
size="m"
>
<div
className="eui-textTruncate eui-textLeft"
style={
Object {
"width": 180,
}
}
>
layer 1
</div>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexItem
component="div"
grow={true}
>
<EuiSpacer
size="s"
/>
<div>
TOC details mock
</div>
</EuiFlexItem>
</div>
`;

View file

@ -7,7 +7,7 @@
import _ from 'lodash';
import { connect } from 'react-redux';
import { TOCEntry } from './view';
import { updateFlyout, FLYOUT_STATE } from '../../../../../store/ui';
import { getIsReadOnly, updateFlyout, FLYOUT_STATE } from '../../../../../store/ui';
import {
fitToLayerExtent,
setSelectedLayer,
@ -19,6 +19,7 @@ import { hasDirtyState, getSelectedLayer } from '../../../../../selectors/map_se
function mapStateToProps(state = {}) {
return {
isReadOnly: getIsReadOnly(state),
zoom: _.get(state, 'map.mapState.zoom', 0),
getSelectedLayerSelector: () => {
return getSelectedLayer(state);

View file

@ -16,6 +16,8 @@ import {
EuiModalFooter,
EuiButton,
EuiButtonEmpty,
EuiLink,
EuiText,
} from '@elastic/eui';
import { LayerTocActions } from '../../../../../shared/components/layer_toc_actions';
@ -50,6 +52,22 @@ export class TOCEntry extends React.Component {
this._updateDisplayName();
}
_openLayerPanelWithCheck = () => {
const selectedLayer = this.props.getSelectedLayerSelector();
if (selectedLayer && selectedLayer.getId() === this.props.layer.getId()) {
return;
}
if (this.props.hasDirtyStateSelector()) {
this.setState({
shouldShowModal: true
});
return;
}
this.props.openLayerPanel(this.props.layer.getId());
}
_renderCancelModal() {
if (!this.state.shouldShowModal) {
return null;
@ -94,85 +112,108 @@ export class TOCEntry extends React.Component {
);
}
render() {
const { layer, openLayerPanel, zoom, toggleVisible, fitToBounds } = this.props;
const legendIcon = (
<LayerTocActions
layer={layer}
fitToBounds={() => {
fitToBounds(layer.getId());
}}
zoom={zoom}
toggleVisible={() => {
toggleVisible(layer.getId());
}}
displayName={this.state.displayName}
/>
_renderLayerName() {
const displayName = (
<div style={{ width: 180 }} className="eui-textTruncate eui-textLeft">
{this.state.displayName}
</div>
);
let tocDetails = layer.getTOCDetails();
if (tocDetails) {
tocDetails = (
<EuiFlexItem>
<EuiSpacer size="s"/>
{tocDetails}
if (this.props.isReadOnly) {
return (
<EuiText>
{displayName}
</EuiText>
);
}
return (
<EuiLink
color="text"
onClick={this._openLayerPanelWithCheck}
data-test-subj={`mapOpenLayerButton${this.state.displayName}`}
>
{displayName}
</EuiLink>
);
}
_renderLayerHeader() {
const {
isReadOnly,
layer,
zoom,
toggleVisible,
fitToBounds
} = this.props;
let sortIcon;
if (!isReadOnly) {
sortIcon = (
<EuiFlexItem grow={false}>
<span className="mapTocEntry__grab"><EuiIcon type="grab"/></span>
</EuiFlexItem>
);
}
const cancelModal = this._renderCancelModal();
const openLayerPanelWithCheck = () => {
const selectedLayer = this.props.getSelectedLayerSelector();
if (selectedLayer && selectedLayer.getId() === this.props.layer.getId()) {
return;
}
if (this.props.hasDirtyStateSelector()) {
this.setState({
shouldShowModal: true
});
} else {
openLayerPanel(layer.getId());
}
};
return (
<div
className="mapTocEntry"
id={layer.getId()}
data-layerid={layer.getId()}
<EuiFlexGroup
gutterSize="none"
alignItems="center"
responsive={false}
className={
layer.isVisible() && layer.showAtZoomLevel(zoom)
&& !layer.hasErrors() ? 'mapTocEntry-visible' : 'mapTocEntry-notVisible'
}
>
{cancelModal}
<EuiFlexGroup
gutterSize="none"
alignItems="center"
responsive={false}
className={
layer.isVisible() && layer.showAtZoomLevel(zoom)
&& !layer.hasErrors() ? 'mapTocEntry-visible' : 'mapTocEntry-notVisible'
}
>
<EuiFlexItem grow={false}>
{ legendIcon }
</EuiFlexItem>
<EuiFlexItem>
<button
onClick={openLayerPanelWithCheck}
data-test-subj={`mapOpenLayerButton${this.state.displayName}`}
>
<div style={{ width: 180 }} className="eui-textTruncate eui-textLeft">
{this.state.displayName}
</div>
</button>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<span className="mapTocEntry__grab"><EuiIcon type="grab"/></span>
</EuiFlexItem>
</EuiFlexGroup>
{tocDetails}
</div>
<EuiFlexItem grow={false}>
<LayerTocActions
layer={layer}
fitToBounds={() => {
fitToBounds(layer.getId());
}}
zoom={zoom}
toggleVisible={() => {
toggleVisible(layer.getId());
}}
displayName={this.state.displayName}
/>
</EuiFlexItem>
<EuiFlexItem>
{this._renderLayerName()}
</EuiFlexItem>
{sortIcon}
</EuiFlexGroup>
);
}
_renderLayerDetails() {
const tocDetails = this.props.layer.getTOCDetails();
if (!tocDetails) {
return null;
}
return (
<EuiFlexItem>
<EuiSpacer size="s"/>
{tocDetails}
</EuiFlexItem>
);
}
render() {
return (
<div
className="mapTocEntry"
id={this.props.layer.getId()}
data-layerid={this.props.layer.getId()}
>
{this._renderCancelModal()}
{this._renderLayerHeader()}
{this._renderLayerDetails()}
</div>
);
}
}

View file

@ -0,0 +1,66 @@
/*
* 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 { shallow } from 'enzyme';
import { TOCEntry } from './view';
const mockLayer = {
getId: () => { return '1'; },
getTOCDetails: () => { return (<div>TOC details mock</div>); },
getDisplayName: () => { return 'layer 1'; },
isVisible: () => { return true; },
showAtZoomLevel: () => { return true; },
hasErrors: () => { return false; },
};
const defaultProps = {
layer: mockLayer,
openLayerPanel: () => {},
toggleVisible: () => {},
fitToBounds: () => {},
getSelectedLayerSelector: () => {},
hasDirtyStateSelector: () => {},
zoom: 0,
};
describe('TOCEntry', () => {
test('is rendered', async () => {
const component = shallow(
<TOCEntry
{...defaultProps}
/>
);
// Ensure all promises resolve
await new Promise(resolve => process.nextTick(resolve));
// Ensure the state changes are reflected
component.update();
expect(component)
.toMatchSnapshot();
});
describe('props', () => {
test('isReadOnly', async () => {
const component = shallow(
<TOCEntry
{...defaultProps}
isReadOnly={true}
/>
);
// Ensure all promises resolve
await new Promise(resolve => process.nextTick(resolve));
// Ensure the state changes are reflected
component.update();
expect(component)
.toMatchSnapshot();
});
});
});

View file

@ -16,7 +16,9 @@ export class LayerTOC extends React.Component {
}
componentDidMount() {
this._attachSortHandler();
if (!this.props.isReadOnly) {
this._attachSortHandler();
}
}
_attachSortHandler() {

View file

@ -14,11 +14,17 @@ import {
} from '@elastic/eui';
import { LayerTOC } from './layer_toc';
export function LayerControl(props) {
const addLayer = (
<EuiButtonEmpty size="xs" flush="right" onClick={props.showAddLayerWizard}>
Add layer
</EuiButtonEmpty>);
export function LayerControl({ isReadOnly, showAddLayerWizard }) {
let addLayer;
if (!isReadOnly) {
addLayer = (
<EuiFlexItem grow={false}>
<EuiButtonEmpty size="xs" flush="right" onClick={showAddLayerWizard}>
Add layer
</EuiButtonEmpty>
</EuiFlexItem>
);
}
return (
<EuiPanel className="mapWidgetControl mapWidgetControl-hasShadow" paddingSize="none" grow={false}>
@ -34,9 +40,7 @@ export function LayerControl(props) {
<h2>Layers</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
{addLayer}
</EuiFlexItem>
{addLayer}
</EuiFlexGroup>
</EuiFlexItem>

View file

@ -0,0 +1,47 @@
/*
* 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.
*/
jest.mock('./layer_toc', () => ({
LayerTOC: () => {
return (<div>mockLayerTOC</div>);
}
}));
import React from 'react';
import { shallow } from 'enzyme';
import { LayerControl } from './view';
const defaultProps = {
showAddLayerWizard: () => {}
};
describe('LayerControl', () => {
test('is rendered', () => {
const component = shallow(
<LayerControl
{...defaultProps}
/>
);
expect(component)
.toMatchSnapshot();
});
describe('props', () => {
test('isReadOnly', () => {
const component = shallow(
<LayerControl
{...defaultProps}
isReadOnly={true}
/>
);
expect(component)
.toMatchSnapshot();
});
});
});

View file

@ -9,6 +9,7 @@ export const UPDATE_FLYOUT = 'UPDATE_FLYOUT';
export const CLOSE_SET_VIEW = 'CLOSE_SET_VIEW';
export const OPEN_SET_VIEW = 'OPEN_SET_VIEW';
export const SET_FULL_SCREEN = 'SET_FULL_SCREEN';
export const SET_READ_ONLY = 'SET_READ_ONLY';
export const FLYOUT_STATE = {
NONE: 'NONE',
LAYER_PANEL: 'LAYER_PANEL',
@ -18,6 +19,7 @@ export const FLYOUT_STATE = {
const INITIAL_STATE = {
flyoutDisplay: FLYOUT_STATE.NONE,
isFullScreen: false,
isReadOnly: false,
};
// Reducer
@ -31,6 +33,8 @@ export function ui(state = INITIAL_STATE, action) {
return { ...state, isSetViewOpen: true };
case SET_FULL_SCREEN:
return { ...state, isFullScreen: action.isFullScreen };
case SET_READ_ONLY:
return { ...state, isReadOnly: action.isReadOnly };
default:
return state;
}
@ -71,3 +75,4 @@ export const getFlyoutDisplay = ({ ui }) => ui && ui.flyoutDisplay
|| INITIAL_STATE.flyoutDisplay;
export const getIsSetViewOpen = ({ ui }) => _.get(ui, 'isSetViewOpen', false);
export const getIsFullScreen = ({ ui }) => _.get(ui, 'isFullScreen', false);
export const getIsReadOnly = ({ ui }) => _.get(ui, 'isReadOnly', true);