/* eslint-disable react/jsx-sort-default-props */ /* eslint-disable react/sort-prop-types */ /* eslint-disable react/jsx-handler-names */ /* eslint-disable react/forbid-prop-types */ /** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import { forwardRef, memo, ReactNode, MouseEvent, useCallback, useEffect, useImperativeHandle, useState, isValidElement, useRef, } from 'react'; import { isEqual } from 'lodash'; import { StaticMap } from 'react-map-gl'; import DeckGL from '@deck.gl/react'; import type { Layer } from '@deck.gl/core'; import { JsonObject, JsonValue, usePrevious } from '@superset-ui/core'; import { styled } from '@apache-superset/core/ui'; import { Device } from '@luma.gl/core'; import Tooltip, { TooltipProps } from './components/Tooltip'; import 'mapbox-gl/dist/mapbox-gl.css'; import { Viewport } from './utils/fitViewport'; import { MAPBOX_LAYER_PREFIX, OSM_LAYER_KEYWORDS, TILE_LAYER_PREFIX, buildTileLayer, } from './utils'; const TICK = 250; // milliseconds export type DeckGLContainerProps = { viewport: Viewport; setControlValue?: (control: string, value: JsonValue) => void; mapStyle?: string; mapboxApiAccessToken: string; children?: ReactNode; width: number; height: number; layers: (Layer | (() => Layer))[]; onViewportChange?: (viewport: Viewport) => void; }; export const DeckGLContainer = memo( forwardRef((props: DeckGLContainerProps, ref) => { const [tooltip, setTooltip] = useState(null); const [lastUpdate, setLastUpdate] = useState(null); const [viewState, setViewState] = useState(props.viewport); const prevViewport = usePrevious(props.viewport); const glContextRef = useRef(null); useEffect( () => () => { glContextRef.current?.getExtension('WEBGL_lose_context')?.loseContext(); }, [], ); useImperativeHandle(ref, () => ({ setTooltip }), []); const tick = useCallback(() => { // Rate limiting updating viewport controls as it triggers lots of renders if (lastUpdate && Date.now() - lastUpdate > TICK) { const setCV = props.setControlValue; if (setCV) { setCV('viewport', viewState); } setLastUpdate(null); } }, [lastUpdate, props.setControlValue, viewState]); useEffect(() => { const timer = setInterval(tick, TICK); return clearInterval(timer); }, [tick]); useEffect(() => { if (!isEqual(props.viewport, prevViewport)) { setViewState(props.viewport); } }, [prevViewport, props.viewport]); const onViewStateChange = useCallback( ({ viewState }: { viewState: JsonObject }) => { setViewState(viewState as Viewport); setLastUpdate(Date.now()); }, [], ); const layers = useCallback(() => { if ( (props.mapStyle?.startsWith(TILE_LAYER_PREFIX) || OSM_LAYER_KEYWORDS.some((tilek: string) => props.mapStyle?.includes(tilek), )) && props.layers.some( l => typeof l !== 'function' && l?.id === 'tile-layer', ) === false ) { props.layers.unshift( buildTileLayer( (props.mapStyle ?? '').replace(TILE_LAYER_PREFIX, ''), 'tile-layer', ), ); } // Support for layer factory if (props.layers.some(l => typeof l === 'function')) { return props.layers.map(l => typeof l === 'function' ? l() : l, ) as Layer[]; } return props.layers as Layer[]; }, [props.layers, props.mapStyle]); const isCustomTooltip = (content: ReactNode): boolean => isValidElement(content) && content.props?.['data-tooltip-type'] === 'custom'; const renderTooltip = (tooltipState: TooltipProps['tooltip']) => { if (!tooltipState) return null; if (isCustomTooltip(tooltipState.content)) { return ; } return ; }; const { children = null, height, width } = props; return ( <>
) => { e.preventDefault(); e.stopPropagation(); }} > { glContextRef.current = context.gl; }} > {props.mapStyle?.startsWith(MAPBOX_LAYER_PREFIX) && ( )} {children}
{renderTooltip(tooltip)} ); }), ); export const DeckGLContainerStyledWrapper = styled(DeckGLContainer)` .deckgl-tooltip > div { overflow: hidden; text-overflow: ellipsis; } `; export type DeckGLContainerHandle = typeof DeckGLContainer & { setTooltip: (tooltip: ReactNode) => void; };