UNPKG

kepler.gl

Version:

kepler.gl is a webgl based application to visualize large scale location data in the browser

237 lines (215 loc) 6.57 kB
// Copyright (c) 2021 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import React, {useState} from 'react'; import styled from 'styled-components'; import LayerHoverInfoFactory from './layer-hover-info'; import CoordinateInfoFactory from './coordinate-info'; import {ArrowLeft, ArrowRight, Pin} from 'components/common/icons'; import {injectIntl} from 'react-intl'; import {FormattedMessage} from 'localization'; import Tippy from '@tippyjs/react/headless'; const MAX_WIDTH = 500; const MAX_HEIGHT = 600; const StyledMapPopover = styled.div` display: flex; flex-direction: column; max-width: ${MAX_WIDTH}px; max-height: ${MAX_HEIGHT}px; padding: 14px; & > * + * { margin-top: 6px; } ${props => props.theme.scrollBar}; font-family: ${props => props.theme.fontFamily}; font-size: 11px; font-weight: 500; background-color: ${props => props.theme.panelBackground}; color: ${props => props.theme.textColor}; z-index: 1000; overflow-x: auto; box-shadow: ${props => props.theme.panelBoxShadow}; :hover { background-color: ${props => `${props.theme.panelBackground}dd`}; } .primary-label { color: ${props => props.theme.notificationColors.success}; font-size: 10px; } .map-popover__layer-info, .coordingate-hover-info { & > * + * { margin-top: 7px; } } table { width: auto; display: grid; border-collapse: collapse; row-gap: 5px; column-gap: 5px; } .coordingate-hover-info > table { grid-template-columns: auto auto auto; } .map-popover__layer-info > table { grid-template-columns: auto auto; } tbody, tr { display: contents; } td { border-color: transparent; color: ${props => props.theme.textColor}; } td.row__value { text-align: right; font-weight: 500; color: ${props => props.theme.textColorHl}; } `; const PinnedButtons = styled.div` display: flex; align-self: center; align-items: center; justify-items: center; & > * + * { margin-left: 10px; } `; const PopoverContent = styled.div` display: flex; flex-direction: column; & > * + * { margin-top: 12px; } `; const StyledIcon = styled.div` color: ${props => props.theme.activeColor}; &.popover-pin { transform: rotate(30deg); } :hover { cursor: pointer; color: ${props => props.theme.linkBtnColor}; } `; MapPopoverFactory.deps = [LayerHoverInfoFactory, CoordinateInfoFactory]; function createVirtualReference(container, x, y, size = 0) { const bounds = container && container.getBoundingClientRect ? container.getBoundingClientRect() : {}; const left = (bounds.left || 0) + x - size / 2; const top = (bounds.top || 0) + y - size / 2; return { left, top, right: left + size, bottom: top + size, width: size, height: size }; } function getOffsetForPlacement({placement, reference, popper}, gap = 20) { switch (placement) { case 'top-start': case 'bottom-start': return [gap, gap]; case 'top-end': case 'bottom-end': return [-gap, gap]; default: return [0, 0]; } } function getPopperOptions(container) { return { modifiers: [ { name: 'preventOverflow', options: { boundary: container } } ] }; } export default function MapPopoverFactory(LayerHoverInfo, CoordinateInfo) { /** @type {typeof import('./map-popover').MapPopover} */ const MapPopover = ({ x, y, frozen, coordinate, layerHoverProp, isBase, zoom, container, onClose }) => { const [horizontalPlacement, setHorizontalPlacement] = useState('start'); const moveLeft = () => setHorizontalPlacement('end'); const moveRight = () => setHorizontalPlacement('start'); return ( <Tippy popperOptions={getPopperOptions(container)} zIndex={999} /* should be below Modal which has zIndex=1000 */ visible={true} interactive={true} getReferenceClientRect={() => createVirtualReference(container, x, y)} // @ts-ignore placement={`bottom-${horizontalPlacement}`} // @ts-ignore offset={getOffsetForPlacement} appendTo={document.body} render={attrs => ( <StyledMapPopover {...attrs} className="map-popover"> {frozen ? ( <PinnedButtons> {horizontalPlacement === 'start' && ( <StyledIcon className="popover-arrow-left" onClick={moveLeft}> <ArrowLeft /> </StyledIcon> )} <StyledIcon className="popover-pin" onClick={onClose}> <Pin height="16px" /> </StyledIcon> {horizontalPlacement === 'end' && ( <StyledIcon className="popover-arrow-right" onClick={moveRight}> <ArrowRight /> </StyledIcon> )} {isBase && ( <div className="primary-label"> <FormattedMessage id="mapPopover.primary" /> </div> )} </PinnedButtons> ) : null} <PopoverContent> {Array.isArray(coordinate) && <CoordinateInfo coordinate={coordinate} zoom={zoom} />} {layerHoverProp && <LayerHoverInfo {...layerHoverProp} />} </PopoverContent> </StyledMapPopover> )} /> ); }; return injectIntl(MapPopover); }