[Maps] Design/SASS cleanup (#28716)

* Separating out GIS styles from _main

to their respective components

* Clean up some styles and try to use more EUI where possible

* Change “landing page” to url

* Change dynamic switch into toggle button

# Conflicts:
#	x-pack/plugins/gis/public/shared/layers/styles/components/vector/size/size_range_selector.js

* Copy clean up

* Add comment

* remove dynmaic style description from heatmap style

* Some fixes to new data source selector and copy changes

* A couple more copy edits

* update oridinal fields when layer changes

* A couple of fixes

* rector static_dynamic_styling_option.js so it makes sense

* fix range selector not showing style

* Specific browser fixes
This commit is contained in:
Caroline Horn 2019-01-17 16:21:40 -05:00 committed by Nathan Reese
parent a38c888351
commit 2bea9afbfe
49 changed files with 674 additions and 670 deletions

View file

@ -1,10 +1,3 @@
// Makes sure the listing page is full height with proper background
map-listing, .gisListingPage {
display: flex;
flex-grow: 1;
}
#gis-plugin {
display: flex;
flex-direction: column;
@ -19,284 +12,25 @@ map-listing, .gisListingPage {
flex-direction: column;
}
.gisMapWrapper {
display: flex;
flex-direction: column;
position: relative;
}
.layerToast {
margin-top: -150px !important;
pointer-events: none;
}
// MapBox
.mapContainer {
flex-grow: 1;
}
/**
* 1. The overlay captures mouse events even if it's empty space. To counter-act this,
* we remove all pointer events from the overlay then add them back on the
* individual widgets.
*/
.gisWidgetOverlay {
position: absolute;
z-index: $euiZLevel1;
top: $euiSizeM;
right: $euiSizeM;
bottom: $euiSizeM;
// left: $euiSizeM;
pointer-events: none; /* 1 */
}
.gisWidgetOverlay__rightSide {
min-width: 17rem;
max-width: 24rem;
}
.gisWidgetControl {
max-height: 100%;
overflow: hidden;
padding-bottom: $euiSizeS; // ensures the scrollbar doesn't appear unnecessarily because of flex group negative margins
border-color: transparent !important;
flex-direction: column;
display: flex;
pointer-events: all; /* 1 */
&.euiPanel--shadow {
@include euiBottomShadowLarge;
}
.gisWidgetControl__header {
padding: $euiSizeS $euiSize;
flex-shrink: 0;
}
}
.gisAttributionControl {
padding: 0 $euiSizeXS;
}
.gisViewControl__coordinates {
padding: $euiSizeXS $euiSizeS;
justify-content: center;
pointer-events: none;
}
.gisViewControl__gotoButton {
min-width: 0;
pointer-events: all; /* 1 */
}
.gisWidgetControl__tocHolder {
@include euiScrollBar;
overflow-y: auto;
}
.layerEntry {
padding: $euiSizeS $euiSize;
position: relative;
}
.alphaRange {
.euiFieldNumber {
max-width: 5.5em !important;
}
}
.colorPicker {
.euiColorPickerPopUp {
z-index: 3000;
}
}
.colorGradient {
width: 100%;
height: 20px;
position: relative;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
}
.layerEntry > .visible {
opacity: 1;
}
.layerEntry > .notvisible { //ugh, already global classname named `invisible`
opacity: 0.5;
}
.layerEntry .grab:hover {
cursor: -webkit-grab;
cursor: grab;
}
.layerSettings__type {
.euiIcon {
margin-top: -2px;
}
}
.hidden {
display: none
}
// HOTFIX coming from EUI
.sourceSelectItem {
width: 374px;
}
.mapboxgl-popup {
z-index: 100;
}
.gisLayerPanel {
background-color: $euiColorLightestShade;
width: 0;
overflow: hidden;
> * {
width: $euiSizeXXL * 11;
}
&-isVisible {
width: $euiSizeXXL * 11;
transition: width $euiAnimSpeedNormal $euiAnimSlightResistance;
}
}
.gisViewPanel__header,
.gisViewPanel__body,
.gisViewPanel__footer {
padding: $euiSize;
}
.gisViewPanel__header {
padding-bottom: 0;
flex-shrink: 0;
z-index: 2;
box-shadow: 0 $euiSize $euiSize (-$euiSize / 2) $euiColorLightestShade;
}
.gisViewPanel__title {
svg {
margin: -.1em .5em 0 0;
}
}
.gisViewPanel__body {
overflow: hidden;
overflow-y: auto;
@include euiScrollBar;
> * {
flex: 0 0 auto;
}
> *:not(:last-child) {
margin-bottom: $euiSize;
}
}
.gisViewPanel__footer {
padding-top: 0;
flex-shrink: 0;
z-index: 2;
box-shadow: 0 ($euiSize *-1) $euiSize (-$euiSize / 2) $euiColorLightestShade;
}
// This is not good practice to create such a generic class.
// I can't seem to find it being applied anywhere in GIS
// .hidden {
// display: none
// }
// EUIFIXTODO:
.euiColorPicker__emptySwatch {
position: relative;
}
.visibilityToggle {
position: relative;
display: inline-block;
min-height: 20px;
color: $euiColorMediumShade;
.visibilityToggle__body {
line-height: 19px;
> * {
vertical-align: baseline;
}
}
.visibilityToggle__content {
svg {
display: inline-block;
vertical-align: middle;
}
.filter {
display: none;
}
}
.visibilityToggle__eye,
.visibilityToggle__eyeClosed,
.visibilityToggle__content {
transition: opacity .2s ease-in-out;
}
.visibilityToggle__eye,
.visibilityToggle__eyeClosed {
position: absolute;
left: 0;
top: 0;
opacity: 0;
z-index: 1;
}
.euiSwitch__input {
z-index: 2;
position: absolute;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
}
.visibilityToggle__body {
display: block;
display: block;
height: 16px;
width: 16px;
}
&:hover,
&:focus {
cursor: pointer;
color: $euiColorPrimary;
.visibilityToggle__content {
opacity: 0;
}
.euiSwitch__input:checked {
+ .visibilityToggle__body > .visibilityToggle__eye {
opacity: 1;
}
}
.euiSwitch__input:not(:checked) {
+ .visibilityToggle__body > .visibilityToggle__content {
opacity: 0;
}
+ .visibilityToggle__body > .visibilityToggle__eyeClosed {
opacity: 1;
}
}
}
}
.euiComboBox {
.euiComboBox__inputWrap {
display: flex;
}
}

View file

@ -1 +1,5 @@
@import './layer_panel/join_editor/resources/join';
@import './gis_map/gis_map';
@import './layer_addpanel/layer_addpanel';
@import './layer_panel/index';
@import './toasts/toasts';
@import './widget_overlay/index';

View file

@ -0,0 +1,18 @@
.gisMapWrapper {
position: relative;
}
.gisMapLayerPanel {
background-color: $euiColorLightestShade;
width: 0;
overflow: hidden;
> * {
width: $euiSizeXXL * 11;
}
&-isVisible {
width: $euiSizeXXL * 11;
transition: width $euiAnimSpeedNormal $euiAnimSlightResistance;
}
}

View file

@ -66,10 +66,10 @@ export class GisMap extends Component {
if (noFlyoutVisible) {
currentPanel = null;
} else if (addLayerVisible) {
currentPanelClassName = "gisLayerPanel-isVisible";
currentPanelClassName = "gisMapLayerPanel-isVisible";
currentPanel = <AddLayerPanel/>;
} else if (layerDetailsVisible) {
currentPanelClassName = "gisLayerPanel-isVisible";
currentPanelClassName = "gisMapLayerPanel-isVisible";
currentPanel = (
<LayerPanel/>
);
@ -81,7 +81,7 @@ export class GisMap extends Component {
<WidgetOverlay/>
</EuiFlexItem>
<EuiFlexItem className={`gisLayerPanel ${currentPanelClassName}`} grow={false}>
<EuiFlexItem className={`gisMapLayerPanel ${currentPanelClassName}`} grow={false}>
{currentPanel}
</EuiFlexItem>

View file

@ -0,0 +1,10 @@
.gisLayerAddpanel__card {
// EUITODO: Fix horizontal layout so it works with any size icon
.euiCard__content {
padding-top: 0 !important;
}
.euiCard__top + .euiCard__content {
padding-top: 2px !important;
}
}

View file

@ -7,16 +7,18 @@
import React, { Component, Fragment } from 'react';
import { ALL_SOURCES } from '../../shared/layers/sources/all_sources';
import {
EuiSpacer,
EuiButton,
EuiHorizontalRule,
EuiButtonEmpty,
EuiFlexGroup,
EuiFlexItem,
EuiTitle,
EuiPanel,
EuiSpacer,
EuiCard,
EuiIcon,
EuiFlyoutHeader,
EuiFlyoutBody,
EuiFlyoutFooter,
} from '@elastic/eui';
export class AddLayerPanel extends Component {
@ -67,7 +69,7 @@ export class AddLayerPanel extends Component {
}}
fill
>
Create layer
Add layer
</EuiButton>
);
}
@ -75,17 +77,20 @@ export class AddLayerPanel extends Component {
_renderSourceCards() {
return ALL_SOURCES.map(Source => {
const icon = Source.icon
? <EuiIcon type={Source.icon} size="xl" />
? <EuiIcon type={Source.icon} size="l" />
: null;
return (
<EuiCard
key={Source.type}
title={Source.title}
icon={icon}
onClick={() => this._onSourceTypeChange(Source.type)}
description={Source.description}
layout="horizontal"
/>
<Fragment key={Source.type}>
<EuiSpacer size="s" />
<EuiCard
className="gisLayerAddpanel__card"
title={Source.title}
icon={icon}
onClick={() => this._onSourceTypeChange(Source.type)}
description={Source.description}
layout="horizontal"
/>
</Fragment>
);
});
}
@ -117,12 +122,14 @@ export class AddLayerPanel extends Component {
return (
<Fragment>
<EuiButtonEmpty
contentProps={{ style: { justifyContent: 'left' } }}
size="xs"
flush="left"
onClick={this._clearSource}
iconType="arrowLeft"
>
Change data source
</EuiButtonEmpty>
<EuiSpacer size="s" />
<EuiPanel>
{Source.renderEditor(editorProperties)}
</EuiPanel>
@ -144,21 +151,17 @@ export class AddLayerPanel extends Component {
direction="column"
gutterSize="none"
>
<EuiFlexItem grow={false} className="gisViewPanel__header">
<EuiFlyoutHeader hasBorder className="gisLayerPanel__header">
<EuiTitle size="s">
<h1>Add layer</h1>
<h2>Add layer</h2>
</EuiTitle>
<EuiSpacer size="m"/>
<EuiHorizontalRule margin="none"/>
</EuiFlexItem>
</EuiFlyoutHeader>
<EuiFlexItem className="gisViewPanel__body">
<EuiFlyoutBody className="gisLayerPanel__body">
{this._renderAddLayerForm()}
</EuiFlexItem>
</EuiFlyoutBody>
<EuiFlexItem grow={false} className="gisViewPanel__footer">
<EuiHorizontalRule margin="none"/>
<EuiSpacer size="m"/>
<EuiFlyoutFooter className="gisLayerPanel__footer">
<EuiFlexGroup justifyContent="spaceBetween" responsive={false}>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
@ -172,7 +175,7 @@ export class AddLayerPanel extends Component {
{this._renderNextBtn()}
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlyoutFooter>
</EuiFlexGroup>
);
}

View file

@ -0,0 +1,3 @@
@import './layer_panel';
@import './join_editor/resources/join';
@import './settings_panel/settings_panel';

View file

@ -0,0 +1,29 @@
/**
* 1. Firefox and IE don't respect bottom padding of overflow scrolling flex items.
* So we instead strip out the bottom padding and add the same amount as a margin
* to the last child element.
*/
.gisLayerPanel__header,
.gisLayerPanel__body,
.gisLayerPanel__footer {
padding: $euiSize;
}
.gisLayerPanel__body {
flex-basis: 1px; /* Fixes scrolling for Firefox */
padding-bottom: 0; /* 1 */
> *:last-child {
margin-bottom: $euiSize; /* 1 */
}
}
.gisLayerPanel__header {
box-shadow: 0 $euiSize $euiSize (-$euiSize / 2) $euiColorLightestShade;
}
.gisLayerPanel__footer {
border-top: $euiBorderThin;
box-shadow: 0 ($euiSize *-1) $euiSize (-$euiSize / 2) $euiColorLightestShade;
}

View file

@ -1,14 +1,9 @@
.gisJoinItem {
background: tintOrShade($euiColorLightShade, 85%, 0);
border-radius: $euiBorderRadius;
margin: $euiSizeXS 0;
padding: $euiSizeXS;
position: relative;
&:last-of-type {
margin-bottom: 0;
}
&:hover,
&:focus {
.gisJoinItem__delete {
@ -19,13 +14,13 @@
}
.gisJoinItem__delete {
@include euiBottomShadowSmall;
position: absolute;
right: 0;
top: 50%;
margin-right: -8px;
margin-top: -12px;
margin-right: -$euiSizeS;
margin-top: -$euiSizeM;
background: $euiColorEmptyShade;
@include euiBottomShadowSmall;
padding: $euiSizeXS;
visibility: hidden;
opacity: 0;

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { } from 'react';
import React, { Fragment } from 'react';
import uuid from 'uuid/v4';
import {
@ -37,13 +37,15 @@ export function JoinEditor({ joins, layer, onChange }) {
};
return (
<Join
key={index}
join={joinDescriptor}
layer={layer}
onChange={handleOnChange}
onRemove={handleOnRemove}
/>
<Fragment key={index}>
<EuiSpacer size="m" />
<Join
join={joinDescriptor}
layer={layer}
onChange={handleOnChange}
onRemove={handleOnRemove}
/>
</Fragment>
);
});
};
@ -73,7 +75,6 @@ export function JoinEditor({ joins, layer, onChange }) {
<EuiButtonIcon iconType="plusInCircle" onClick={addJoin} aria-label="Add join" title="Add join" />
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />
{renderJoins()}
</div>

View file

@ -0,0 +1,6 @@
.gisAlphaRange {
// EUITODO: Fix range slider to calculate the max digits available
.euiFieldNumber {
max-width: 5.5em !important;
}
}

View file

@ -58,7 +58,7 @@ export class SettingsPanel extends Component {
renderZoomSliders() {
return (
<EuiFormRow
helpText="Dislay layer when map is within zoom level range."
helpText="Display layer when map is within zoom level range."
>
<EuiFlexGroup>
<EuiFlexItem>
@ -110,9 +110,9 @@ export class SettingsPanel extends Component {
renderAlphaSlider() {
return (
<EuiFormRow
label="Layer opacity"
label="Layer transparency"
>
<div className="alphaRange">
<div className="gisAlphaRange">
<EuiRange
min={.00}
max={1.00}

View file

@ -9,58 +9,37 @@ import React from 'react';
import {
EuiTitle,
EuiPanel,
EuiSpacer
EuiSpacer,
EuiText
} from '@elastic/eui';
export class StyleTabs extends React.Component {
constructor(props) {
super();
this.state = {
currentStyle: props.layer && props.layer.getCurrentStyle()
};
}
static getDerivedStateFromProps(nextProps, prevState) {
const currentStyle = nextProps.layer.getCurrentStyle();
if (currentStyle) {
return {
...prevState,
currentStyle
};
} else {
return {};
export function StyleTabs({ layer, reset, updateStyle }) {
return layer.getSupportedStyles().map((Style, index) => {
let description;
if (Style.description) {
description = (
<EuiText size="s">
<p>{Style.description}</p>
</EuiText>
);
}
}
render() {
const { currentStyle } = this.state;
const supportedStyles = this.props.layer.getSupportedStyles();
const styleEditors = supportedStyles.map((style, index) => {
const seedStyle = (style.canEdit(currentStyle)) ? currentStyle : null;
const editorHeader = (
<EuiTitle size="xs"><h5>{style.getDisplayName()}</h5></EuiTitle>
);
const styleEditor = this.props.layer.renderStyleEditor(style, {
handleStyleChange: (styleDescriptor) => {
this.props.updateStyle(styleDescriptor);
},
style: seedStyle,
resetStyle: () => this.props.reset()
});
return (
<EuiPanel key={index}>
{editorHeader}
<EuiSpacer margin="m"/>
{styleEditor}
</EuiPanel>
);
const currentStyle = layer.getCurrentStyle();
const styleEditor = layer.renderStyleEditor(Style, {
handleStyleChange: (styleDescriptor) => {
updateStyle(styleDescriptor);
},
style: (Style.canEdit(currentStyle)) ? currentStyle : null,
resetStyle: () => reset()
});
return (styleEditors);
}
return (
<EuiPanel key={index}>
<EuiTitle size="xs"><h5>{Style.getDisplayName()}</h5></EuiTitle>
{description}
<EuiSpacer margin="m"/>
{styleEditor}
</EuiPanel>
);
});
}

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import React, { Fragment } from 'react';
import { StyleTabs } from './style_tabs';
import { JoinEditor } from './join_editor';
@ -12,12 +12,14 @@ import { FlyoutFooter } from './flyout_footer';
import { SettingsPanel } from './settings_panel';
import {
EuiHorizontalRule,
EuiFlexItem,
EuiTitle,
EuiSpacer,
EuiPanel,
EuiFlexGroup,
EuiFlyoutHeader,
EuiFlyoutBody,
EuiFlyoutFooter,
EuiSpacer,
} from '@elastic/eui';
export class LayerPanel extends React.Component {
@ -48,9 +50,12 @@ export class LayerPanel extends React.Component {
}
return (
<EuiPanel>
<JoinEditor/>
</EuiPanel>
<Fragment>
<EuiPanel>
<JoinEditor/>
</EuiPanel>
<EuiSpacer size="s" />
</Fragment>
);
}
@ -62,28 +67,29 @@ export class LayerPanel extends React.Component {
direction="column"
gutterSize="none"
>
<EuiFlexItem grow={false} className="gisViewPanel__header">
<EuiTitle size="s" className="gisViewPanel__title">
<h1>
<EuiFlyoutHeader hasBorder className="gisLayerPanel__header">
<EuiFlexGroup responsive={false} alignItems="center" gutterSize="s">
<EuiFlexItem grow={false}>
{selectedLayer.getIcon()}
{this.state.displayName}
</h1>
</EuiTitle>
<EuiSpacer size="m"/>
<EuiHorizontalRule margin="none"/>
</EuiFlexItem>
</EuiFlexItem>
<EuiFlexItem>
<EuiTitle size="s">
<h2>{this.state.displayName}</h2>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutHeader>
<EuiFlexItem className="gisViewPanel__body">
<EuiFlyoutBody className="gisLayerPanel__body">
<SettingsPanel/>
<EuiSpacer size="s" />
{this._renderJoinSection()}
<StyleTabs layer={selectedLayer}/>
</EuiFlexItem>
</EuiFlyoutBody>
<EuiFlexItem grow={false} className="gisViewPanel__footer">
<EuiHorizontalRule margin="none"/>
<EuiSpacer size="m"/>
<EuiFlyoutFooter className="gisLayerPanel__footer">
<FlyoutFooter/>
</EuiFlexItem>
</EuiFlyoutFooter>
</EuiFlexGroup>
);
}

View file

@ -0,0 +1,4 @@
.gisLayerToast {
margin-top: -150px !important;
pointer-events: none;
}

View file

@ -10,12 +10,12 @@ export function Toasts({ layerLoadToast, clearLayerLoadToast }) {
if (layerLoadToast === 'success') {
toastNotifications.add({
title: 'Layer added',
className: 'layerToast'
className: 'gisLayerToast'
}) && clearLayerLoadToast();
} else if (layerLoadToast === 'error') {
toastNotifications.addDanger({
title: 'Error adding layer',
className: 'layerToast'
className: 'gisLayerToast'
}) && clearLayerLoadToast();
} else {
// Do nothing

View file

@ -0,0 +1,4 @@
@import './widget_overlay';
@import './attribution_control/attribution_control';
@import './layer_control/index';
@import './view_control/view_control';

View file

@ -0,0 +1,40 @@
/**
* 1. The overlay captures mouse events even if it's empty space. To counter-act this,
* we remove all pointer events from the overlay then add them back on the
* individual widgets.
*/
.gisWidgetOverlay {
position: absolute;
z-index: $euiZLevel1;
top: $euiSizeM;
right: $euiSizeM;
bottom: $euiSizeM;
pointer-events: none; /* 1 */
}
.gisWidgetOverlay__rightSide {
min-width: 17rem;
max-width: 24rem;
}
.gisWidgetControl {
max-height: 100%;
overflow: hidden;
padding-bottom: $euiSizeS; // ensures the scrollbar doesn't appear unnecessarily because of flex group negative margins
border-color: transparent !important;
flex-direction: column;
display: flex;
pointer-events: all; /* 1 */
&.gisWidgetControl-hasShadow {
@include euiBottomShadowLarge;
}
.gisWidgetControl__header {
padding: $euiSizeS $euiSize;
flex-shrink: 0;
}
}

View file

@ -0,0 +1,3 @@
.gisAttributionControl {
padding: 0 $euiSizeXS;
}

View file

@ -0,0 +1,2 @@
@import './layer_control';
@import './layer_toc/toc_entry/toc_entry';

View file

@ -0,0 +1,5 @@
.gisLayerControl {
@include euiScrollBar;
overflow-y: auto;
flex-basis: auto !important; // Fixes IE and ensures the layer items are visible
}

View file

@ -0,0 +1,17 @@
.gisTocEntry {
padding: $euiSizeS $euiSize;
position: relative;
}
.gisTocEntry-visible {
opacity: 1;
}
.gisTocEntry-notVisible {
opacity: 0.5;
}
.gisTocEntry__grab:hover {
cursor: grab;
}

View file

@ -101,7 +101,7 @@ export class TOCEntry extends React.Component {
return (
<div
className="layerEntry"
className="gisTocEntry"
id={layer.getId()}
data-layerid={layer.getId()}
>
@ -109,9 +109,11 @@ export class TOCEntry extends React.Component {
gutterSize="s"
alignItems="center"
responsive={false}
className={layer.isVisible() && layer.showAtZoomLevel(zoom) && !layer.dataHasLoadError() ? 'visible' : 'notvisible'}
className={
layer.isVisible() && layer.showAtZoomLevel(zoom) && !layer.dataHasLoadError() ? 'gisTocEntry-visible' : 'gisTocEntry-notVisible'
}
>
<EuiFlexItem grow={false} className="layerEntry--visibility">
<EuiFlexItem grow={false}>
{visibilityIndicator}
</EuiFlexItem>
<EuiFlexItem>
@ -125,7 +127,7 @@ export class TOCEntry extends React.Component {
</button>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<span className="grab"><EuiIcon type="grab" className="grab"/></span>
<span className="gisTocEntry__grab"><EuiIcon type="grab"/></span>
</EuiFlexItem>
</EuiFlexGroup>
{tocDetails}

View file

@ -21,7 +21,7 @@ export function LayerControl(props) {
</EuiButtonEmpty>);
return (
<EuiPanel className="gisWidgetControl" hasShadow paddingSize="none" grow={false}>
<EuiPanel className="gisWidgetControl gisWidgetControl-hasShadow" paddingSize="none" grow={false}>
<EuiFlexItem className="gisWidgetControl__header" grow={false}>
<EuiFlexGroup
justifyContent="spaceBetween"
@ -30,8 +30,8 @@ export function LayerControl(props) {
gutterSize="none"
>
<EuiFlexItem>
<EuiTitle size="s">
<h1>Layers</h1>
<EuiTitle size="xs">
<h2>Layers</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
@ -40,7 +40,7 @@ export function LayerControl(props) {
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem className="gisWidgetControl__tocHolder">
<EuiFlexItem className="gisLayerControl">
<LayerTOC />
</EuiFlexItem>
</EuiPanel>

View file

@ -0,0 +1,16 @@
/**
* 1. The overlay captures mouse events even if it's empty space. To counter-act this,
* we remove all pointer events from the overlay then add them back on the
* individual widgets.
*/
.gisViewControl__coordinates {
padding: $euiSizeXS $euiSizeS;
justify-content: center;
pointer-events: none;
}
.gisViewControl__gotoButton {
min-width: 0;
pointer-events: all; /* 1 */
}

View file

@ -12,4 +12,4 @@
@import './main';
@import './components/index';
@import './shared/layers/layers';
@import './shared/index';

View file

@ -0,0 +1,3 @@
@import './components/index';
@import './icons/color_gradient';
@import './layers/index';

View file

@ -0,0 +1,2 @@
@import './map_listing';
@import './visibility_toggle';

View file

@ -0,0 +1,5 @@
// Makes sure the listing page is full height with proper background
map-listing, {
min-height: 100vh;
background-color: $euiColorLightestShade;
}

View file

@ -0,0 +1,78 @@
.gisVisibilityToggle {
position: relative;
display: inline-block;
min-height: 20px;
.gisVisibilityToggle__body {
line-height: 19px;
> * {
vertical-align: baseline;
}
}
.gisVisibilityToggle__content {
svg {
display: inline-block;
vertical-align: middle;
}
.filter {
display: none;
}
}
.gisVisibilityToggle__eye,
.gisVisibilityToggle__eyeClosed,
.gisVisibilityToggle__content {
transition: opacity .2s ease-in-out;
}
.gisVisibilityToggle__eye,
.gisVisibilityToggle__eyeClosed {
position: absolute;
left: 0;
top: 0;
opacity: 0;
z-index: 1;
}
.gisVisibilityToggle__input {
z-index: 2;
position: absolute;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
}
.gisVisibilityToggle__body {
@include size($euiSize);
display: block;
}
&:hover,
&:focus {
cursor: pointer;
color: $euiColorPrimary;
.gisVisibilityToggle__content {
opacity: 0;
}
.gisVisibilityToggle__input:checked {
+ .gisVisibilityToggle__body > .gisVisibilityToggle__eye {
opacity: 1;
}
}
}
.gisVisibilityToggle__input:not(:checked) {
+ .gisVisibilityToggle__body > .gisVisibilityToggle__content {
opacity: 0;
}
+ .gisVisibilityToggle__body > .gisVisibilityToggle__eyeClosed {
opacity: 1;
}
}
}

View file

@ -373,8 +373,8 @@ export class MapListing extends React.Component {
render() {
return (
<EuiPage data-test-subj="gisListingPage" className="gisListingPage">
<EuiPageBody restrictWidth>
<EuiPage data-test-subj="gisListingPage" restrictWidth>
<EuiPageBody>
{this.renderPageContent()}
</EuiPageBody>
</EuiPage>

View file

@ -22,12 +22,12 @@ export const VisibilityToggle = ({
children,
className
}) => {
const classes = classNames('visibilityToggle', className);
const classes = classNames('gisVisibilityToggle', className);
return (
<div className={classes}>
<input
className="euiSwitch__input"
className="gisVisibilityToggle__input"
name={name}
id={id}
type="checkbox"
@ -36,18 +36,18 @@ export const VisibilityToggle = ({
onChange={onChange}
/>
<span className="visibilityToggle__body">
<span className="visibilityToggle__eye" >
<span className="gisVisibilityToggle__body">
<span className="gisVisibilityToggle__eye" >
<EuiIcon
type={'eye'}
/>
</span>
<span className="visibilityToggle__eyeClosed" >
<span className="gisVisibilityToggle__eyeClosed" >
<EuiIcon
type={'eyeClosed'}
/>
</span>
<span className="visibilityToggle__content">
<span className="gisVisibilityToggle__content">
{children}
</span>
</span>

View file

@ -0,0 +1,9 @@
.gisColorGradient {
width: 100%;
height: $euiSize;
position: relative;
top: 0;
right: 0;
bottom: 0;
left: 0;
}

View file

@ -19,7 +19,7 @@ export const ColorGradient = ({ color }) => {
const background = getLinearGradient(rgbColorStrings, GRADIENT_INTERVALS);
return (
<div
className="colorGradient"
className="gisColorGradient"
style={{ background }}
/>
);
@ -33,4 +33,4 @@ function getLinearGradient(colorStrings, intervals) {
${Math.floor(100 * i / (intervals - 1))}%,`;
}
return `${linearGradient} ${colorStrings.pop()} 100%)`;
}
}

View file

@ -0,0 +1,2 @@
@import './styles/index';
@import './layers';

View file

@ -77,7 +77,7 @@ export class EMSFileSource extends VectorSource {
<p className="gisLayerDetails">
<strong className="gisLayerDetails__label">Source </strong><span>Elastic Maps Service</span><br/>
<strong className="gisLayerDetails__label">Id </strong><span>{this._descriptor.id}</span><br/>
<EuiLink href={emsHotLink} target="_blank">Preview on landing page</EuiLink><br/>
<EuiLink href={emsHotLink} target="_blank">Preview on maps.elastic.co</EuiLink><br/>
</p>
</EuiText>
);

View file

@ -68,10 +68,10 @@ export class UpdateSourceEditor extends Component {
return (
<Fragment>
<EuiFormRow
label="Fields displayed in tooltip"
label="Fields to display in tooltip"
>
<MultiFieldSelect
placeholder="Select field(s)"
placeholder="Select fields"
value={this.props.tooltipProperties}
onChange={this.onTooltipPropertiesSelect}
fields={this.state.fields}
@ -80,7 +80,7 @@ export class UpdateSourceEditor extends Component {
<EuiFormRow>
<EuiSwitch
label="Use map extent to filter data"
label="Only filter for data in the visible map area"
checked={this.props.filterByMapBounds}
onChange={this.onFilterByMapBoundsChange}
/>

View file

@ -0,0 +1,2 @@
@import './components/static_dynamic_styling_option';
@import './components/vector/color/static_color_selection';

View file

@ -0,0 +1,3 @@
.gisStaticDynamicSylingOption__dynamicSizeHack {
width: calc(100% - #{$euiSizeXXL + $euiSizeS});
}

View file

@ -64,6 +64,7 @@ export function DynamicOrdinalStyleOption({ fields, selectedOptions, onChange, t
const renderAdditionalOptions = () => {
if (!_.has(selectedOptions, 'field')) {
// TODO: Don't hide the component
return;
}
@ -74,10 +75,13 @@ export function DynamicOrdinalStyleOption({ fields, selectedOptions, onChange, t
switch (type) {
case styleTypes.COLOR_RAMP:
return (
<ColorRampSelector
onChange={onAdditionalOptionsChange}
color={_.get(selectedOptions, 'color')}
/>
<Fragment>
<ColorRampSelector
onChange={onAdditionalOptionsChange}
color={_.get(selectedOptions, 'color')}
/>
<EuiSpacer size="s" />
</Fragment>
);
case styleTypes.SIZE_RANGE:
return (
@ -94,6 +98,8 @@ export function DynamicOrdinalStyleOption({ fields, selectedOptions, onChange, t
return (
<Fragment>
{renderAdditionalOptions()}
<EuiComboBox
selectedOptions={
_.has(selectedOptions, 'field')
@ -102,15 +108,11 @@ export function DynamicOrdinalStyleOption({ fields, selectedOptions, onChange, t
}
options={groupFieldsByOrigin()}
onChange={onFieldChange}
singleSelection={true}
singleSelection={{ asPlainText: true }}
isClearable={false}
fullWidth
placeholder="Select a field"
/>
<EuiSpacer size="m" />
{renderAdditionalOptions()}
</Fragment>
);
}

View file

@ -11,22 +11,22 @@ import _ from 'lodash';
import {
EuiFlexGroup,
EuiFlexItem,
EuiSwitch,
EuiFormLabel,
EuiSpacer
EuiToolTip,
EuiFormRow,
EuiButtonToggle
} from '@elastic/eui';
export class StaticDynamicStyleSelector extends React.Component {
constructor() {
super();
this._isMounted = false;
this.state = {
ordinalFields: null,
isDynamic: false,
styleDescriptor: VectorStyle.STYLE_TYPE.STATIC
};
// Store previous options locally so when type is toggled,
// previous style options can be used.
prevOptions = {
// TODO: Move default to central location with other defaults
color: '#e6194b'
}
_canBeDynamic() {
return this.props.ordinalFields.length > 0;
}
_isDynamic() {
@ -36,127 +36,91 @@ export class StaticDynamicStyleSelector extends React.Component {
return this.props.styleDescriptor.type === VectorStyle.STYLE_TYPE.DYNAMIC;
}
componentWillUnmount() {
this._isMounted = false;
_getStyleOptions() {
return _.get(this.props, 'styleDescriptor.options');
}
componentDidMount() {
this._isMounted = true;
this._loadOrdinalFields();
this.setState({
isDynamic: this._isDynamic()
});
}
componentDidUpdate() {
const isDynamic = this._isDynamic();
if (this.state.isDynamic !== isDynamic) {
this.setState({
isDynamic
});
}
if (isDynamic) {
this._loadOrdinalFields();
}
}
async _loadOrdinalFields() {
if (!this._isMounted) {
return;
}
//check if fields are the same..
const ordinalFields = await this.props.layer.getOrdinalFields();
const eqls = _.isEqual(ordinalFields, this.state.ordinalFields);
if (!eqls) {
this.setState({
ordinalFields
});
}
}
_getStyleUpdateFunction = type => {
const { property } = this.props;
return options => {
const styleDescriptor = {
type,
options
};
this.props.handlePropertyChange(property, styleDescriptor);
_onStaticStyleChange = options => {
const styleDescriptor = {
type: VectorStyle.STYLE_TYPE.STATIC,
options
};
};
this.props.handlePropertyChange(this.props.property, styleDescriptor);
}
_onTypeToggle = (() => {
let lastOptions = {
// TODO: Move default to central location with other defaults
color: 'rgba(0,0,0,0.4)'
_onDynamicStyleChange = options => {
const styleDescriptor = {
type: VectorStyle.STYLE_TYPE.DYNAMIC,
options
};
const { DYNAMIC, STATIC } = VectorStyle.STYLE_TYPE;
return ({ target }, currentOptions) => {
const selectedStyleType = target.checked ? DYNAMIC : STATIC;
this.setState({
isDynamic: target.checked
}, () => {
if (!_.isEqual(lastOptions, currentOptions)) {
lastOptions && this._getStyleUpdateFunction(selectedStyleType)(lastOptions);
lastOptions = currentOptions;
}
});
};
})();
this.props.handlePropertyChange(this.props.property, styleDescriptor);
}
_renderStyleSelector(currentOptions) {
let styleSelector;
if (this.state.isDynamic) {
if (this.state.ordinalFields && this.state.ordinalFields.length) {
const DynamicSelector = this.props.DynamicSelector;
styleSelector = (
<DynamicSelector
fields={this.state.ordinalFields}
onChange={this._getStyleUpdateFunction(VectorStyle.STYLE_TYPE.DYNAMIC)}
selectedOptions={currentOptions}
/>
);
} else {
styleSelector = null;
}
_onTypeToggle = () => {
if (this._isDynamic()) {
// toggle to static style
this._onStaticStyleChange(this.prevOptions);
} else {
const StaticSelector = this.props.StaticSelector;
styleSelector = (
<StaticSelector
changeOptions={this._getStyleUpdateFunction(VectorStyle.STYLE_TYPE.STATIC)}
selectedOptions={currentOptions}
// toggle to dynamic style
this._onDynamicStyleChange(this.prevOptions);
}
this.prevOptions = this._getStyleOptions();
}
_renderStyleSelector() {
if (this._isDynamic()) {
const DynamicSelector = this.props.DynamicSelector;
return (
<DynamicSelector
fields={this.props.ordinalFields}
onChange={this._onDynamicStyleChange}
selectedOptions={this._getStyleOptions()}
/>
);
}
return styleSelector;
}
_renderStaticAndDynamicStyles = () => {
const currentOptions = _.get(this.props, 'styleDescriptor.options');
const StaticSelector = this.props.StaticSelector;
return (
<div>
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow={true}>
<EuiFormLabel style={{ marginBottom: 0 }}>
{this.props.name}
</EuiFormLabel>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiSwitch
label={'Dynamic'}
checked={this.state.isDynamic}
onChange={e => this._onTypeToggle(e, currentOptions)}
/>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />
{this._renderStyleSelector(currentOptions)}
</div>
<StaticSelector
changeOptions={this._onStaticStyleChange}
selectedOptions={this._getStyleOptions()}
/>
);
};
}
render() {
return this._renderStaticAndDynamicStyles();
const isDynamic = this._isDynamic();
const dynamicTooltipContent =
isDynamic ? "Disable dynamic styling." : "Enable dynamic styling.";
return (
<EuiFlexGroup gutterSize="s">
<EuiFlexItem className={isDynamic ? 'gisStaticDynamicSylingOption__dynamicSizeHack' : undefined}>
<EuiFormRow label={this.props.name && this.props.name}>
{this._renderStyleSelector()}
</EuiFormRow>
</EuiFlexItem>
{this._canBeDynamic() &&
<EuiFlexItem grow={false}>
<EuiFormRow hasEmptyLabelSpace>
<EuiToolTip
content={dynamicTooltipContent}
delay="long"
>
<EuiButtonToggle
label={dynamicTooltipContent}
iconType="link"
onChange={this._onTypeToggle}
isEmpty={!isDynamic}
fill={isDynamic}
isIconOnly
/>
</EuiToolTip>
</EuiFormRow>
</EuiFlexItem>
}
</EuiFlexGroup>
);
}
}

View file

@ -0,0 +1 @@
@import './static_color_selection';

View file

@ -0,0 +1,7 @@
.gisColorPicker {
padding-top: 9px;
.euiColorPickerPopUp {
z-index: $euiZLevel3;
}
}

View file

@ -6,7 +6,8 @@
import React from 'react';
import {
EuiColorPicker
EuiColorPicker,
EuiFormControlLayout
} from '@elastic/eui';
export class StaticColorSelection extends React.Component {
@ -18,10 +19,14 @@ export class StaticColorSelection extends React.Component {
});
};
return (<EuiColorPicker
onChange={onOptionChange}
color={this.props.selectedOptions ? this.props.selectedOptions.color : null}
className="colorPicker"
/>);
return (
<EuiFormControlLayout>
<EuiColorPicker
onChange={onOptionChange}
color={this.props.selectedOptions ? this.props.selectedOptions.color : null}
className="gisColorPicker euiFieldText"
/>
</EuiFormControlLayout>
);
}
}

View file

@ -10,23 +10,16 @@ import { StaticDynamicStyleSelector } from '../../static_dynamic_styling_option'
import { DynamicColorSelection } from './dynamic_color_selection';
import { StaticColorSelection } from './static_color_selection';
export class VectorStyleColorEditor extends React.Component {
constructor() {
super();
}
render() {
return (
<StaticDynamicStyleSelector
layer={this.props.layer}
property={this.props.styleProperty}
name={this.props.stylePropertyName}
styleDescriptor={this.props.styleDescriptor}
handlePropertyChange={this.props.handlePropertyChange}
DynamicSelector={DynamicColorSelection}
StaticSelector={StaticColorSelection}
/>
);
}
export function VectorStyleColorEditor(props) {
return (
<StaticDynamicStyleSelector
ordinalFields={props.ordinalFields}
property={props.styleProperty}
name={props.stylePropertyName}
styleDescriptor={props.styleDescriptor}
handlePropertyChange={props.handlePropertyChange}
DynamicSelector={DynamicColorSelection}
StaticSelector={StaticColorSelection}
/>
);
}

View file

@ -29,6 +29,12 @@ export class SizeRangeSelector extends React.Component {
return typeof this.props.minSize === 'number' && typeof this.props.maxSize === 'number';
}
componentDidMount() {
if (!this._areSizesValid()) {
this._onSizeChange(DEFAULT_MIN_SIZE, DEFAULT_MAX_SIZE);
}
}
componentDidUpdate() {
if (!this._areSizesValid()) {
this._onSizeChange(DEFAULT_MIN_SIZE, DEFAULT_MAX_SIZE);
@ -52,40 +58,38 @@ export class SizeRangeSelector extends React.Component {
};
return (
<EuiFormRow>
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
label="Min size"
compressed
>
<EuiRange
min={DEFAULT_MIN_SIZE}
max={DEFAULT_MAX_SIZE}
value={this.props.minSize.toString()}
onChange={onMinSizeChange}
showInput
showRange
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
label="Max size"
compressed
>
<EuiRange
min={DEFAULT_MIN_SIZE}
max={DEFAULT_MAX_SIZE}
value={this.props.maxSize.toString()}
onChange={onMaxSizeChange}
showInput
showRange
/>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFormRow>
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
label="Min size"
compressed
>
<EuiRange
min={DEFAULT_MIN_SIZE}
max={DEFAULT_MAX_SIZE}
value={this.props.minSize.toString()}
onChange={onMinSizeChange}
showInput
showRange
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
label="Max size"
compressed
>
<EuiRange
min={DEFAULT_MIN_SIZE}
max={DEFAULT_MAX_SIZE}
value={this.props.maxSize.toString()}
onChange={onMaxSizeChange}
showInput
showRange
/>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
);
}
}

View file

@ -13,23 +13,16 @@ import { StaticDynamicStyleSelector } from '../../static_dynamic_styling_option'
import { DynamicSizeSelection } from './dynamic_size_selection';
import { StaticSizeSelection } from './static_size_selection';
export class VectorStyleSizeEditor extends React.Component {
constructor() {
super();
this.state = {};
}
render() {
return (<StaticDynamicStyleSelector
layer={this.props.layer}
property={this.props.styleProperty}
name={this.props.stylePropertyName}
styleDescriptor={this.props.styleDescriptor}
handlePropertyChange={this.props.handlePropertyChange}
export function VectorStyleSizeEditor(props) {
return (
<StaticDynamicStyleSelector
ordinalFields={props.ordinalFields}
property={props.styleProperty}
name={props.stylePropertyName}
styleDescriptor={props.styleDescriptor}
handlePropertyChange={props.handlePropertyChange}
DynamicSelector={DynamicSizeSelection}
StaticSelector={StaticSizeSelection}
/>);
}
/>
);
}

View file

@ -0,0 +1,88 @@
/*
* 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 _ from 'lodash';
import React, { Component, Fragment } from 'react';
import { VectorStyleColorEditor } from './color/vector_style_color_editor';
import { VectorStyleSizeEditor } from './size/vector_style_size_editor';
import { EuiSpacer } from '@elastic/eui';
export class VectorStyleEditor extends Component {
state = {
ordinalFields: []
}
componentWillUnmount() {
this._isMounted = false;
}
componentDidMount() {
this._isMounted = true;
this._loadOrdinalFields();
}
componentDidUpdate() {
this._loadOrdinalFields();
}
async _loadOrdinalFields() {
const ordinalFields = await this.props.layer.getOrdinalFields();
if (!this._isMounted) {
return;
}
if (!_.isEqual(ordinalFields, this.state.ordinalFields)) {
this.setState({ ordinalFields });
}
}
render() {
return (
<Fragment>
<VectorStyleColorEditor
styleProperty="fillColor"
stylePropertyName="Fill color"
handlePropertyChange={this.props.handlePropertyChange}
styleDescriptor={this.props.styleProperties.fillColor}
ordinalFields={this.state.ordinalFields}
/>
<EuiSpacer size="m" />
<VectorStyleColorEditor
styleProperty="lineColor"
stylePropertyName="Border color"
handlePropertyChange={this.props.handlePropertyChange}
styleDescriptor={this.props.styleProperties.lineColor}
ordinalFields={this.state.ordinalFields}
/>
<EuiSpacer size="m" />
<VectorStyleSizeEditor
styleProperty="lineWidth"
stylePropertyName="Border width"
handlePropertyChange={this.props.handlePropertyChange}
styleDescriptor={this.props.styleProperties.lineWidth}
ordinalFields={this.state.ordinalFields}
/>
<EuiSpacer size="m" />
<VectorStyleSizeEditor
styleProperty="iconSize"
stylePropertyName="Symbol size"
handlePropertyChange={this.props.handlePropertyChange}
styleDescriptor={this.props.styleProperties.iconSize}
ordinalFields={this.state.ordinalFields}
/>
</Fragment>
);
}
}

View file

@ -4,18 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { Fragment } from 'react';
import { VectorStyleColorEditor } from './components/vector/color/vector_style_color_editor';
import { VectorStyleSizeEditor } from './components/vector/size/vector_style_size_editor';
import _ from 'lodash';
import React from 'react';
import {
EuiFlexGroup,
EuiFlexItem
} from '@elastic/eui';
import { FillableCircle, FillableVector } from '../../icons/additional_layer_icons';
import { ColorGradient } from '../../icons/color_gradient';
import { getHexColorRangeStrings } from '../../utils/color_utils';
import _ from 'lodash';
import { VectorStyleEditor } from './components/vector/vector_style_editor';
export class VectorStyle {
@ -45,56 +40,23 @@ export class VectorStyle {
return 'Vector style';
}
static description = 'Link styles to property values for data driven styling.';
static renderEditor({ handleStyleChange, style, layer }) {
const properties = { ...style.getProperties() };
const styleProperties = { ...style.getProperties() };
const handlePropertyChange = (propertyName, settings) => {
properties[propertyName] = settings;//override single property, but preserve the rest
const vectorStyleDescriptor = VectorStyle.createDescriptor(properties);
styleProperties[propertyName] = settings;//override single property, but preserve the rest
const vectorStyleDescriptor = VectorStyle.createDescriptor(styleProperties);
handleStyleChange(vectorStyleDescriptor);
};
return (
<Fragment>
<EuiFlexGroup direction="column">
<EuiFlexItem>
<VectorStyleColorEditor
styleProperty={'fillColor'}
stylePropertyName={"Fill color"}
handlePropertyChange={handlePropertyChange}
styleDescriptor={properties.fillColor}
layer={layer}
/>
</EuiFlexItem>
<EuiFlexItem>
<VectorStyleColorEditor
styleProperty={'lineColor'}
stylePropertyName={"Line color"}
handlePropertyChange={handlePropertyChange}
styleDescriptor={properties.lineColor}
layer={layer}
/>
</EuiFlexItem>
<EuiFlexItem>
<VectorStyleSizeEditor
styleProperty={'lineWidth'}
stylePropertyName={"Line width"}
handlePropertyChange={handlePropertyChange}
styleDescriptor={properties.lineWidth}
layer={layer}
/>
</EuiFlexItem>
<EuiFlexItem>
<VectorStyleSizeEditor
styleProperty={'iconSize'}
stylePropertyName={"Icon size"}
handlePropertyChange={handlePropertyChange}
styleDescriptor={properties.iconSize}
layer={layer}
/>
</EuiFlexItem>
</EuiFlexGroup>
</Fragment>
<VectorStyleEditor
handlePropertyChange={handlePropertyChange}
styleProperties={styleProperties}
layer={layer}
/>
);
}