diff --git a/src/components/Map/Drawer/BioEntityDrawer/BioEntityDrawer.component.tsx b/src/components/Map/Drawer/BioEntityDrawer/BioEntityDrawer.component.tsx index 42c5a1f9659b1a26b3024907b5830427cbb59b84..6d8f627c22e443de30fde139ed5a4612d87b1d95 100644 --- a/src/components/Map/Drawer/BioEntityDrawer/BioEntityDrawer.component.tsx +++ b/src/components/Map/Drawer/BioEntityDrawer/BioEntityDrawer.component.tsx @@ -29,7 +29,6 @@ export const BioEntityDrawer = (): React.ReactNode => { const commentsData = useAppSelector(currentDrawerElementCommentsSelector); const relatedSubmap = useAppSelector(currentDrawerBioEntityRelatedSubmapSelector); const currentTargetId = bioEntityData?.id ? `${TARGET_PREFIX}:${bioEntityData.id}` : ''; - const fetchChemicalsForTarget = (): void => { dispatch(getChemicalsForBioEntityDrawerTarget(currentTargetId)); }; diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleAlias.test.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleAlias.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..9d86665c5424621cf78406b7812571a862659816 --- /dev/null +++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleAlias.test.ts @@ -0,0 +1,58 @@ +/* eslint-disable no-magic-numbers */ +import { leftClickHandleAlias } from '@/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleAlias'; +import { openBioEntityDrawerById, selectTab } from '@/redux/drawer/drawer.slice'; +import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus'; +import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds'; +import { Feature } from 'ol'; + +jest.mock('../../../../../../services/pluginsManager/map/triggerSearch/searchFitBounds'); +const eventBusDispatchEventSpy = jest.spyOn(PluginsEventBus, 'dispatchEvent'); + +describe('leftClickHandleAlias', () => { + let dispatch: jest.Mock; + const modelId = 1; + const hasFitBounds = true; + const feature = new Feature({ type: 'ALIAS', id: 1 }); + + beforeEach(() => { + jest.clearAllMocks(); + dispatch = jest.fn(); + }); + + it('dispatches getMultiBioEntityByIds, selectTab, and openBioEntityDrawerById, then triggers PluginsEventBus and searchFitBounds when hasFitBounds is true', async () => { + const mockBioEntities = [{ id: 1, name: 'BioEntity 1' }]; + dispatch = jest.fn(() => ({ + unwrap: jest.fn().mockResolvedValue(mockBioEntities), + })); + + await leftClickHandleAlias(dispatch, hasFitBounds)(feature, modelId); + expect(dispatch).toHaveBeenCalledTimes(3); + + expect(dispatch).toHaveBeenCalledWith(selectTab('1')); + expect(dispatch).toHaveBeenCalledWith(openBioEntityDrawerById(1)); + + expect(eventBusDispatchEventSpy).toHaveBeenCalledWith('onSearch', { + type: 'bioEntity', + searchValues: [{ id: 1, modelId, type: 'ALIAS' }], + results: [ + mockBioEntities.map(bioEntity => ({ + perfect: true, + bioEntity, + })), + ], + }); + + expect(searchFitBounds).toHaveBeenCalled(); + }); + + it('does not call searchFitBounds when hasFitBounds is false', async () => { + const mockBioEntities = [{ id: 1, name: 'BioEntity 1' }]; + dispatch.mockImplementation(() => ({ + unwrap: jest.fn().mockResolvedValue(mockBioEntities), + })); + + await leftClickHandleAlias(dispatch, false)(feature, modelId); + + expect(searchFitBounds).not.toHaveBeenCalled(); + }); +}); diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleAlias.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleAlias.ts new file mode 100644 index 0000000000000000000000000000000000000000..1657e8255f034bb700bad71c3b61134660d7c83c --- /dev/null +++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleAlias.ts @@ -0,0 +1,34 @@ +import { openBioEntityDrawerById, selectTab } from '@/redux/drawer/drawer.slice'; +import { AppDispatch } from '@/redux/store'; +import { getMultiBioEntityByIds } from '@/redux/bioEntity/thunks/getMultiBioEntity'; +import { FeatureLike } from 'ol/Feature'; +import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus'; +import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds'; + +/* prettier-ignore */ +export const leftClickHandleAlias = + (dispatch: AppDispatch, hasFitBounds = false) => + async (feature: FeatureLike, modelId: number): Promise<void> => { + const id = feature.get('id'); + const bioEntities = await dispatch( + getMultiBioEntityByIds({ + elementsToFetch: [{ elementId: id, type: 'ALIAS', modelId, addNumbersToEntityNumber: true }], + }), + ).unwrap(); + dispatch(selectTab(`${id}`)); + dispatch(openBioEntityDrawerById(id)); + + PluginsEventBus.dispatchEvent('onSearch', { + type: 'bioEntity', + searchValues: [{ id, modelId, type: 'ALIAS' }], + results: [ + bioEntities.map(bioEntity => { + return { perfect: true, bioEntity }; + }), + ], + }); + + if (hasFitBounds) { + searchFitBounds(); + } + }; diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleReaction.test.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleReaction.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..372bc9a70e23937b23cd9b87a105bdb968b39abe --- /dev/null +++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleReaction.test.ts @@ -0,0 +1,74 @@ +/* eslint-disable no-magic-numbers */ +import { leftClickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleReaction'; +import { openReactionDrawerById, selectTab } from '@/redux/drawer/drawer.slice'; +import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus'; +import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds'; +import { Feature } from 'ol'; +import { reactionsFixture } from '@/models/fixtures/reactionFixture'; +import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse'; +import { apiPath } from '@/redux/apiPath'; +import { HttpStatusCode } from 'axios'; +import { bioEntityFixture } from '@/models/fixtures/bioEntityFixture'; + +const mockedAxiosClient = mockNetworkNewAPIResponse(); +jest.mock('../../../../../../services/pluginsManager/map/triggerSearch/searchFitBounds'); +const eventBusDispatchEventSpy = jest.spyOn(PluginsEventBus, 'dispatchEvent'); + +describe('leftClickHandleReaction', () => { + let dispatch: jest.Mock; + let modelId = 1; + let reactionId = 1; + const hasFitBounds = true; + const feature = new Feature({ type: 'REACTION', id: 1 }); + + beforeEach(() => { + jest.clearAllMocks(); + dispatch = jest.fn(); + }); + + it('dispatches getMultiBioEntityByIds, selectTab, and openBioEntityDrawerById, then triggers PluginsEventBus and searchFitBounds when hasFitBounds is true', async () => { + dispatch = jest.fn(() => { + return { + unwrap: jest.fn().mockResolvedValue([]), + payload: { + data: [reactionsFixture[0]], + }, + }; + }); + reactionId = reactionsFixture[0].id; + modelId = reactionsFixture[0].modelId; + + mockedAxiosClient + .onGet(apiPath.getReactionByIdInNewApi(reactionId, modelId)) + .reply(HttpStatusCode.Ok, bioEntityFixture); + + [ + ...reactionsFixture[0].products, + ...reactionsFixture[0].reactants, + ...reactionsFixture[0].modifiers, + ].forEach(element => { + mockedAxiosClient + .onGet( + apiPath.getElementById('aliasId' in element ? element.aliasId : element.element, modelId), + ) + .reply(HttpStatusCode.Ok, bioEntityFixture); + }); + await leftClickHandleReaction(dispatch, hasFitBounds)(feature, modelId); + expect(dispatch).toHaveBeenCalledTimes(4); + expect(dispatch).toHaveBeenCalledWith(openReactionDrawerById(reactionId)); + expect(dispatch).toHaveBeenCalledWith(selectTab('')); + expect(eventBusDispatchEventSpy).toHaveBeenCalled(); + expect(searchFitBounds).toHaveBeenCalled(); + }); + + it('does not call searchFitBounds when hasFitBounds is false', async () => { + const mockBioEntities = [{ id: 1, name: 'BioEntity 1' }]; + dispatch.mockImplementation(() => ({ + unwrap: jest.fn().mockResolvedValue(mockBioEntities), + })); + + await leftClickHandleReaction(dispatch, false)(feature, modelId); + + expect(searchFitBounds).not.toHaveBeenCalled(); + }); +}); diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleReaction.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleReaction.ts new file mode 100644 index 0000000000000000000000000000000000000000..34996f35c0c7df6b1a10a0e6df630b6da6213ee1 --- /dev/null +++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleReaction.ts @@ -0,0 +1,67 @@ +import { FIRST_ARRAY_ELEMENT, SIZE_OF_EMPTY_ARRAY } from '@/constants/common'; +import { openReactionDrawerById, selectTab } from '@/redux/drawer/drawer.slice'; +import { getReactionsByIds } from '@/redux/reactions/reactions.thunks'; +import { AppDispatch } from '@/redux/store'; +import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds'; +import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus'; +import { BioEntity } from '@/types/models'; +import { axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance'; +import { apiPath } from '@/redux/apiPath'; +import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema'; +import { bioEntitySchema } from '@/models/bioEntitySchema'; +import { getMultiBioEntityByIds } from '@/redux/bioEntity/thunks/getMultiBioEntity'; +import { FeatureLike } from 'ol/Feature'; +import { getBioEntitiesIdsFromReaction } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/getBioEntitiesIdsFromReaction'; + +/* prettier-ignore */ +export const leftClickHandleReaction = + (dispatch: AppDispatch, hasFitBounds = false) => + async (feature: FeatureLike, modelId: number): Promise<void> => { + const id = feature.get('id'); + const data = await dispatch(getReactionsByIds([id])); + const payload = data?.payload; + if (!data || !payload || typeof payload === 'string' || payload.data.length === SIZE_OF_EMPTY_ARRAY) { + return; + } + + const reaction = payload.data[FIRST_ARRAY_ELEMENT]; + + const bioEntitiesIds = getBioEntitiesIdsFromReaction(reaction); + + dispatch(openReactionDrawerById(reaction.id)); + + dispatch(selectTab('')); + + const response = await axiosInstanceNewAPI.get<BioEntity>(apiPath.getReactionByIdInNewApi(reaction.id, reaction.modelId)); + const isDataValid = validateDataUsingZodSchema(response.data, bioEntitySchema); + + if (isDataValid) { + const reactionNewApi = response.data; + + const bioEntities = await dispatch( + getMultiBioEntityByIds({ + elementsToFetch: bioEntitiesIds.map((bioEntityId) => { + return { + elementId: parseInt(bioEntityId, 10), + modelId, + type: 'ALIAS' + }; + }) + }) + ).unwrap(); + + if (bioEntities) { + const result = bioEntities.map((bioEntity) => {return { bioEntity, perfect: true };}); + result.push({ bioEntity: reactionNewApi, perfect: true }); + PluginsEventBus.dispatchEvent('onSearch', { + type: 'reaction', + searchValues: [{ id, modelId, type: 'REACTION' }], + results: [result] + }); + + if (hasFitBounds) { + searchFitBounds(); + } + } + } + }; diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/onMapLeftClick.test.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/onMapLeftClick.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..0065208e344ff94544141bb2a1f47877e0dabd05 --- /dev/null +++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/onMapLeftClick.test.ts @@ -0,0 +1,112 @@ +/* eslint-disable no-magic-numbers */ +import { updateLastClick } from '@/redux/map/map.slice'; +import { closeDrawer } from '@/redux/drawer/drawer.slice'; +import { resetReactionsData } from '@/redux/reactions/reactions.slice'; +import { clearBioEntitiesData } from '@/redux/bioEntity/bioEntity.slice'; +import { handleFeaturesClick } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick'; +import Map from 'ol/Map'; +import { onMapLeftClick } from '@/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/onMapLeftClick'; +import { Comment } from '@/types/models'; +import { Layer } from 'ol/layer'; +import SimpleGeometry from 'ol/geom/SimpleGeometry'; +import { Feature } from 'ol'; +import * as leftClickHandleAlias from './leftClickHandleAlias'; +import * as leftClickHandleReaction from './leftClickHandleReaction'; + +jest.mock('../../../utils/listeners/mapSingleClick/handleFeaturesClick', () => ({ + handleFeaturesClick: jest.fn(), +})); +jest.mock('./leftClickHandleAlias', () => ({ + __esModule: true, + ...jest.requireActual('./leftClickHandleAlias'), +})); +const leftClickHandleAliasSpy = jest.spyOn(leftClickHandleAlias, 'leftClickHandleAlias'); +jest.mock('./leftClickHandleReaction', () => ({ + __esModule: true, + ...jest.requireActual('./leftClickHandleReaction'), +})); +const leftClickHandleReactionSpy = jest.spyOn(leftClickHandleReaction, 'leftClickHandleReaction'); + +describe('onMapLeftClick', () => { + const modelId = 1; + const isResultDrawerOpen = true; + const comments: Array<Comment> = []; + let mapInstance: Map; + const event = { coordinate: [100, 50], pixel: [200, 100] }; + const mapSize = { + width: 90, + height: 90, + tileSize: 256, + minZoom: 2, + maxZoom: 9, + }; + + beforeEach(() => { + const dummyElement = document.createElement('div'); + mapInstance = new Map({ target: dummyElement }); + jest.clearAllMocks(); + }); + + it('dispatches updateLastClick and resets data if no feature at pixel', async () => { + const dispatch = jest.fn(); + jest.spyOn(mapInstance, 'forEachFeatureAtPixel').mockImplementation((_, callback) => { + callback(new Feature({}), null as unknown as Layer, null as unknown as SimpleGeometry); + }); + await onMapLeftClick( + mapSize, + modelId, + dispatch, + isResultDrawerOpen, + comments, + )(event, mapInstance); + + expect(dispatch).toHaveBeenCalledWith(updateLastClick(expect.any(Object))); + expect(dispatch).toHaveBeenCalledWith(closeDrawer()); + expect(dispatch).toHaveBeenCalledWith(resetReactionsData()); + expect(dispatch).toHaveBeenCalledWith(clearBioEntitiesData()); + }); + + it('calls leftClickHandleAlias if feature type is ALIAS', async () => { + const mockBioEntities = [{ id: 1, name: 'BioEntity 1' }]; + const dispatch = jest.fn(() => ({ + unwrap: jest.fn().mockResolvedValue(mockBioEntities), + })); + const feature = new Feature({ id: 1, type: 'ALIAS' }); + jest.spyOn(mapInstance, 'forEachFeatureAtPixel').mockImplementation((_, callback) => { + callback(feature, null as unknown as Layer, null as unknown as SimpleGeometry); + }); + (handleFeaturesClick as jest.Mock).mockReturnValue({ shouldBlockCoordSearch: false }); + + await onMapLeftClick( + mapSize, + modelId, + dispatch, + isResultDrawerOpen, + comments, + )(event, mapInstance); + + expect(leftClickHandleAliasSpy).toHaveBeenCalledWith(dispatch); + }); + + it('calls leftClickHandleReaction if feature type is REACTION', async () => { + const mockBioEntities = [{ id: 1, name: 'BioEntity 1' }]; + const dispatch = jest.fn(() => ({ + unwrap: jest.fn().mockResolvedValue(mockBioEntities), + })); + const feature = new Feature({ id: 1, type: 'REACTION' }); + jest.spyOn(mapInstance, 'forEachFeatureAtPixel').mockImplementation((_, callback) => { + callback(feature, null as unknown as Layer, null as unknown as SimpleGeometry); + }); + (handleFeaturesClick as jest.Mock).mockReturnValue({ shouldBlockCoordSearch: false }); + + await onMapLeftClick( + mapSize, + modelId, + dispatch, + isResultDrawerOpen, + comments, + )(event, mapInstance); + + expect(leftClickHandleReactionSpy).toHaveBeenCalledWith(dispatch); + }); +}); diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/onMapLeftClick.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/onMapLeftClick.ts new file mode 100644 index 0000000000000000000000000000000000000000..035d1a5bacf820b375c7d5f9dddc3076049aa318 --- /dev/null +++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/onMapLeftClick.ts @@ -0,0 +1,57 @@ +import { MapSize } from '@/redux/map/map.types'; +import { AppDispatch } from '@/redux/store'; +import { Map, MapBrowserEvent } from 'ol'; +import { Comment } from '@/types/models'; +import { updateLastClick } from '@/redux/map/map.slice'; +import { toLonLat } from 'ol/proj'; +import { latLngToPoint } from '@/utils/map/latLngToPoint'; +import { FeatureLike } from 'ol/Feature'; +import { closeDrawer } from '@/redux/drawer/drawer.slice'; +import { clearBioEntitiesData } from '@/redux/bioEntity/bioEntity.slice'; +import { leftClickHandleAlias } from '@/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleAlias'; +import { handleFeaturesClick } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick'; +import { resetReactionsData } from '@/redux/reactions/reactions.slice'; +import { leftClickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleReaction'; +import { handleDataReset } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleDataReset'; + +/* prettier-ignore */ +export const onMapLeftClick = + (mapSize: MapSize, modelId: number, dispatch: AppDispatch, isResultDrawerOpen: boolean, comments: Comment[]) => + async ({ coordinate, pixel }: Pick<MapBrowserEvent<UIEvent>, 'coordinate' | 'pixel'>, mapInstance: Map): Promise<void> => { + const [lng, lat] = toLonLat(coordinate); + const point = latLngToPoint([lat, lng], mapSize); + + dispatch(updateLastClick({ coordinates: point, modelId })); + + let featureAtPixel: FeatureLike | undefined; + mapInstance.forEachFeatureAtPixel(pixel, feature => { + if(feature.get('id') && ['ALIAS', 'REACTION'].includes(feature.get('type'))) { + featureAtPixel = feature; + return true; + } + return false; + }, {hitTolerance: 20}); + if(!featureAtPixel) { + if (isResultDrawerOpen) { + dispatch(closeDrawer()); + } + + dispatch(resetReactionsData()); + dispatch(clearBioEntitiesData()); + return; + } + + const { shouldBlockCoordSearch } = handleFeaturesClick([featureAtPixel], dispatch, comments); + if (shouldBlockCoordSearch) { + return; + } + + dispatch(handleDataReset); + + const type = featureAtPixel.get('type'); + if(type === 'ALIAS') { + await leftClickHandleAlias(dispatch)(featureAtPixel, modelId); + } else if (type === 'REACTION') { + await leftClickHandleReaction(dispatch)(featureAtPixel, modelId); + } + }; diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.test.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..6469151111cf79bc1147bc4028a13ad023431b53 --- /dev/null +++ b/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.test.ts @@ -0,0 +1,48 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { renderHook } from '@testing-library/react'; +import { View } from 'ol'; +import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; +import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/map/map.fixtures'; +import { useOlMapVectorListeners } from '@/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners'; +import { onMapLeftClick } from '@/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/onMapLeftClick'; + +jest.mock('./mapLeftClick/onMapLeftClick', () => ({ + __esModule: true, + onMapLeftClick: jest.fn(), +})); + +jest.mock('use-debounce', () => { + return { + useDebounce: () => {}, + useDebouncedCallback: () => {}, + }; +}); + +describe('useOlMapVectorListeners - util', () => { + const { Wrapper } = getReduxWrapperWithStore({ + map: { + data: { ...initialMapDataFixture }, + loading: 'succeeded', + error: { message: '', name: '' }, + openedMaps: openedMapsThreeSubmapsFixture, + }, + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('on left click event', () => { + it('should run onMapLeftClick event', () => { + const CALLED_ONCE = 1; + const view = new View(); + + renderHook(() => useOlMapVectorListeners({ mapInstance: undefined }), { + wrapper: Wrapper, + }); + view.dispatchEvent('singleclick'); + + expect(onMapLeftClick).toBeCalledTimes(CALLED_ONCE); + }); + }); +}); diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.ts new file mode 100644 index 0000000000000000000000000000000000000000..ab9dd6a3326f33120908d28e31243304f2c3e30b --- /dev/null +++ b/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.ts @@ -0,0 +1,46 @@ +import { OPTIONS } from '@/constants/map'; +import { resultDrawerOpen } from '@/redux/drawer/drawer.selectors'; +import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; +import { mapDataSizeSelector } from '@/redux/map/map.selectors'; +import { currentModelIdSelector, vectorRenderingSelector } from '@/redux/models/models.selectors'; +import { MapInstance } from '@/types/map'; +import { unByKey } from 'ol/Observable'; +import { useEffect } from 'react'; +import { useSelector } from 'react-redux'; +import { useDebouncedCallback } from 'use-debounce'; +import { allCommentsSelectorOfCurrentMap } from '@/redux/comment/comment.selectors'; +import { onMapLeftClick } from '@/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/onMapLeftClick'; +import { useAppSelector } from '@/redux/hooks/useAppSelector'; + +interface UseOlMapListenersInput { + mapInstance: MapInstance; +} + +export const useOlMapVectorListeners = ({ mapInstance }: UseOlMapListenersInput): void => { + const mapSize = useSelector(mapDataSizeSelector); + const modelId = useSelector(currentModelIdSelector); + const isResultDrawerOpen = useSelector(resultDrawerOpen); + const dispatch = useAppDispatch(); + + const comments = useSelector(allCommentsSelectorOfCurrentMap); + const vectorRendering = useAppSelector(vectorRenderingSelector); + + const handleMapLeftClick = useDebouncedCallback( + onMapLeftClick(mapSize, modelId, dispatch, isResultDrawerOpen, comments), + OPTIONS.clickPersistTime, + { leading: false }, + ); + + useEffect(() => { + if (!mapInstance || !vectorRendering) { + return; + } + + const key = mapInstance.on('singleclick', event => + handleMapLeftClick({ coordinate: event.coordinate, pixel: event.pixel }, mapInstance), + ); + + // eslint-disable-next-line consistent-return + return () => unByKey(key); + }, [mapInstance, handleMapLeftClick, vectorRendering]); +}; diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts index caba733755f14d7c6bfccb84a711a173fe4d2487..e2d4fca4e2a409e7e5fc3bc1ab7ad2f4b1a69b94 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/config/reactionsLayer/useOlMapReactionsLayer.ts @@ -55,6 +55,7 @@ export const useOlMapReactionsLayer = ({ return []; } const reactionObject = new Reaction({ + id: reaction.id, line: reaction.line, products: reaction.products, reactants: reaction.reactants, @@ -99,6 +100,7 @@ export const useOlMapReactionsLayer = ({ if (element.sboTerm === 'SBO:0000290') { const compartmentProps = { + id: element.id, x: element.x, y: element.y, nameX: element.nameX, @@ -134,6 +136,7 @@ export const useOlMapReactionsLayer = ({ if (elementShapes) { validElements.push( new MapElement({ + id: element.id, shapes: elementShapes, x: element.x, y: element.y, diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/Compartment.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/Compartment.ts deleted file mode 100644 index bdf6026934bf9f8b697885cdd0feb391fcd1f5ad..0000000000000000000000000000000000000000 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/Compartment.ts +++ /dev/null @@ -1,137 +0,0 @@ -/* eslint-disable no-magic-numbers */ -import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection'; -import { - HorizontalAlign, - VerticalAlign, -} from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types'; -import BaseMultiPolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon'; -import { Coordinate } from 'ol/coordinate'; -import Polygon from 'ol/geom/Polygon'; -import { Style } from 'ol/style'; -import getFill from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getFill'; -import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex'; -import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke'; -import { MapInstance } from '@/types/map'; -import { Color } from '@/types/models'; - -export interface CompartmentProps { - x: number; - y: number; - width: number; - height: number; - thickness: number; - outerWidth: number; - innerWidth: number; - zIndex: number; - text: string; - fontSize: number; - nameX: number; - nameY: number; - nameWidth: number; - nameHeight: number; - fontColor: Color; - nameVerticalAlign: VerticalAlign; - nameHorizontalAlign: HorizontalAlign; - fillColor: Color; - borderColor: Color; - pointToProjection: UsePointToProjectionResult; - mapInstance: MapInstance; -} - -export default abstract class Compartment extends BaseMultiPolygon { - outerCoords: Array<Coordinate> = []; - - innerCoords: Array<Coordinate> = []; - - outerWidth: number; - - innerWidth: number; - - thickness: number; - - constructor({ - x, - y, - width, - height, - thickness, - outerWidth, - innerWidth, - zIndex, - text, - fontSize, - nameX, - nameY, - nameWidth, - nameHeight, - fontColor, - nameVerticalAlign, - nameHorizontalAlign, - fillColor, - borderColor, - pointToProjection, - mapInstance, - }: CompartmentProps) { - super({ - x, - y, - width, - height, - zIndex, - text, - fontSize, - nameX, - nameY, - nameWidth, - nameHeight, - fontColor, - nameVerticalAlign, - nameHorizontalAlign, - fillColor, - borderColor, - pointToProjection, - }); - this.outerWidth = outerWidth; - this.innerWidth = innerWidth; - this.thickness = thickness; - this.getCompartmentCoords(); - this.createPolygons(); - this.drawText(); - this.drawMultiPolygonFeature(mapInstance); - } - - protected abstract getCompartmentCoords(): void; - - protected createPolygons(): void { - const framePolygon = new Polygon([this.outerCoords, this.innerCoords]); - this.styles.push( - new Style({ - geometry: framePolygon, - fill: getFill({ color: rgbToHex({ ...this.fillColor, alpha: 128 }) }), - zIndex: this.zIndex, - }), - ); - this.polygons.push(framePolygon); - - const outerPolygon = new Polygon([this.outerCoords]); - this.styles.push( - new Style({ - geometry: outerPolygon, - stroke: getStroke({ color: rgbToHex(this.borderColor), width: this.outerWidth }), - zIndex: this.zIndex, - }), - ); - this.polygons.push(outerPolygon); - - const innerPolygon = new Polygon([this.innerCoords]); - this.styles.push( - new Style({ - geometry: innerPolygon, - stroke: getStroke({ color: rgbToHex(this.borderColor), width: this.innerWidth }), - fill: getFill({ color: rgbToHex({ ...this.fillColor, alpha: 9 }) }), - zIndex: this.zIndex, - }), - ); - this.polygons.push(innerPolygon); - } -} diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts index 240901b7b6377baf1d605335e06d0223cc63dafb..84dfd1595b3583ae4788bcdfd390bb99164eabf9 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon.ts @@ -15,6 +15,8 @@ import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shap import { Color } from '@/types/models'; export interface BaseMapElementProps { + type: string; + id: number; x: number; y: number; width: number; @@ -35,6 +37,10 @@ export interface BaseMapElementProps { } export default abstract class BaseMultiPolygon { + type: string; + + id: number; + x: number; y: number; @@ -80,6 +86,8 @@ export default abstract class BaseMultiPolygon { pointToProjection: UsePointToProjectionResult; constructor({ + type, + id, x, y, width, @@ -98,6 +106,8 @@ export default abstract class BaseMultiPolygon { borderColor, pointToProjection, }: BaseMapElementProps) { + this.type = type; + this.id = id; this.x = x; this.y = y; this.width = width; @@ -159,6 +169,8 @@ export default abstract class BaseMultiPolygon { } return 1; }, + id: this.id, + type: this.type, }); this.feature.setStyle(this.getStyle.bind(this)); diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts index 342529b7f6e19872a8ea146cc453645f0da093ca..b1aca3d89a513f765786ccef75c00ba6a5a85806 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/Compartment.ts @@ -15,6 +15,7 @@ import { MapInstance } from '@/types/map'; import { Color } from '@/types/models'; export interface CompartmentProps { + id: number; x: number; y: number; width: number; @@ -50,6 +51,7 @@ export default abstract class Compartment extends BaseMultiPolygon { thickness: number; constructor({ + id, x, y, width, @@ -73,6 +75,8 @@ export default abstract class Compartment extends BaseMultiPolygon { mapInstance, }: CompartmentProps) { super({ + type: 'COMPARTMENT', + id, x, y, width, diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts index 84c2b16b42b6f4356e37f5b0e61572751d061d97..3a880e948c6c95a25b11bcdf1cbfc28bb0d1073f 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.test.ts @@ -40,6 +40,7 @@ describe('MapElement', () => { }), }); props = { + id: 1, x: 0, y: 0, width: 100, diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts index f4c7cf16d09203ddd6c4202d22e38ec9d4a910d8..ad8440b3acdeab2a095072364c738906807320e0 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentCircle.ts @@ -16,6 +16,7 @@ import Compartment from '@/components/Map/MapViewer/MapViewerVector/utils/shapes import { Color } from '@/types/models'; export type CompartmentCircleProps = { + id: number; x: number; y: number; width: number; @@ -41,6 +42,7 @@ export type CompartmentCircleProps = { export default class CompartmentCircle extends Compartment { constructor({ + id, x, y, width, @@ -64,6 +66,7 @@ export default class CompartmentCircle extends Compartment { mapInstance, }: CompartmentCircleProps) { super({ + id, x, y, width, diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts index 8d0e6e989c9c62672870b56f739e300e6ecc6085..db133e7f25649b5bf4265021a5bc500b4002abba 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.test.ts @@ -40,6 +40,7 @@ describe('MapElement', () => { }), }); props = { + id: 1, x: 0, y: 0, width: 100, diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts index bc6622c460ae42041ebd71231d7d09da9549a5cd..8adec3ec2f53b5920fa8dde3fe036c6a190d4de0 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentPathway.ts @@ -15,6 +15,7 @@ import getStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/st import { Color } from '@/types/models'; export type CompartmentPathwayProps = { + id: number; x: number; y: number; width: number; @@ -40,6 +41,7 @@ export default class CompartmentPathway extends BaseMultiPolygon { outerWidth: number; constructor({ + id, x, y, width, @@ -61,6 +63,8 @@ export default class CompartmentPathway extends BaseMultiPolygon { mapInstance, }: CompartmentPathwayProps) { super({ + type: 'COMPARTMENT', + id, x, y, width, diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts index 73b47eb7274ece9bf3f4c95af6d98e52df8d7725..45c5ad453099499c2e1d45950421ebc3d4e0494b 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.test.ts @@ -38,6 +38,7 @@ describe('MapElement', () => { }), }); props = { + id: 1, x: 0, y: 0, width: 100, diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts index 1ff35d7f1d6b2ad6bd40cd4e677e045dc1b4eb1b..1d71ac43429aa2ff8b7704cea75edd8ebc749522 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/CompartmentSquare.ts @@ -15,6 +15,7 @@ import Compartment from '@/components/Map/MapViewer/MapViewerVector/utils/shapes import { Color } from '@/types/models'; export type CompartmentSquareProps = { + id: number; x: number; y: number; width: number; @@ -40,6 +41,7 @@ export type CompartmentSquareProps = { export default class CompartmentSquare extends Compartment { constructor({ + id, x, y, width, @@ -63,6 +65,7 @@ export default class CompartmentSquare extends Compartment { mapInstance, }: CompartmentSquareProps) { super({ + id, x, y, width, diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts index 392ff91967766b5460a28be859deaa2b5964dc31..64c1f92d23ef03b84c8c1a39f226655f4fa2f189 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.test.ts @@ -39,6 +39,7 @@ describe('MapElement', () => { }), }); props = { + id: 1, shapes: shapesFixture, x: 0, y: 0, diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts index 2717f41ff4fe5e9a0e91f31b7887c3279f25e747..92798f3b7105f59cab6c1c9aea4197fe033db58e 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/MapElement.ts @@ -23,6 +23,7 @@ import getShapePolygon from '@/components/Map/MapViewer/MapViewerVector/utils/sh import { BioShapesDict, LineTypeDict } from '@/redux/shapes/shapes.types'; export type MapElementProps = { + id: number; shapes: Array<Shape>; x: number; y: number; @@ -71,6 +72,7 @@ export default class MapElement extends BaseMultiPolygon { lineDash: Array<number> = []; constructor({ + id, shapes, x, y, @@ -99,6 +101,8 @@ export default class MapElement extends BaseMultiPolygon { modifications = [], }: MapElementProps) { super({ + type: 'ALIAS', + id, x, y, width, diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.test.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.test.ts index f8c68fc15fa5ca8785aaaca1696b2c4f148a6f99..86025da4d8f4ddcf8e7a4dd7eeafe173c60eb3a2 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.test.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.test.ts @@ -25,6 +25,7 @@ describe('Layer', () => { }), }); props = { + id: 1, line: newReactionFixture.line, products: newReactionFixture.products, reactants: newReactionFixture.reactants, diff --git a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts index 56f57fe48f708d2b2e0a3c8ceb417f580a778bca..67c061ec82aa2a1d016c4df3ce85195bccfe0687 100644 --- a/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts +++ b/src/components/Map/MapViewer/MapViewerVector/utils/shapes/reaction/Reaction.ts @@ -17,6 +17,7 @@ import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shape import { ArrowTypeDict, LineTypeDict } from '@/redux/shapes/shapes.types'; export interface ReactionProps { + id: number; line: Line; products: Array<ReactionProduct>; reactants: Array<ReactionProduct>; @@ -31,6 +32,8 @@ export interface ReactionProps { } export default class Reaction { + id: number; + line: Line; products: Array<ReactionProduct>; @@ -56,6 +59,7 @@ export default class Reaction { features: Array<Feature> = []; constructor({ + id, line, products, reactants, @@ -68,6 +72,7 @@ export default class Reaction { pointToProjection, mapInstance, }: ReactionProps) { + this.id = id; this.line = line; this.products = products; this.reactants = reactants; @@ -198,6 +203,8 @@ export default class Reaction { geometry: lineString, style: lineStyle, lineWidth: line.width, + id: this.id, + type: 'REACTION', }); lineFeature.setStyle(this.getStyle.bind(this)); @@ -244,6 +251,8 @@ export default class Reaction { geometry: new MultiPolygon(polygons), style: styles, lineWidth: this.line.width, + id: this.id, + type: 'REACTION', }); squareFeature.setStyle(this.getStyle.bind(this)); return squareFeature; @@ -296,6 +305,8 @@ export default class Reaction { geometry: circle, style: circleStyle, lineWidth: 1, + id: this.id, + type: 'REACTION', }); circleFeature.setStyle(this.getStyle.bind(this)); return circleFeature; diff --git a/src/components/Map/MapViewer/utils/config/useOlMapCommonLayers.test.ts b/src/components/Map/MapViewer/utils/config/useOlMapCommonLayers.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..ded469b28450cc91d57fd1cc0410b380e9de994c --- /dev/null +++ b/src/components/Map/MapViewer/utils/config/useOlMapCommonLayers.test.ts @@ -0,0 +1,98 @@ +/* eslint-disable no-magic-numbers */ +import { MAP_DATA_INITIAL_STATE, OPENED_MAPS_INITIAL_STATE } from '@/redux/map/map.constants'; +import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; +import { renderHook } from '@testing-library/react'; +import BaseLayer from 'ol/layer/Base'; +import React from 'react'; +import VectorLayer from 'ol/layer/Vector'; +import { useOlMapCommonLayers } from '@/components/Map/MapViewer/utils/config/useOlMapCommonLayers'; + +const useRefValue = { + current: null, +}; + +Object.defineProperty(useRefValue, 'current', { + get: jest.fn(() => ({ + innerHTML: '', + appendChild: jest.fn(), + addEventListener: jest.fn(), + getRootNode: jest.fn(), + })), + set: jest.fn(() => ({ + innerHTML: '', + appendChild: jest.fn(), + addEventListener: jest.fn(), + getRootNode: jest.fn(), + })), +}); + +jest.spyOn(React, 'useRef').mockReturnValue(useRefValue); + +describe('useOlMapCommonLayers - util', () => { + const getRenderedHookResults = (): BaseLayer[] => { + const { Wrapper } = getReduxWrapperWithStore({ + map: { + data: { + ...MAP_DATA_INITIAL_STATE, + size: { + width: 256, + height: 256, + tileSize: 256, + minZoom: 1, + maxZoom: 1, + }, + position: { + initial: { + x: 256, + y: 256, + }, + last: { + x: 256, + y: 256, + }, + }, + }, + loading: 'idle', + error: { + name: '', + message: '', + }, + openedMaps: OPENED_MAPS_INITIAL_STATE, + }, + }); + + const { result } = renderHook(() => useOlMapCommonLayers(), { + wrapper: Wrapper, + }); + + return result.current; + }; + + it('should return valid VectorLayer instance [1]', () => { + const result = getRenderedHookResults(); + + expect(result[0]).toBeInstanceOf(VectorLayer); + expect(result[0].getSourceState()).toBe('ready'); + }); + + it('should return valid VectorLayer instance [2]', () => { + const result = getRenderedHookResults(); + + expect(result[1]).toBeInstanceOf(VectorLayer); + expect(result[1].getSourceState()).toBe('ready'); + }); + + it('should return valid VectorLayer instance [3]', () => { + const result = getRenderedHookResults(); + + expect(result[2]).toBeInstanceOf(VectorLayer); + expect(result[2].getSourceState()).toBe('ready'); + }); + + it('should return valid VectorLayer instance [4]', () => { + const result = getRenderedHookResults(); + + expect(result[3]).toBeInstanceOf(VectorLayer); + expect(result[3].getSourceState()).toBe('ready'); + }); +}); diff --git a/src/components/Map/MapViewer/utils/config/useOlMapCommonLayers.ts b/src/components/Map/MapViewer/utils/config/useOlMapCommonLayers.ts new file mode 100644 index 0000000000000000000000000000000000000000..bca964e7fd5f23b3ac90b2be4eb6e5e2640fb2f4 --- /dev/null +++ b/src/components/Map/MapViewer/utils/config/useOlMapCommonLayers.ts @@ -0,0 +1,15 @@ +/* eslint-disable no-magic-numbers */ +import { useOlMapOverlaysLayer } from '@/components/Map/MapViewer/utils/config/overlaysLayer/useOlMapOverlaysLayer'; +import { useOlMapPinsLayer } from '@/components/Map/MapViewer/utils/config/pinsLayer/useOlMapPinsLayer'; +import { useOlMapReactionsLayer } from '@/components/Map/MapViewer/utils/config/reactionsLayer/useOlMapReactionsLayer'; +import { useOlMapCommentsLayer } from '@/components/Map/MapViewer/utils/config/commentsLayer/useOlMapCommentsLayer'; +import { MapConfig } from '../../MapViewer.types'; + +export const useOlMapCommonLayers = (): MapConfig['layers'] => { + const overlaysLayer = useOlMapOverlaysLayer(); + const pinsLayer = useOlMapPinsLayer(); + const reactionsLayer = useOlMapReactionsLayer(); + const commentsLayer = useOlMapCommentsLayer(); + + return [overlaysLayer, pinsLayer, reactionsLayer, commentsLayer]; +}; diff --git a/src/components/Map/MapViewer/utils/config/useOlMapLayers.test.ts b/src/components/Map/MapViewer/utils/config/useOlMapLayers.test.ts index c8d29fa33d44ff5b00be2eac73e8d57364d47b12..c0be0af81ff66c3ec74e7d2ebb0825c3df17e38e 100644 --- a/src/components/Map/MapViewer/utils/config/useOlMapLayers.test.ts +++ b/src/components/Map/MapViewer/utils/config/useOlMapLayers.test.ts @@ -4,7 +4,6 @@ import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithSto import { renderHook } from '@testing-library/react'; import BaseLayer from 'ol/layer/Base'; import TileLayer from 'ol/layer/Tile'; -import VectorLayer from 'ol/layer/Vector'; import React from 'react'; import { useOlMapLayers } from './useOlMapLayers'; @@ -75,18 +74,4 @@ describe('useOlMapLayers - util', () => { expect(result[0]).toBeInstanceOf(TileLayer); expect(result[0].getSourceState()).toBe('ready'); }); - - it('should return valid VectorLayer instance [2]', () => { - const result = getRenderedHookResults(); - - expect(result[1]).toBeInstanceOf(VectorLayer); - expect(result[1].getSourceState()).toBe('ready'); - }); - - it('should return valid VectorLayer instance [3]', () => { - const result = getRenderedHookResults(); - - expect(result[2]).toBeInstanceOf(VectorLayer); - expect(result[2].getSourceState()).toBe('ready'); - }); }); diff --git a/src/components/Map/MapViewer/utils/config/useOlMapLayers.ts b/src/components/Map/MapViewer/utils/config/useOlMapLayers.ts index 925b92d842cafa41753999063738d69b13d886b0..f05f8c9d85e764a7074a9e35d98f5d695a65ae92 100644 --- a/src/components/Map/MapViewer/utils/config/useOlMapLayers.ts +++ b/src/components/Map/MapViewer/utils/config/useOlMapLayers.ts @@ -1,17 +1,9 @@ /* eslint-disable no-magic-numbers */ -import { useOlMapCommentsLayer } from '@/components/Map/MapViewer/utils/config/commentsLayer/useOlMapCommentsLayer'; import { MapConfig } from '../../MapViewer.types'; -import { useOlMapOverlaysLayer } from './overlaysLayer/useOlMapOverlaysLayer'; -import { useOlMapPinsLayer } from './pinsLayer/useOlMapPinsLayer'; -import { useOlMapReactionsLayer } from './reactionsLayer/useOlMapReactionsLayer'; import { useOlMapTileLayer } from './useOlMapTileLayer'; export const useOlMapLayers = (): MapConfig['layers'] => { const tileLayer = useOlMapTileLayer(); - const pinsLayer = useOlMapPinsLayer(); - const reactionsLayer = useOlMapReactionsLayer(); - const overlaysLayer = useOlMapOverlaysLayer(); - const commentsLayer = useOlMapCommentsLayer(); - return [tileLayer, pinsLayer, reactionsLayer, overlaysLayer, commentsLayer]; + return [tileLayer]; }; diff --git a/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts b/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts index aee769c396795820143e546a7a59d916b8a420be..7b1888d579e83e8bd13cba038ef84e99182ae3b5 100644 --- a/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts +++ b/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts @@ -99,7 +99,7 @@ export const useOlMapListeners = ({ view, mapInstance }: UseOlMapListenersInput) useEffect(() => { const key = view.on('change:center', handleChangeCenter); return () => unByKey(key); - }, [view, handleChangeCenter, vectorRendering]); + }, [view, handleChangeCenter]); useEffect(() => { if (!mapInstance || vectorRendering) { diff --git a/src/components/Map/MapViewer/utils/useOlMap.ts b/src/components/Map/MapViewer/utils/useOlMap.ts index 8cb003eef8ed44ec2226b6dfac062fb2200acd6d..de917a955e022296f2099303e948114ba2d62739 100644 --- a/src/components/Map/MapViewer/utils/useOlMap.ts +++ b/src/components/Map/MapViewer/utils/useOlMap.ts @@ -8,6 +8,8 @@ import LayerGroup from 'ol/layer/Group'; import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { vectorRenderingSelector } from '@/redux/models/models.selectors'; import { defaults, MouseWheelZoom } from 'ol/interaction'; +import { useOlMapVectorListeners } from '@/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners'; +import { useOlMapCommonLayers } from '@/components/Map/MapViewer/utils/config/useOlMapCommonLayers'; import { useOlMapLayers } from './config/useOlMapLayers'; import { useOlMapView } from './config/useOlMapView'; import { useOlMapListeners } from './listeners/useOlMapListeners'; @@ -34,13 +36,23 @@ export const useOlMap: UseOlMap = ({ target } = {}) => { layers: rasterLayers, }); }, [rasterLayers]); + const vectorLayers = useOlMapVectorLayers({ mapInstance }); const vectorLayersGroup = useMemo(() => { return new LayerGroup({ layers: vectorLayers, }); }, [vectorLayers]); + + const commonLayers = useOlMapCommonLayers(); + const commonLayersGroup = useMemo(() => { + return new LayerGroup({ + layers: commonLayers, + }); + }, [commonLayers]); + useOlMapListeners({ view, mapInstance }); + useOlMapVectorListeners({ mapInstance }); useEffect(() => { // checking if innerHTML is empty due to possibility of target element cloning by OpenLayers map instance @@ -74,8 +86,8 @@ export const useOlMap: UseOlMap = ({ target } = {}) => { if (!mapInstance) { return; } - mapInstance.setLayers([vectorLayersGroup, rasterLayersGroup]); - }, [mapInstance, rasterLayersGroup, vectorLayersGroup]); + mapInstance.setLayers([vectorLayersGroup, rasterLayersGroup, commonLayersGroup]); + }, [mapInstance, rasterLayersGroup, vectorLayersGroup, commonLayersGroup]); useEffect(() => { if (vectorRendering) {