[Maps] Add attribution handling for TMS (config & UI-entry) and WMS (#44147)

* Add attribution for tms from config

* Add attribution entry fields & logic for tms from URL

* Set text and url fields invalid if both aren't present

* Don't return attribution unless both text and url defined

* Add wms inputs & handling

* Debounce input to prevent constantly generating new layers on keypress

* Remove unapplicable placeholder text on WMS attribution inputs
This commit is contained in:
Aaron Caldwell 2019-08-28 19:12:19 -06:00 committed by GitHub
parent eb9f54d216
commit d42bc7d9ae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 214 additions and 36 deletions

View file

@ -118,17 +118,7 @@ export class EMSTMSSource extends AbstractTMSSource {
if (!markdown) {
return [];
}
return markdown.split('|').map((attribution) => {
attribution = attribution.trim();
//this assumes attribution is plain markdown link
const extractLink = /\[(.*)\]\((.*)\)/;
const result = extractLink.exec(attribution);
return {
label: result ? result[1] : null,
url: result ? result[2] : null
};
});
return this.convertMarkdownLinkToObjectArr(markdown);
}
async getUrlTemplate() {

View file

@ -10,6 +10,7 @@ import { CreateSourceEditor } from './create_source_editor';
import { getKibanaTileMap } from '../../../meta';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../common/i18n_getters';
import _ from 'lodash';
export class KibanaTilemapSource extends AbstractTMSSource {
@ -77,6 +78,13 @@ export class KibanaTilemapSource extends AbstractTMSSource {
return tilemap.url;
}
async getAttributions() {
const tilemap = getKibanaTileMap();
const markdown = _.get(tilemap, 'options.attribution', '');
const objArr = this.convertMarkdownLinkToObjectArr(markdown);
return objArr;
}
async getDisplayName() {
try {
return await this.getUrlTemplate();

View file

@ -11,4 +11,18 @@ export class AbstractTMSSource extends AbstractSource {
async getUrlTemplate() {
throw new Error('Should implement TMSSource#getUrlTemplate');
}
convertMarkdownLinkToObjectArr(markdown) {
return markdown.split('|').map((attribution) => {
attribution = attribution.trim();
//this assumes attribution is plain markdown link
const extractLink = /\[(.*)\]\((.*)\)/;
const result = extractLink.exec(attribution);
return {
label: result ? result[1] : null,
url: result ? result[2] : null
};
});
}
}

View file

@ -17,6 +17,7 @@ import {
EuiSpacer,
} from '@elastic/eui';
import { WmsClient } from './wms_client';
import _ from 'lodash';
const LAYERS_LABEL = i18n.translate('xpack.maps.source.wms.layersLabel', {
defaultMessage: 'Layers'
@ -38,8 +39,9 @@ export class WMSCreateSourceEditor extends Component {
styleOptions: [],
selectedLayerOptions: [],
selectedStyleOptions: [],
attributionText: '',
attributionUrl: '',
}
componentDidMount() {
this._isMounted = true;
}
@ -48,18 +50,26 @@ export class WMSCreateSourceEditor extends Component {
this._isMounted = false;
}
_previewIfPossible() {
_previewIfPossible = _.debounce(() => {
const {
serviceUrl,
layers,
styles
styles,
attributionText,
attributionUrl,
} = this.state;
const sourceConfig = (serviceUrl && layers)
? { serviceUrl, layers, styles }
? {
serviceUrl,
layers,
styles,
attributionText,
attributionUrl,
}
: null;
this.props.onSourceConfigChange(sourceConfig);
}
}, 2000);
_loadCapabilities = async () => {
if (!this.state.serviceUrl) {
@ -137,6 +147,18 @@ export class WMSCreateSourceEditor extends Component {
}, this._previewIfPossible);
}
_handleWMSAttributionChange(attributionUpdate) {
const {
attributionText,
attributionUrl,
} = this.state;
this.setState(attributionUpdate, () => {
if (attributionText && attributionUrl) {
this._previewIfPossible();
}
});
}
_renderLayerAndStyleInputs() {
if (!this.state.hasAttemptedToLoadCapabilities || this.state.isLoadingCapabilities) {
return null;
@ -226,6 +248,54 @@ export class WMSCreateSourceEditor extends Component {
);
}
_renderAttributionInputs() {
if (!this.state.layers) {
return;
}
const {
attributionText,
attributionUrl,
} = this.state;
return (
<Fragment>
<EuiFormRow
label="Attribution text"
isInvalid={attributionUrl !== '' && attributionText === ''}
error={[
i18n.translate('xpack.maps.source.wms.attributionText', {
defaultMessage:
'Attribution url must have accompanying text',
})
]}
>
<EuiFieldText
onChange={({ target }) =>
this._handleWMSAttributionChange({ attributionText: target.value })
}
/>
</EuiFormRow>
<EuiFormRow
label="Attribution link"
isInvalid={attributionText !== '' && attributionUrl === ''}
error={[
i18n.translate('xpack.maps.source.wms.attributionLink', {
defaultMessage:
'Attribution text must have an accompanying link',
})
]}
>
<EuiFieldText
onChange={({ target }) =>
this._handleWMSAttributionChange({ attributionUrl: target.value })
}
/>
</EuiFormRow>
</Fragment>
);
}
render() {
return (
<EuiForm>
@ -244,6 +314,8 @@ export class WMSCreateSourceEditor extends Component {
{this._renderLayerAndStyleInputs()}
{this._renderAttributionInputs()}
</EuiForm>
);
}

View file

@ -24,12 +24,14 @@ export class WMSSource extends AbstractTMSSource {
});
static icon = 'grid';
static createDescriptor({ serviceUrl, layers, styles }) {
static createDescriptor({ serviceUrl, layers, styles, attributionText, attributionUrl }) {
return {
type: WMSSource.type,
serviceUrl: serviceUrl,
layers: layers,
styles: styles
serviceUrl,
layers,
styles,
attributionText,
attributionUrl
};
}
@ -84,6 +86,18 @@ export class WMSSource extends AbstractTMSSource {
return this._descriptor.serviceUrl;
}
getAttributions() {
const { attributionText, attributionUrl } = this._descriptor;
const attributionComplete = !!attributionText && !!attributionUrl;
return attributionComplete
? [{
url: attributionUrl,
label: attributionText
}]
: [];
}
getUrlTemplate() {
const client = new WmsClient({ serviceUrl: this._descriptor.serviceUrl });
return client.getUrlTemplate(this._descriptor.layers, this._descriptor.styles || '');

View file

@ -4,8 +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 {
EuiFieldText,
EuiFormRow,
@ -15,6 +14,7 @@ import { AbstractTMSSource } from './tms_source';
import { TileLayer } from '../tile_layer';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel, getUrlLabel } from '../../../common/i18n_getters';
import _ from 'lodash';
export class XYZTMSSource extends AbstractTMSSource {
@ -27,10 +27,12 @@ export class XYZTMSSource extends AbstractTMSSource {
});
static icon = 'grid';
static createDescriptor({ urlTemplate }) {
static createDescriptor({ urlTemplate, attributionText, attributionUrl }) {
return {
type: XYZTMSSource.type,
urlTemplate
urlTemplate,
attributionText,
attributionUrl
};
}
@ -68,40 +70,118 @@ export class XYZTMSSource extends AbstractTMSSource {
return this._descriptor.urlTemplate;
}
getAttributions() {
const { attributionText, attributionUrl } = this._descriptor;
const attributionComplete = !!attributionText && !!attributionUrl;
return attributionComplete
? [{
url: attributionUrl,
label: attributionText
}]
: [];
}
getUrlTemplate() {
return this._descriptor.urlTemplate;
}
}
class XYZTMSEditor extends React.Component {
class XYZTMSEditor extends React.Component {
state = {
tmsInput: '',
tmsCanPreview: false
tmsCanPreview: false,
attributionText: '',
attributionUrl: '',
}
_sourceConfigChange = _.debounce(updatedSourceConfig => {
if (this.state.tmsCanPreview) {
this.props.onSourceConfigChange(updatedSourceConfig);
}
}, 2000);
_handleTMSInputChange(e) {
const url = e.target.value;
const canPreview = (url.indexOf('{x}') >= 0 && url.indexOf('{y}') >= 0 && url.indexOf('{z}') >= 0);
this.setState({
tmsInput: url,
tmsCanPreview: canPreview
});
}, () => this._sourceConfigChange({ urlTemplate: url }));
}
if (canPreview) {
this.props.onSourceConfigChange({ urlTemplate: url });
}
_handleTMSAttributionChange(attributionUpdate) {
this.setState(attributionUpdate, () => {
const {
attributionText,
attributionUrl,
tmsInput,
} = this.state;
if (tmsInput && attributionText && attributionUrl) {
this._sourceConfigChange({
urlTemplate: tmsInput,
attributionText,
attributionUrl
});
}
});
}
render() {
const {
attributionText,
attributionUrl,
} = this.state;
return (
<EuiFormRow label="Url">
<EuiFieldText
placeholder={'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png'}
onChange={(e) => this._handleTMSInputChange(e)}
/>
</EuiFormRow>
<Fragment>
<EuiFormRow
label="Url"
>
<EuiFieldText
placeholder={'https://a.tile.openstreetmap.org/{z}/{x}/{y}.png'}
onChange={e => this._handleTMSInputChange(e)}
/>
</EuiFormRow>
<EuiFormRow
label="Attribution text"
isInvalid={attributionUrl !== '' && attributionText === ''}
error={[
i18n.translate('xpack.maps.xyztmssource.attributionText', {
defaultMessage:
'Attribution url must have accompanying text',
})
]}
>
<EuiFieldText
placeholder={'© OpenStreetMap contributors'}
onChange={({ target }) =>
this._handleTMSAttributionChange({ attributionText: target.value })
}
/>
</EuiFormRow>
<EuiFormRow
label="Attribution link"
isInvalid={attributionText !== '' && attributionUrl === ''}
error={[
i18n.translate('xpack.maps.xyztmssource.attributionLink', {
defaultMessage:
'Attribution text must have an accompanying link',
})
]}
>
<EuiFieldText
placeholder={'https://www.openstreetmap.org/copyright'}
onChange={({ target }) =>
this._handleTMSAttributionChange({ attributionUrl: target.value })
}
/>
</EuiFormRow>
</Fragment>
);
}
}