Skip to content
Snippets Groups Projects
LayersDrawerObjectsList.component.tsx 9.13 KiB
Newer Older
/* eslint-disable no-magic-numbers */
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import {
} from '@/redux/layers/layers.selectors';
import { JSX, useState } from 'react';
import { LayersDrawerImageItem } from '@/components/Map/Drawer/LayersDrawer/LayersDrawerImageItem.component';
import { LayersDrawerTextItem } from '@/components/Map/Drawer/LayersDrawer/LayerDrawerTextItem.component';
import QuestionModal from '@/components/FunctionalArea/Modal/QuestionModal/QustionModal.component';
import {
  removeLayerImage,
  removeLayerText,
  updateLayerImageObject,
import {
  layerDeleteImage,
  layerDeleteText,
  layerUpdateImage,
  layerUpdateText,
} from '@/redux/layers/layers.slice';
import removeElementFromLayer from '@/components/Map/MapViewer/utils/shapes/elements/removeElementFromLayer';
import { showToast } from '@/utils/showToast';
import { SerializedError } from '@reduxjs/toolkit';
import { LayerImage, LayerText } from '@/types/models';
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { useMapInstance } from '@/utils/context/mapInstanceContext';
import { mapModelIdSelector } from '@/redux/map/map.selectors';
import { mapEditToolsSetLayerObject } from '@/redux/mapEditTools/mapEditTools.slice';
import updateElement from '@/components/Map/MapViewer/utils/shapes/layer/utils/updateElement';
import { useSetBounds } from '@/utils/map/useSetBounds';
import { mapEditToolsLayerObjectSelector } from '@/redux/mapEditTools/mapEditTools.selectors';
import { usePointToProjection } from '@/utils/map/usePointToProjection';
import { Coordinate } from 'ol/coordinate';
import {
  openLayerImageObjectEditFactoryModal,
  openLayerTextEditFactoryModal,
} from '@/redux/modal/modal.slice';

interface LayersDrawerObjectsListProps {
  layerId: number;
  isLayerVisible: boolean;
  isLayerActive: boolean;
}

const removeObjectConfig = {
  image: {
    question: 'Are you sure you want to remove the image?',
    successMessage: 'The layer image has been successfully removed',
    errorMessage: 'An error occurred while removing the layer text',
  },
  text: {
    question: 'Are you sure you want to remove the text?',
    successMessage: 'The layer text has been successfully removed',
    errorMessage: 'An error occurred while removing the layer text',
  },
};

export const LayersDrawerObjectsList = ({
  layerId,
  isLayerVisible,
  isLayerActive,
}: LayersDrawerObjectsListProps): JSX.Element | null => {
  const currentModelId = useAppSelector(mapModelIdSelector);
  const highestZIndex = useAppSelector(highestZIndexSelector);
  const lowestZIndex = useAppSelector(lowestZIndexSelector);
  const layer = useAppSelector(state => layerByIdSelector(state, layerId));
  const mapEditToolsLayerImageObject = useAppSelector(mapEditToolsLayerObjectSelector);
  const [removeModalState, setRemoveModalState] = useState<undefined | 'text' | 'image'>(undefined);
  const [layerObjectToRemove, setLayerObjectToRemove] = useState<LayerImage | LayerText | null>(
    null,
  );
  const dispatch = useAppDispatch();
  const setBounds = useSetBounds();
  const pointToProjection = usePointToProjection();
  const { mapInstance } = useMapInstance();

  const removeObject = (layerObject: LayerImage | LayerText): void => {
    setLayerObjectToRemove(layerObject);
    if ('glyph' in layerObject) {
      setRemoveModalState('image');
    } else {
      setRemoveModalState('text');
    }
  const rejectRemove = (): void => {
    setRemoveModalState(undefined);
  const confirmRemove = async (): Promise<void> => {
    if (!layerObjectToRemove || !removeModalState) {
      if (removeModalState === 'text') {
        await dispatch(
          removeLayerText({
            modelId: currentModelId,
            layerId: layerObjectToRemove.layer,
            textId: layerObjectToRemove.id,
          }),
        ).unwrap();
        dispatch(
          layerDeleteText({
            modelId: currentModelId,
            layerId: layerObjectToRemove.layer,
            textId: layerObjectToRemove.id,
          }),
        );
      } else {
        await dispatch(
          removeLayerImage({
            modelId: currentModelId,
            layerId: layerObjectToRemove.layer,
            imageId: layerObjectToRemove.id,
          }),
        ).unwrap();
        dispatch(
          layerDeleteImage({
            modelId: currentModelId,
            layerId: layerObjectToRemove.layer,
            imageId: layerObjectToRemove.id,
          }),
        );
      }
      removeElementFromLayer({
        mapInstance,
        layerId: layerObjectToRemove.layer,
        featureId: layerObjectToRemove.id,
        message: removeObjectConfig[removeModalState].successMessage,
    } catch (error) {
      const typedError = error as SerializedError;
      showToast({
        type: 'error',
        message: typedError.message || removeObjectConfig[removeModalState].errorMessage,
      });
    }
  };

  const updateImageZIndex = async ({
    zIndex,
    layerImage,
  }: {
    zIndex: number;
    layerImage: LayerImage;
  }): Promise<void> => {
    const newLayerImage = await dispatch(
      updateLayerImageObject({
        modelId: currentModelId,
        layerId: layerImage.layer,
        ...layerImage,
        z: zIndex,
      }),
    ).unwrap();
    if (newLayerImage) {
      dispatch(
        layerUpdateImage({
          modelId: currentModelId,
          layerId: newLayerImage.layer,
          layerImage: newLayerImage,
        }),
      );
      dispatch(mapEditToolsSetLayerObject(newLayerImage));
      updateElement(mapInstance, newLayerImage.layer, newLayerImage);
  const bringImageToFront = async (layerImage: LayerImage): Promise<void> => {
    await updateImageZIndex({ zIndex: highestZIndex + 1, layerImage });
  const bringImageToBack = async (layerImage: LayerImage): Promise<void> => {
    await updateImageZIndex({ zIndex: lowestZIndex - 1, layerImage });
  const updateTextZIndex = async ({
    zIndex,
    layerText,
  }: {
    zIndex: number;
    layerText: LayerText;
  }): Promise<void> => {
    const newLayerText = await dispatch(
      updateLayerText({
        modelId: currentModelId,
        layerId: layerText.layer,
        ...layerText,
        z: zIndex,
      }),
    ).unwrap();
    if (newLayerText) {
      dispatch(
        layerUpdateText({
          modelId: currentModelId,
          layerId: newLayerText.layer,
          layerText: newLayerText,
        }),
      );
      dispatch(mapEditToolsSetLayerObject(newLayerText));
      updateElement(mapInstance, newLayerText.layer, newLayerText);
    }
  };

  const bringTextToFront = async (layerText: LayerText): Promise<void> => {
    await updateTextZIndex({ zIndex: highestZIndex + 1, layerText });
  const bringTextToBack = async (layerText: LayerText): Promise<void> => {
    await updateTextZIndex({ zIndex: lowestZIndex - 1, layerText });
  const centerObject = (layerObject: LayerImage | LayerText): void => {
    if (mapEditToolsLayerImageObject && mapEditToolsLayerImageObject.id === layerObject.id) {
      const point1 = pointToProjection({ x: layerObject.x, y: layerObject.y });
      const point2 = pointToProjection({
        x: layerObject.x + layerObject.width,
        y: layerObject.y + layerObject.height,
      });
      setBounds([point1, point2] as Coordinate[]);
    }
  };

  const editImage = (): void => {
    dispatch(openLayerImageObjectEditFactoryModal());
  };

  const editText = (): void => {
    dispatch(openLayerTextEditFactoryModal());
  };

  if (!layer) {
    return null;
  }

  return (
    <div className={`${isLayerVisible ? 'opacity-100' : 'opacity-40'} flex flex-col gap-1 ps-3`}>
      <QuestionModal
        isOpen={Boolean(removeModalState)}
        onClose={rejectRemove}
        onConfirm={confirmRemove}
        question={
          removeModalState
            ? removeObjectConfig[removeModalState]?.question
      />
      {Object.values(layer.texts).map(layerText => (
        <LayersDrawerTextItem
          layerText={layerText}
          key={layerText.id}
          bringToFront={() => bringTextToFront(layerText)}
          bringToBack={() => bringTextToBack(layerText)}
          removeObject={() => removeObject(layerText)}
          centerObject={() => centerObject(layerText)}
          isLayerVisible={isLayerVisible}
          isLayerActive={isLayerActive}
        />
      ))}
      {Object.values(layer.images).map(layerImage => (
        <LayersDrawerImageItem
          layerImage={layerImage}
          key={layerImage.id}
          bringToFront={() => bringImageToFront(layerImage)}
          bringToBack={() => bringImageToBack(layerImage)}
          centerObject={() => centerObject(layerImage)}
          editObject={() => editImage()}
          isLayerVisible={isLayerVisible}
          isLayerActive={isLayerActive}
        />
      ))}
    </div>
  );
};