mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
MapScaleControlStyles in emotion (#221156)
Part of #207852 Before: https://github.com/user-attachments/assets/5e69db7f-2437-4166-b52e-72c16fd950d8 After: https://github.com/user-attachments/assets/86a7b6d8-b8fd-4d0b-9283-fe77fa0d8f98 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
43431d55e4
commit
5f85a5dee5
5 changed files with 266 additions and 119 deletions
|
@ -4,4 +4,3 @@
|
|||
@import 'toolbar_overlay/index';
|
||||
@import 'mb_map/keydown_scroll_zoom/index';
|
||||
@import 'mb_map/tooltip_control/features_tooltip/index';
|
||||
@import 'mb_map/scale_control/index';
|
||||
|
|
|
@ -1,27 +1,206 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`isFullScreen 1`] = `
|
||||
<div
|
||||
className="mapScaleControl mapScaleControlFullScreen"
|
||||
style={
|
||||
<ScaleControl
|
||||
isFullScreen={true}
|
||||
mbMap={
|
||||
Object {
|
||||
"width": "62.7474245034397px",
|
||||
"getBounds": [Function],
|
||||
"getContainer": [Function],
|
||||
"getZoom": [Function],
|
||||
"off": [Function],
|
||||
"on": [Function],
|
||||
"unproject": [Function],
|
||||
}
|
||||
}
|
||||
>
|
||||
~50 km
|
||||
</div>
|
||||
<div
|
||||
css="unknown styles"
|
||||
style={
|
||||
Object {
|
||||
"width": "62.7474245034397px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Insertion
|
||||
cache={
|
||||
Object {
|
||||
"insert": [Function],
|
||||
"inserted": Object {
|
||||
"1hok13p-mapScaleControlStyles-ScaleControl": true,
|
||||
"5ojysv-mapScaleControlStyles-mapScaleControlFullScreenStyles-ScaleControl": true,
|
||||
},
|
||||
"key": "css",
|
||||
"nonce": undefined,
|
||||
"registered": Object {},
|
||||
"sheet": StyleSheet {
|
||||
"_alreadyInsertedOrderInsensitiveRule": true,
|
||||
"_insertTag": [Function],
|
||||
"before": null,
|
||||
"container": <head>
|
||||
<style
|
||||
data-emotion="css"
|
||||
data-s=""
|
||||
>
|
||||
|
||||
.css-721pd1-floatTopRight{position:absolute;top:-8px;right:-8px;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
data-s=""
|
||||
>
|
||||
|
||||
.css-1hok13p-mapScaleControlStyles-ScaleControl{position:absolute;z-index:1000;left:12px;bottom:12px;pointer-events:none;color:#1D2A3E;border-left:2px solid #1D2A3E99;border-bottom:2px solid #1D2A3E99;text-align:right;background-color:#F6F9FC;padding-left:4px;padding-right:4px;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
data-s=""
|
||||
>
|
||||
|
||||
.css-5ojysv-mapScaleControlStyles-mapScaleControlFullScreenStyles-ScaleControl{position:absolute;z-index:1000;left:12px;bottom:12px;pointer-events:none;color:#1D2A3E;border-left:2px solid #1D2A3E99;border-bottom:2px solid #1D2A3E99;text-align:right;background-color:#F6F9FC;padding-left:4px;padding-right:4px;bottom:calc(24px * 2);}
|
||||
</style>
|
||||
</head>,
|
||||
"ctr": 2,
|
||||
"insertionPoint": undefined,
|
||||
"isSpeedy": false,
|
||||
"key": "css",
|
||||
"nonce": undefined,
|
||||
"prepend": undefined,
|
||||
"tags": Array [
|
||||
<style
|
||||
data-emotion="css"
|
||||
data-s=""
|
||||
>
|
||||
|
||||
.css-1hok13p-mapScaleControlStyles-ScaleControl{position:absolute;z-index:1000;left:12px;bottom:12px;pointer-events:none;color:#1D2A3E;border-left:2px solid #1D2A3E99;border-bottom:2px solid #1D2A3E99;text-align:right;background-color:#F6F9FC;padding-left:4px;padding-right:4px;}
|
||||
</style>,
|
||||
<style
|
||||
data-emotion="css"
|
||||
data-s=""
|
||||
>
|
||||
|
||||
.css-5ojysv-mapScaleControlStyles-mapScaleControlFullScreenStyles-ScaleControl{position:absolute;z-index:1000;left:12px;bottom:12px;pointer-events:none;color:#1D2A3E;border-left:2px solid #1D2A3E99;border-bottom:2px solid #1D2A3E99;text-align:right;background-color:#F6F9FC;padding-left:4px;padding-right:4px;bottom:calc(24px * 2);}
|
||||
</style>,
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
isStringTag={true}
|
||||
serialized={
|
||||
Object {
|
||||
"map": undefined,
|
||||
"name": "5ojysv-mapScaleControlStyles-mapScaleControlFullScreenStyles-ScaleControl",
|
||||
"next": undefined,
|
||||
"styles": "position:absolute;z-index:1000;left:12px;bottom:12px;pointer-events:none;color:#1D2A3E;border-left:2px solid #1D2A3E99;border-bottom:2px solid #1D2A3E99;text-align:right;background-color:#F6F9FC;padding-left:4px;padding-right:4px;;label:mapScaleControlStyles;;;bottom:calc(24px * 2);;label:mapScaleControlFullScreenStyles;;;;label:ScaleControl;;",
|
||||
"toString": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="css-5ojysv-mapScaleControlStyles-mapScaleControlFullScreenStyles-ScaleControl"
|
||||
style={
|
||||
Object {
|
||||
"width": "62.7474245034397px",
|
||||
}
|
||||
}
|
||||
>
|
||||
~50 km
|
||||
</div>
|
||||
</div>
|
||||
</ScaleControl>
|
||||
`;
|
||||
|
||||
exports[`render 1`] = `
|
||||
<div
|
||||
className="mapScaleControl"
|
||||
style={
|
||||
<ScaleControl
|
||||
isFullScreen={false}
|
||||
mbMap={
|
||||
Object {
|
||||
"width": "62.7474245034397px",
|
||||
"getBounds": [Function],
|
||||
"getContainer": [Function],
|
||||
"getZoom": [Function],
|
||||
"off": [Function],
|
||||
"on": [Function],
|
||||
"unproject": [Function],
|
||||
}
|
||||
}
|
||||
>
|
||||
~50 km
|
||||
</div>
|
||||
<div
|
||||
css="unknown styles"
|
||||
style={
|
||||
Object {
|
||||
"width": "62.7474245034397px",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Insertion
|
||||
cache={
|
||||
Object {
|
||||
"insert": [Function],
|
||||
"inserted": Object {
|
||||
"1hok13p-mapScaleControlStyles-ScaleControl": true,
|
||||
},
|
||||
"key": "css",
|
||||
"nonce": undefined,
|
||||
"registered": Object {},
|
||||
"sheet": StyleSheet {
|
||||
"_alreadyInsertedOrderInsensitiveRule": true,
|
||||
"_insertTag": [Function],
|
||||
"before": null,
|
||||
"container": <head>
|
||||
<style
|
||||
data-emotion="css"
|
||||
data-s=""
|
||||
>
|
||||
|
||||
.css-721pd1-floatTopRight{position:absolute;top:-8px;right:-8px;}
|
||||
</style>
|
||||
<style
|
||||
data-emotion="css"
|
||||
data-s=""
|
||||
>
|
||||
|
||||
.css-1hok13p-mapScaleControlStyles-ScaleControl{position:absolute;z-index:1000;left:12px;bottom:12px;pointer-events:none;color:#1D2A3E;border-left:2px solid #1D2A3E99;border-bottom:2px solid #1D2A3E99;text-align:right;background-color:#F6F9FC;padding-left:4px;padding-right:4px;}
|
||||
</style>
|
||||
</head>,
|
||||
"ctr": 1,
|
||||
"insertionPoint": undefined,
|
||||
"isSpeedy": false,
|
||||
"key": "css",
|
||||
"nonce": undefined,
|
||||
"prepend": undefined,
|
||||
"tags": Array [
|
||||
<style
|
||||
data-emotion="css"
|
||||
data-s=""
|
||||
>
|
||||
|
||||
.css-1hok13p-mapScaleControlStyles-ScaleControl{position:absolute;z-index:1000;left:12px;bottom:12px;pointer-events:none;color:#1D2A3E;border-left:2px solid #1D2A3E99;border-bottom:2px solid #1D2A3E99;text-align:right;background-color:#F6F9FC;padding-left:4px;padding-right:4px;}
|
||||
</style>,
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
isStringTag={true}
|
||||
serialized={
|
||||
Object {
|
||||
"map": undefined,
|
||||
"name": "1hok13p-mapScaleControlStyles-ScaleControl",
|
||||
"next": undefined,
|
||||
"styles": "position:absolute;z-index:1000;left:12px;bottom:12px;pointer-events:none;color:#1D2A3E;border-left:2px solid #1D2A3E99;border-bottom:2px solid #1D2A3E99;text-align:right;background-color:#F6F9FC;padding-left:4px;padding-right:4px;;label:mapScaleControlStyles;;;;;label:ScaleControl;;",
|
||||
"toString": [Function],
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="css-1hok13p-mapScaleControlStyles-ScaleControl"
|
||||
style={
|
||||
Object {
|
||||
"width": "62.7474245034397px",
|
||||
}
|
||||
}
|
||||
>
|
||||
~50 km
|
||||
</div>
|
||||
</div>
|
||||
</ScaleControl>
|
||||
`;
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
.mapScaleControl {
|
||||
position: absolute;
|
||||
z-index: $euiZLevel1;
|
||||
left: $euiSizeM;
|
||||
bottom: $euiSizeM;
|
||||
pointer-events: none;
|
||||
color: $euiTextColor;
|
||||
border-left: 2px solid rgba($euiTextColor, .6);
|
||||
border-bottom: 2px solid rgba($euiTextColor, .6);
|
||||
text-align: right;
|
||||
@include mapOverlayIsTextOnly;
|
||||
}
|
||||
|
||||
.mapScaleControlFullScreen {
|
||||
bottom: $euiSizeL * 2;
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { mount, shallow } from 'enzyme';
|
||||
import { mount } from 'enzyme';
|
||||
import { ScaleControl } from './scale_control';
|
||||
import type { LngLat, LngLatBounds, Map as MapboxMap, PointLike } from '@kbn/mapbox-gl';
|
||||
|
||||
|
@ -49,12 +49,12 @@ const mockMBMap = {
|
|||
} as unknown as MapboxMap;
|
||||
|
||||
test('render', () => {
|
||||
const component = shallow(<ScaleControl mbMap={mockMBMap} isFullScreen={false} />);
|
||||
const component = mount(<ScaleControl mbMap={mockMBMap} isFullScreen={false} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('isFullScreen', () => {
|
||||
const component = shallow(<ScaleControl mbMap={mockMBMap} isFullScreen={true} />);
|
||||
const component = mount(<ScaleControl mbMap={mockMBMap} isFullScreen={true} />);
|
||||
expect(component).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
|
|
@ -6,9 +6,12 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import classNames from 'classnames';
|
||||
import React, { Component } from 'react';
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import { UseEuiTheme } from '@elastic/eui';
|
||||
import type { Map as MapboxMap } from '@kbn/mapbox-gl';
|
||||
import { css } from '@emotion/react';
|
||||
import { useMemoizedStyles } from '@kbn/core/public';
|
||||
|
||||
const MAX_WIDTH = 110;
|
||||
|
||||
interface Props {
|
||||
|
@ -16,107 +19,89 @@ interface Props {
|
|||
mbMap: MapboxMap;
|
||||
}
|
||||
|
||||
interface State {
|
||||
label: string;
|
||||
width: number;
|
||||
}
|
||||
|
||||
function getScaleDistance(value: number) {
|
||||
const getScaleDistance = (value: number) => {
|
||||
const orderOfMagnitude = Math.floor(Math.log10(value));
|
||||
const pow10 = Math.pow(10, orderOfMagnitude);
|
||||
|
||||
// reduce value to single order of magnitude to making rounding simple regardless of order of magnitude
|
||||
const distance = value / pow10;
|
||||
|
||||
if (distance < 1) {
|
||||
return pow10 * (Math.round(distance * 10) / 10);
|
||||
}
|
||||
|
||||
if (distance < 1) return pow10 * (Math.round(distance * 10) / 10);
|
||||
// provide easy to multiple round numbers for scale distance so its easy to measure distances longer then the scale
|
||||
if (distance >= 10) {
|
||||
return 10 * pow10;
|
||||
}
|
||||
|
||||
if (distance >= 5) {
|
||||
return 5 * pow10;
|
||||
}
|
||||
|
||||
if (distance >= 3) {
|
||||
return 3 * pow10;
|
||||
}
|
||||
if (distance >= 10) return 10 * pow10;
|
||||
if (distance >= 5) return 5 * pow10;
|
||||
if (distance >= 3) return 3 * pow10;
|
||||
|
||||
return Math.floor(distance) * pow10;
|
||||
}
|
||||
};
|
||||
|
||||
export class ScaleControl extends Component<Props, State> {
|
||||
private _isMounted: boolean = false;
|
||||
const componentStyles = {
|
||||
mapScaleControlStyles: ({ euiTheme }: UseEuiTheme) =>
|
||||
css({
|
||||
position: 'absolute',
|
||||
zIndex: euiTheme.levels.header,
|
||||
left: euiTheme.size.m,
|
||||
bottom: euiTheme.size.m,
|
||||
pointerEvents: 'none',
|
||||
color: euiTheme.colors.textParagraph,
|
||||
borderLeft: `2px solid ${euiTheme.colors.textParagraph}99`,
|
||||
borderBottom: `2px solid ${euiTheme.colors.textParagraph}99`,
|
||||
textAlign: 'right',
|
||||
backgroundColor: euiTheme.colors.backgroundBaseSubdued,
|
||||
paddingLeft: euiTheme.size.xs,
|
||||
paddingRight: euiTheme.size.xs,
|
||||
}),
|
||||
mapScaleControlFullScreenStyles: ({ euiTheme }: UseEuiTheme) =>
|
||||
css({
|
||||
bottom: `calc(${euiTheme.size.l} * 2)`,
|
||||
}),
|
||||
};
|
||||
|
||||
state: State = { label: '', width: 0 };
|
||||
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
this.props.mbMap.on('move', this._onUpdate);
|
||||
this._onUpdate();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
this.props.mbMap.off('move', this._onUpdate);
|
||||
}
|
||||
|
||||
_onUpdate = () => {
|
||||
if (!this._isMounted) {
|
||||
return;
|
||||
}
|
||||
const centerHeight = this.props.mbMap.getContainer().clientHeight / 2;
|
||||
const leftLatLon = this.props.mbMap.unproject([0, centerHeight]);
|
||||
const rightLatLon = this.props.mbMap.unproject([MAX_WIDTH, centerHeight]);
|
||||
export const ScaleControl: React.FC<Props> = ({ isFullScreen, mbMap }) => {
|
||||
const [label, setLabel] = useState('');
|
||||
const [width, setWidth] = useState(0);
|
||||
const styles = useMemoizedStyles(componentStyles);
|
||||
const onUpdate = useCallback(() => {
|
||||
const container = mbMap.getContainer();
|
||||
const centerHeight = container.clientHeight / 2;
|
||||
const leftLatLon = mbMap.unproject([0, centerHeight]);
|
||||
const rightLatLon = mbMap.unproject([MAX_WIDTH, centerHeight]);
|
||||
const maxDistanceMeters = leftLatLon.distanceTo(rightLatLon);
|
||||
if (maxDistanceMeters >= 1000) {
|
||||
this._setScale(
|
||||
maxDistanceMeters / 1000,
|
||||
i18n.translate('xpack.maps.kilometersAbbr', {
|
||||
defaultMessage: 'km',
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this._setScale(
|
||||
maxDistanceMeters,
|
||||
i18n.translate('xpack.maps.metersAbbr', {
|
||||
defaultMessage: 'm',
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
_setScale(maxDistance: number, unit: string) {
|
||||
const isKm = maxDistanceMeters >= 1000;
|
||||
const unit = isKm
|
||||
? i18n.translate('xpack.maps.kilometersAbbr', { defaultMessage: 'km' })
|
||||
: i18n.translate('xpack.maps.metersAbbr', { defaultMessage: 'm' });
|
||||
const maxDistance = isKm ? maxDistanceMeters / 1000 : maxDistanceMeters;
|
||||
const scaleDistance = getScaleDistance(maxDistance);
|
||||
const zoom = this.props.mbMap.getZoom();
|
||||
const bounds = this.props.mbMap.getBounds();
|
||||
let label = `${scaleDistance} ${unit}`;
|
||||
|
||||
const zoom = mbMap.getZoom();
|
||||
const bounds = mbMap.getBounds();
|
||||
let nextLabel = `${scaleDistance} ${unit}`;
|
||||
if (
|
||||
zoom <= 4 ||
|
||||
(zoom <= 6 && (bounds.getNorth() > 23.5 || bounds.getSouth() < -23.5)) ||
|
||||
(zoom <= 8 && (bounds.getNorth() > 45 || bounds.getSouth() < -45))
|
||||
) {
|
||||
label = '~' + label;
|
||||
nextLabel = `~${nextLabel}`;
|
||||
}
|
||||
this.setState({
|
||||
width: MAX_WIDTH * (scaleDistance / maxDistance),
|
||||
label,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className={classNames('mapScaleControl', {
|
||||
mapScaleControlFullScreen: this.props.isFullScreen,
|
||||
})}
|
||||
style={{ width: `${this.state.width}px` }}
|
||||
>
|
||||
{this.state.label}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
setLabel(nextLabel);
|
||||
setWidth(MAX_WIDTH * (scaleDistance / maxDistance));
|
||||
}, [mbMap]);
|
||||
|
||||
useEffect(() => {
|
||||
mbMap.on('move', onUpdate);
|
||||
onUpdate();
|
||||
return () => {
|
||||
mbMap.off('move', onUpdate);
|
||||
};
|
||||
}, [mbMap, onUpdate]);
|
||||
|
||||
return (
|
||||
<div
|
||||
css={[styles.mapScaleControlStyles, isFullScreen && styles.mapScaleControlFullScreenStyles]}
|
||||
style={{ width: `${width}px` }}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue