imaginerio/narratives

View on GitHub
src/components/Atlas/Context.jsx

Summary

Maintainability
A
2 hrs
Test Coverage
import React, { useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { useQuery, useMutation, gql } from '@apollo/client';
import { pick, isEqual } from 'lodash';
import { Dimmer, Loader } from 'semantic-ui-react';

import Atlas from './index';
import { minZoom, maxZoom, minLon, maxLon, minLat, maxLat } from '../../config/map';
import debouncedMutation from '../../providers/debouncedMutation';
import useLocale from '../../hooks/useLocale';

const GET_SLIDE_ATLAS = gql`
  query GetSlideYear($slide: ID!) {
    Slide(where: { id: $slide }) {
      id
      year
      longitude
      latitude
      zoom
      bearing
      pitch
      selectedFeature
      opacity
      annotations {
        id
        feature
      }
      disabledLayers: layers {
        id
        layerId
      }
      basemap {
        id
        ssid
        title
        thumbnail
        creator
      }
    }
  }
`;

const UPDATE_VIEWPORT = gql`
  mutation UpdateViewport(
    $slide: ID!
    $longitude: Float
    $latitude: Float
    $zoom: Float
    $bearing: Float
    $pitch: Float
  ) {
    updateSlide(
      id: $slide
      data: {
        longitude: $longitude
        latitude: $latitude
        zoom: $zoom
        bearing: $bearing
        pitch: $pitch
      }
    ) {
      id
      longitude
      latitude
      zoom
      bearing
      pitch
    }
  }
`;

const AtlasContext = ({ slide }) => {
  const { loading, error, data } = useQuery(GET_SLIDE_ATLAS, {
    variables: { slide },
  });

  const [onUpdateViewport] = useMutation(UPDATE_VIEWPORT);
  const viewportTimer = useRef();
  const { loadingText } = useLocale();

  const [mapViewport, setMapViewport] = useState(null);
  const [annotations, setAnnotations] = useState(null);

  useEffect(() => {
    if (data) {
      setMapViewport(pick(data.Slide, ['longitude', 'latitude', 'zoom', 'bearing', 'pitch']));
      if (data.Slide.annotations) {
        setAnnotations({
          type: 'FeatureCollection',
          features: data.Slide.annotations.map(({ feature }) => JSON.parse(feature)),
        });
      }
    }
  }, [loading, data]);

  const onViewportChange = nextViewport => {
    if (nextViewport) {
      const clampedPort = {
        ...nextViewport,
        longitude: Math.max(minLon, Math.min(maxLon, nextViewport.longitude)),
        latitude: Math.max(minLat, Math.min(maxLat, nextViewport.latitude)),
        zoom: Math.max(minZoom, Math.min(maxZoom, nextViewport.zoom)),
      };
      setMapViewport(clampedPort);
      if (
        !isEqual(
          clampedPort,
          pick(data.Slide, ['longitude', 'latitude', 'zoom', 'bearing', 'pitch'])
        )
      ) {
        viewportTimer.current = debouncedMutation({
          slide,
          timerRef: viewportTimer,
          mutation: onUpdateViewport,
          values: clampedPort,
        });
      }
    }
  };

  if (loading || !mapViewport || !data)
    return (
      <Dimmer active>
        <Loader size="huge">{loadingText}</Loader>
      </Dimmer>
    );
  if (error) return <p>Error :(</p>;

  return (
    <Atlas
      handler={onViewportChange}
      viewport={mapViewport}
      year={data.Slide.year}
      disabledLayers={data.Slide.disabledLayers}
      activeBasemap={data.Slide.basemap}
      selectedFeature={data.Slide.selectedFeature}
      opacity={data.Slide.opacity}
      annotations={annotations}
    />
  );
};

AtlasContext.propTypes = {
  slide: PropTypes.string.isRequired,
};

export default AtlasContext;