/* eslint-disable no-magic-numbers */ import { Collection, Feature } from 'ol'; import VectorLayer from 'ol/layer/Vector'; import VectorSource from 'ol/source/Vector'; import { useEffect, useMemo, useState } from 'react'; import { useSelector } from 'react-redux'; import { currentModelIdSelector } from '@/redux/models/models.selectors'; import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; import { getLayersForModel } from '@/redux/layers/layers.thunks'; import { layersActiveLayerSelector, layersForCurrentModelSelector, layersLoadingSelector, layersVisibilityForCurrentModelSelector, } from '@/redux/layers/layers.selectors'; import { usePointToProjection } from '@/utils/map/usePointToProjection'; import { MapInstance } from '@/types/map'; import { Geometry, LineString, MultiPolygon, Point } from 'ol/geom'; import Polygon from 'ol/geom/Polygon'; import Layer from '@/components/Map/MapViewer/utils/shapes/layer/Layer'; import { arrowTypesSelector, lineTypesSelector } from '@/redux/shapes/shapes.selectors'; import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { mapDataSizeSelector } from '@/redux/map/map.selectors'; import { LayerState } from '@/redux/layers/layers.types'; import { mapEditToolsActiveActionSelector } from '@/redux/mapEditTools/mapEditTools.selectors'; import { MAP_EDIT_ACTIONS } from '@/redux/mapEditTools/mapEditTools.constants'; import getDrawBoundingBoxInteraction from '@/components/Map/MapViewer/utils/shapes/layer/interaction/getDrawBoundingBoxInteraction'; import { openLayerImageObjectFactoryModal, openLayerTextFactoryModal, } from '@/redux/modal/modal.slice'; import { Extent } from 'ol/extent'; import { mapEditToolsSetLayerObject } from '@/redux/mapEditTools/mapEditTools.slice'; import getTransformImageInteraction from '@/components/Map/MapViewer/utils/shapes/layer/interaction/getTransformImageInteraction'; import { useWebSocketEntityUpdatesContext } from '@/utils/websocket-entity-updates/webSocketEntityUpdatesProvider'; import processMessage from '@/components/Map/MapViewer/utils/websocket/processMessage'; export const useOlMapAdditionalLayers = ( mapInstance: MapInstance, ): Array< VectorLayer< VectorSource<Feature<Point> | Feature<Polygon> | Feature<LineString> | Feature<MultiPolygon>> > > => { const activeAction = useAppSelector(mapEditToolsActiveActionSelector); const dispatch = useAppDispatch(); const mapSize = useSelector(mapDataSizeSelector); const currentModelId = useSelector(currentModelIdSelector); const layersForCurrentModel = useAppSelector(layersForCurrentModelSelector); const layersLoading = useAppSelector(layersLoadingSelector); const layersVisibilityForCurrentModel = useAppSelector(layersVisibilityForCurrentModelSelector); const activeLayer = useAppSelector(layersActiveLayerSelector); const [layersState, setLayersState] = useState<Array<LayerState>>([]); const [layersLoadingState, setLayersLoadingState] = useState(false); const lineTypes = useSelector(lineTypesSelector); const arrowTypes = useSelector(arrowTypesSelector); const pointToProjection = usePointToProjection(); const { lastJsonMessage } = useWebSocketEntityUpdatesContext(); useEffect(() => { if (!lastJsonMessage || !('entityType' in lastJsonMessage)) { return; } processMessage({ jsonMessage: lastJsonMessage, mapInstance }); }, [lastJsonMessage, mapInstance]); const restrictionExtent: Extent = useMemo(() => { const restrictionMinPoint = pointToProjection({ x: 0, y: 0 }); const restrictionMaxPoint = pointToProjection({ x: mapSize.width, y: mapSize.height }); return [ restrictionMinPoint[0], restrictionMaxPoint[1], restrictionMaxPoint[0], restrictionMinPoint[1], ]; }, [mapSize, pointToProjection]); const drawImageInteraction = useMemo(() => { if (!mapSize || !dispatch) { return null; } return getDrawBoundingBoxInteraction( mapSize, dispatch, restrictionExtent, openLayerImageObjectFactoryModal, ); }, [mapSize, dispatch, restrictionExtent]); const addTextInteraction = useMemo(() => { if (!mapSize || !dispatch) { return null; } return getDrawBoundingBoxInteraction( mapSize, dispatch, restrictionExtent, openLayerTextFactoryModal, ); }, [mapSize, dispatch, restrictionExtent]); useEffect(() => { if (!currentModelId) { return; } if (!['succeeded', 'pending'].includes(layersLoading)) { dispatch(getLayersForModel(currentModelId)); } }, [currentModelId, dispatch, layersLoading]); const vectorLayers = useMemo(() => { return layersState.map(layer => { const additionalLayer = new Layer({ texts: layer.texts, rects: layer.rects, ovals: layer.ovals, lines: layer.lines, images: layer.images, visible: layer.details.visible, layerId: layer.details.id, lineTypes, arrowTypes, mapInstance, mapSize, pointToProjection, }); return additionalLayer.vectorLayer; }); }, [layersState, lineTypes, arrowTypes, mapInstance, mapSize, pointToProjection]); useEffect(() => { if (layersLoading === 'pending') { setLayersLoadingState(true); } else if (layersLoading === 'succeeded' && layersLoadingState) { setLayersLoadingState(false); setLayersState(layersForCurrentModel); } }, [layersForCurrentModel, layersLoading, layersLoadingState]); const transformInteraction = useMemo(() => { if (!dispatch || !currentModelId || !activeLayer) { return null; } let imagesFeatures: Collection<Feature<Geometry>> = new Collection(); const vectorLayer = vectorLayers.find(layer => layer.get('id') === activeLayer); if (vectorLayer) { imagesFeatures = new Collection(vectorLayer.get('imagesFeatures')); } return getTransformImageInteraction( dispatch, mapSize, currentModelId, activeLayer, imagesFeatures, restrictionExtent, ); }, [dispatch, mapSize, currentModelId, restrictionExtent, activeLayer, vectorLayers]); useEffect(() => { vectorLayers.forEach(layer => { const layerId = layer.get('id'); if (layerId && layersVisibilityForCurrentModel[layerId] !== undefined) { layer.setVisible(layersVisibilityForCurrentModel[layerId]); } }); }, [layersVisibilityForCurrentModel, vectorLayers]); useEffect(() => { const activeVectorLayer = vectorLayers.find(layer => { const layerId = layer.get('id'); return layerId === activeLayer; }); if (!activeVectorLayer) { return () => {}; } const removeFeatureHandler = (): void => { transformInteraction?.setSelection(new Collection<Feature>()); }; const source = activeVectorLayer.getSource(); source?.on('removefeature', removeFeatureHandler); return () => { if (source) { source.un('removefeature', removeFeatureHandler); } }; }, [activeLayer, layersVisibilityForCurrentModel, transformInteraction, vectorLayers]); useEffect(() => { if (!transformInteraction) { return () => {}; } if (!activeLayer || activeAction !== MAP_EDIT_ACTIONS.TRANSFORM_IMAGE) { return () => {}; } mapInstance?.addInteraction(transformInteraction); return () => { dispatch(mapEditToolsSetLayerObject(null)); mapInstance?.removeInteraction(transformInteraction); }; }, [activeAction, activeLayer, dispatch, mapInstance, transformInteraction]); useEffect(() => { if (!drawImageInteraction) { return; } mapInstance?.removeInteraction(drawImageInteraction); if (!activeLayer || activeAction !== MAP_EDIT_ACTIONS.DRAW_IMAGE) { return; } mapInstance?.addInteraction(drawImageInteraction); }, [activeAction, activeLayer, currentModelId, drawImageInteraction, mapInstance]); useEffect(() => { if (!addTextInteraction) { return; } mapInstance?.removeInteraction(addTextInteraction); if (!activeLayer || activeAction !== MAP_EDIT_ACTIONS.ADD_TEXT) { return; } mapInstance?.addInteraction(addTextInteraction); }, [activeAction, activeLayer, currentModelId, addTextInteraction, mapInstance]); return vectorLayers; };