Vizzuality/landgriffon

View on GitHub
client/src/components/map/layer-manager/provider.tsx

Summary

Maintainability
A
0 mins
Test Coverage
import { createContext, useCallback, useContext, useMemo } from 'react';
import { useControl } from 'react-map-gl/maplibre';
import { MapboxOverlay } from '@deck.gl/mapbox/typed';

import type { MapboxOverlayProps } from '@deck.gl/mapbox/typed';
import type { PropsWithChildren } from 'react';
import type { Layer } from 'types';
import type { LayerSettings, DeckLayerProps } from 'components/map/layers/types';

interface MapboxOverlayContext {
  addLayer: (layer: DeckLayerProps<unknown, LayerSettings>) => void;
  removeLayer: (id: Layer['id']) => void;
}

const Context = createContext<MapboxOverlayContext>({
  addLayer: () => undefined,
  removeLayer: () => undefined,
});

function useMapboxOverlay(
  props: MapboxOverlayProps & {
    interleaved?: boolean;
  },
) {
  const overlay = useControl<MapboxOverlay>(() => new MapboxOverlay(props));
  overlay.setProps(props);

  return overlay;
}

export const MapboxOverlayProvider = ({ children }: PropsWithChildren) => {
  const OVERLAY = useMapboxOverlay({
    interleaved: true,
  });

  const addLayer = useCallback(
    (layer) => {
      // @ts-expect-error to fix
      const layers = OVERLAY._props.layers || [];

      const l1 = new layer.type({
        ...layer,
        getPolygonOffset: () => [0, -100000000 + layer.zIndex * 1000],
      });

      OVERLAY.setProps({
        layers: [...layers.filter((l) => l.id !== layer.id), l1],
      });
    },
    [OVERLAY],
  );

  const removeLayer = useCallback(
    (id) => {
      // @ts-expect-error to fix
      const layers = OVERLAY._props.layers || [];

      OVERLAY.setProps({
        layers: [...layers.filter((l) => l.id !== id)],
      });
    },
    [OVERLAY],
  );

  const context = useMemo(
    () => ({
      addLayer,
      removeLayer,
    }),
    [addLayer, removeLayer],
  );

  return (
    <Context.Provider key="mapbox-overlay-provider" value={context}>
      {children}
    </Context.Provider>
  );
};

export const useMapboxOverlayContext = () => {
  const context = useContext(Context);

  if (!context) {
    throw new Error('useMapboxOverlayContext must be used within a MapboxOverlayProvider');
  }

  return context;
};