diff --git a/CHANGELOG b/CHANGELOG index 417135f75f193e2f667b0193b8108f81689a404f..b1f11f84605d8452591eb2dbb2fe1dc2c71ea6a8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,42 +3,13 @@ minerva-front (19.0.0~alpha.0) stable; urgency=medium -- Piotr Gawron <piotr.gawron@uni.lu> Fri, 18 Oct 2024 13:00:00 +0200 -minerva-front (18.0.0~beta.5) stable; urgency=medium - * Small improvements: when ToS is defined ask user to accept it (#298) - * Small improvements: there is a waiting spinner after clicking on download - button (#297) - * Small improvements: when exporting map as image provide default selections - in for format and submap (#295) - * Small improvements: dropdown sections in exporting map as image are - unfolded permanently (#296) - * Bugfix: missing links added (#299) +-- Piotr Gawron <piotr.gawron@uni.lu> Wed, 02 Oct 2024 13:00:00 +0200 +minerva-front (18.0.0) stable; urgency=medium + * Bug fix: show cookie baner only when cookie baner link is provided (#304) - -- Piotr Gawron <piotr.gawron@uni.lu> Fri, 04 Oct 2024 13:00:00 +0200 + -- Piotr Gawron <piotr.gawron@uni.lu> Thu, 24 Oct 2024 13:00:00 +0200 -minerva-front (18.0.0~beta.4) stable; urgency=medium - * Bugfix: connectivity issue should report a problem with network instead of - submitting error report(#293) - * Bugfix: source map for js was missing (#292) - * Bugfix: sometimes project don't have link to disease or organism, this - crashed listing of projects after log in (#290) - * Bugfix: show proper message when there is a problem with overlay data - instead error report form (#291) - - -- Piotr Gawron <piotr.gawron@uni.lu> Wed, 02 Oct 2024 13:00:00 +0200 - -minerva-front (18.0.0~beta.3) stable; urgency=medium - * Bugfix: link to download project source was invalid - * Bugfix: change background to empty after overlay is loaded so there is no - blank background (#285) - * Bugfix: license info styling (#280) - * Bugfix: list of plugins did not contain version (#287) - - -- Piotr Gawron <piotr.gawron@uni.lu> Thu, 26 Sep 2024 13:00:00 +0200 - -minerva-front (18.0.0~beta.2) stable; urgency=medium +minerva-front (18.0.0) stable; urgency=medium * Feature: minerva frontend - first version - * Bugfix: when opening overlay provide loading info (#285) - * Bugfix: problem with reporting errors in minervanet (#284) - * Bugfix: license info styling (#284) - -- Piotr Gawron <piotr.gawron@uni.lu> Thu, 19 Sep 2024 13:00:00 +0200 + -- Piotr Gawron <piotr.gawron@uni.lu> Wed, 23 Oct 2024 13:00:00 +0200 diff --git a/src/components/FunctionalArea/CookieBanner/CookieBanner.component.test.tsx b/src/components/FunctionalArea/CookieBanner/CookieBanner.component.test.tsx index f4c495825584af08cf7ef7695cc7a969438a67df..c5a0ccdb795bd74fb8218c58685bb2fdef8bb34c 100644 --- a/src/components/FunctionalArea/CookieBanner/CookieBanner.component.test.tsx +++ b/src/components/FunctionalArea/CookieBanner/CookieBanner.component.test.tsx @@ -1,18 +1,24 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; -import { ToolkitStoreWithSingleSlice } from '@/utils/createStoreInstanceUsingSliceReducer'; -import { CookieBannerState } from '@/redux/cookieBanner/cookieBanner.types'; -import { getReduxWrapperUsingSliceReducer } from '@/utils/testing/getReduxWrapperUsingSliceReducer'; -import cookieBannerReducer from '@/redux/cookieBanner/cookieBanner.slice'; import { act } from 'react-dom/test-utils'; -import { CookieBanner } from './CookieBanner.component'; +import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; +import { INITIAL_STORE_STATE_MOCK } from '@/redux/root/root.fixtures'; +import { StoreType } from '@/redux/store'; +import { CONFIGURATION_INITIAL_STORE_MOCKS } from '@/redux/configuration/configuration.mock'; import { USER_ACCEPTED_COOKIES_COOKIE_NAME, USER_ACCEPTED_COOKIES_COOKIE_VALUE, } from './CookieBanner.constants'; +import { CookieBanner } from './CookieBanner.component'; -const renderComponent = (): { store: ToolkitStoreWithSingleSlice<CookieBannerState> } => { - const { Wrapper, store } = getReduxWrapperUsingSliceReducer('cookieBanner', cookieBannerReducer); +const renderComponent = (): { store: StoreType } => { + // const { Wrapper, store } = getReduxWrapperUsingSliceReducer('cookieBanner', cookieBannerReducer); + // + + const { Wrapper, store } = getReduxWrapperWithStore({ + ...INITIAL_STORE_STATE_MOCK, + configuration: CONFIGURATION_INITIAL_STORE_MOCKS, + }); return ( render( diff --git a/src/components/FunctionalArea/CookieBanner/CookieBanner.component.tsx b/src/components/FunctionalArea/CookieBanner/CookieBanner.component.tsx index f27ac87c1a7192929a5a7276e599aa8eb1c60b65..643ed796c87e8bb4b154f780fc75e2a8d5dd60e0 100644 --- a/src/components/FunctionalArea/CookieBanner/CookieBanner.component.tsx +++ b/src/components/FunctionalArea/CookieBanner/CookieBanner.component.tsx @@ -5,6 +5,7 @@ import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { Button } from '@/shared/Button'; import Link from 'next/link'; import { useEffect } from 'react'; +import { cookiePolicyUrlSelector } from '@/redux/configuration/configuration.selectors'; import { USER_ACCEPTED_COOKIES_COOKIE_NAME, USER_ACCEPTED_COOKIES_COOKIE_VALUE, @@ -13,6 +14,7 @@ import { export const CookieBanner = (): React.ReactNode => { const dispatch = useAppDispatch(); const { visible, accepted } = useAppSelector(selectCookieBanner); + const cookiePolicyUrl = useAppSelector(cookiePolicyUrlSelector); useEffect(() => { const isAccepted = @@ -33,7 +35,7 @@ export const CookieBanner = (): React.ReactNode => { ); }; - if (!visible || accepted) { + if (!visible || accepted || !cookiePolicyUrl) { return null; } @@ -43,7 +45,7 @@ export const CookieBanner = (): React.ReactNode => { <p className="my-4 leading-loose"> Minerva platform uses essential cookies to ensure its proper operation. For any queries in relation to our policy on cookies and your choices, please{' '} - <Link href="/" className="font-semibold text-[#1C00DE]"> + <Link href={cookiePolicyUrl} className="font-semibold text-[#1C00DE]"> read here </Link> </p> diff --git a/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.test.ts b/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.test.ts index bead3d12015b534bd89e5f5cc0beff50f93caeae..33216241a6256adb94cfb5414169ebcc5b6f0c25 100644 --- a/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.test.ts +++ b/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.test.ts @@ -47,7 +47,7 @@ describe('onMapRightClick - util', () => { minZoom: 2, maxZoom: 9, }; - const handler = onMapRightClick(mapSize, modelId, dispatch); + const handler = onMapRightClick(mapSize, modelId, dispatch, false); const coordinate = [90, 90]; const pixel = [250, 250]; @@ -74,7 +74,7 @@ describe('onMapRightClick - util', () => { minZoom: 2, maxZoom: 9, }; - const handler = onMapRightClick(mapSize, modelId, dispatch); + const handler = onMapRightClick(mapSize, modelId, dispatch, false); const coordinate = [90, 90]; const point = { x: 180.0008084837557, y: 179.99919151624428 }; const pixel = [250, 250]; @@ -110,7 +110,7 @@ describe('onMapRightClick - util', () => { .reply(HttpStatusCode.Ok, [ELEMENT_SEARCH_RESULT_MOCK_ALIAS]); it('does fire search result for right click action handler', async () => { - const handler = onMapRightClick(mapSize, modelId, dispatch); + const handler = onMapRightClick(mapSize, modelId, dispatch, false); await handler(coordinate, pixel); await waitFor(() => expect(handleSearchResultForRightClickActionSpy).toBeCalled()); }); @@ -139,7 +139,7 @@ describe('onMapRightClick - util', () => { .reply(HttpStatusCode.Ok, [ELEMENT_SEARCH_RESULT_MOCK_REACTION]); it('does fire search result for right click action - handle reaction', async () => { - const handler = onMapRightClick(mapSize, modelId, dispatch); + const handler = onMapRightClick(mapSize, modelId, dispatch, false); await handler(coordinate, pixel); await waitFor(() => expect(handleSearchResultForRightClickActionSpy).toBeCalled()); }); diff --git a/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.ts b/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.ts index d5d284892aeb7aeff9b263df4d8db19e93d50dfb..a52cd458507ab2dcf153c14a04acefdf7ae82498 100644 --- a/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.ts +++ b/src/components/Map/MapViewer/utils/listeners/mapRightClick/onMapRightClick.ts @@ -13,7 +13,8 @@ import { handleSearchResultForRightClickAction } from './handleSearchResultForRi /* prettier-ignore */ export const onMapRightClick = - (mapSize: MapSize, modelId: number, dispatch: AppDispatch) => async (coordinate: Coordinate, pixel: Pixel): Promise<void> => { + (mapSize: MapSize, modelId: number,dispatch: AppDispatch, shouldConsiderZoomLevel:boolean, + considerZoomLevel?:number, ) => async (coordinate: Coordinate, pixel: Pixel): Promise<void> => { const [lng, lat] = toLonLat(coordinate); const point = latLngToPoint([lat, lng], mapSize); @@ -22,7 +23,7 @@ export const onMapRightClick = dispatch(handleDataReset); dispatch(openContextMenu(pixel)); - const { searchResults } = await getSearchResults({ coordinate, mapSize, modelId }); + const { searchResults } = await getSearchResults({ coordinate, mapSize, modelId, shouldConsiderZoomLevel, considerZoomLevel }); if (!searchResults || searchResults.length === SIZE_OF_EMPTY_ARRAY) { return; } diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.test.ts index 996d2d1c32589c53684be6a25e362289b0ca9177..0fa4d06f1128a897b2efb484962d6342d05cb06c 100644 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.test.ts +++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.test.ts @@ -34,6 +34,7 @@ describe('getSearchResults - util', () => { coordinate, mapSize, modelId, + shouldConsiderZoomLevel: false, }); expect(result).toEqual({ @@ -66,6 +67,7 @@ describe('getSearchResults - util', () => { coordinate, mapSize, modelId, + shouldConsiderZoomLevel: false, }); expect(result).toEqual({ @@ -100,6 +102,7 @@ describe('getSearchResults - util', () => { coordinate, mapSize, modelId, + shouldConsiderZoomLevel: false, }); expect(result).toEqual({ diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.ts index 0fedae9f89f1f2c788b52e133b64b58b1e5f7623..f86ee0fe825823f7b186616676c4fa3efc700388 100644 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.ts +++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/getSearchResults.ts @@ -10,19 +10,28 @@ interface GetSearchResultsInput { coordinate: Coordinate; mapSize: MapSize; modelId: number; + shouldConsiderZoomLevel: boolean; + considerZoomLevel?: number; } export const getSearchResults = async ({ coordinate, mapSize, modelId, + shouldConsiderZoomLevel, + considerZoomLevel, }: GetSearchResultsInput): Promise<{ searchResults: ElementSearchResult[] | undefined; point: Point; }> => { const [lng, lat] = toLonLat(coordinate); const point = latLngToPoint([lat, lng], mapSize); - const searchResults = await getElementsByPoint({ point, currentModelId: modelId }); + const searchResults = await getElementsByPoint({ + point, + currentModelId: modelId, + shouldConsiderZoomLevel, + considerZoomLevel, + }); return { searchResults, diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.test.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.test.ts index 138ab32fd28777c55abbf9a0b09c972e34b883e4..b91b2b33c59d4b6b3532edf5bf3fcbc43c31f995 100644 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.test.ts +++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.test.ts @@ -68,6 +68,7 @@ describe('onMapSingleClick - util', () => { ZOOM_MOCK, IS_RESULT_DRAWER_OPEN_MOCK, [], + false, ); const coordinate = [90, 90]; const event = getEvent(coordinate); @@ -102,6 +103,7 @@ describe('onMapSingleClick - util', () => { ZOOM_MOCK, IS_RESULT_DRAWER_OPEN_MOCK, [], + false, ); const coordinate = [90, 90]; const point = { x: 180.0008084837557, y: 179.99919151624428 }; @@ -143,6 +145,7 @@ describe('onMapSingleClick - util', () => { ZOOM_MOCK, IS_RESULT_DRAWER_OPEN_MOCK, [], + false, ); const coordinate = [180, 180]; const point = { x: 360.0032339350228, y: 359.9967660649771 }; @@ -202,6 +205,7 @@ describe('onMapSingleClick - util', () => { ZOOM_MOCK, IS_RESULT_DRAWER_OPEN_MOCK, [], + false, ); await handler(event, mapInstanceMock); await waitFor(() => expect(handleSearchResultActionSpy).not.toBeCalled()); @@ -242,6 +246,7 @@ describe('onMapSingleClick - util', () => { ZOOM_MOCK, IS_RESULT_DRAWER_OPEN_MOCK, [], + false, ); await handler(event, mapInstanceMock); await waitFor(() => expect(handleSearchResultActionSpy).toBeCalled()); @@ -284,6 +289,7 @@ describe('onMapSingleClick - util', () => { ZOOM_MOCK, IS_RESULT_DRAWER_OPEN_MOCK, [], + false, ); await handler(event, mapInstanceMock); await waitFor(() => expect(handleSearchResultActionSpy).toBeCalled()); diff --git a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.ts b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.ts index bc86c2326c8b0d31c21e7d2f0839d9b1332f4cba..04908fba221a40849ca9a55175f7c2e35520e819 100644 --- a/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.ts +++ b/src/components/Map/MapViewer/utils/listeners/mapSingleClick/onMapSingleClick.ts @@ -15,7 +15,9 @@ import { handleSearchResultAction } from './handleSearchResultAction'; /* prettier-ignore */ export const onMapSingleClick = (mapSize: MapSize, modelId: number, dispatch: AppDispatch, searchDistance: string | undefined, maxZoom: number, zoom: number, isResultDrawerOpen: boolean, - comments: Comment[]) => + comments: Comment[], shouldConsiderZoomLevel:boolean, + considerZoomLevel?:number, + ) => async ({ coordinate, pixel }: Pick<MapBrowserEvent<UIEvent>, 'coordinate' | 'pixel'>, mapInstance: Map): Promise<void> => { const [lng, lat] = toLonLat(coordinate); const point = latLngToPoint([lat, lng], mapSize); @@ -34,7 +36,9 @@ export const onMapSingleClick = // so we need to reset all the data before updating dispatch(handleDataReset); - const {searchResults} = await getSearchResults({ coordinate, mapSize, modelId }); + const {searchResults} = await getSearchResults({ coordinate, mapSize, modelId, shouldConsiderZoomLevel, + considerZoomLevel, + }); if (!searchResults || searchResults.length === SIZE_OF_EMPTY_ARRAY) { return; } diff --git a/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts b/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts index c0dbb4b334030bedc41334617286d886b64286d0..aee769c396795820143e546a7a59d916b8a420be 100644 --- a/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts +++ b/src/components/Map/MapViewer/utils/listeners/useOlMapListeners.ts @@ -7,7 +7,11 @@ import { mapDataMaxZoomValue, mapDataSizeSelector, } from '@/redux/map/map.selectors'; -import { currentModelIdSelector, vectorRenderingSelector } from '@/redux/models/models.selectors'; +import { + currentModelSelector, + currentModelIdSelector, + vectorRenderingSelector, +} from '@/redux/models/models.selectors'; import { MapInstance } from '@/types/map'; import { View } from 'ol'; import { unByKey } from 'ol/Observable'; @@ -17,6 +21,12 @@ import { useEffect, useRef } from 'react'; import { useSelector } from 'react-redux'; import { useDebouncedCallback } from 'use-debounce'; import { allCommentsSelectorOfCurrentMap } from '@/redux/comment/comment.selectors'; +import { currentBackgroundSelector } from '@/redux/backgrounds/background.selectors'; +import { + PATHWAYS_AND_COMPARTMENTS_BACKGROUND, + SEMANTIC_BACKGROUND, +} from '@/redux/backgrounds/backgrounds.constants'; +import { TWO } from '@/constants/common'; import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { onMapRightClick } from './mapRightClick/onMapRightClick'; import { onMapSingleClick } from './mapSingleClick/onMapSingleClick'; @@ -31,10 +41,13 @@ interface UseOlMapListenersInput { export const useOlMapListeners = ({ view, mapInstance }: UseOlMapListenersInput): void => { const mapSize = useSelector(mapDataSizeSelector); + const model = useSelector(currentModelSelector); const modelId = useSelector(currentModelIdSelector); + const modelMinZoom = model?.minZoom || TWO; + const lastZoom = useSelector(mapDataLastZoomValue) || TWO; + const background = useSelector(currentBackgroundSelector); const searchDistance = useSelector(searchDistanceValSelector); const maxZoom = useSelector(mapDataMaxZoomValue); - const lastZoom = useSelector(mapDataLastZoomValue); const isResultDrawerOpen = useSelector(resultDrawerOpen); const coordinate = useRef<Coordinate>([]); const pixel = useRef<Pixel>([]); @@ -45,7 +58,14 @@ export const useOlMapListeners = ({ view, mapInstance }: UseOlMapListenersInput) useHandlePinIconClick(); const handleRightClick = useDebouncedCallback( - onMapRightClick(mapSize, modelId, dispatch), + onMapRightClick( + mapSize, + modelId, + dispatch, + background?.name === SEMANTIC_BACKGROUND || + background?.name === PATHWAYS_AND_COMPARTMENTS_BACKGROUND, + lastZoom - modelMinZoom, + ), OPTIONS.clickPersistTime, { leading: false, @@ -68,6 +88,9 @@ export const useOlMapListeners = ({ view, mapInstance }: UseOlMapListenersInput) lastZoom || DEFAULT_ZOOM, isResultDrawerOpen, comments, + background?.name === SEMANTIC_BACKGROUND || + background?.name === PATHWAYS_AND_COMPARTMENTS_BACKGROUND, + lastZoom - modelMinZoom, ), OPTIONS.clickPersistTime, { leading: false }, diff --git a/src/components/Map/MapViewer/utils/useOlMap.ts b/src/components/Map/MapViewer/utils/useOlMap.ts index 5244411aa621727be223cde1628d82c7d6817d7e..f65c7f04551ba952e7be17cf396a0fa4a1edb2d7 100644 --- a/src/components/Map/MapViewer/utils/useOlMap.ts +++ b/src/components/Map/MapViewer/utils/useOlMap.ts @@ -42,7 +42,7 @@ export const useOlMap: UseOlMap = ({ target } = {}) => { useOlMapListeners({ view, mapInstance }); useEffect(() => { - // checking if innerHTML is empty due to possibility of target element cloning by openlayers map instance + // checking if innerHTML is empty due to possibility of target element cloning by OpenLayers map instance if (!mapRef.current || mapRef.current.innerHTML !== '') { return; } diff --git a/src/models/mocks/configurationOptionMock.ts b/src/models/mocks/configurationOptionMock.ts index f7a293f7592e80217573eef6dab39dc5a1038e87..16fb91c7fd1f4bafa5816bf2932f71ea3014be0b 100644 --- a/src/models/mocks/configurationOptionMock.ts +++ b/src/models/mocks/configurationOptionMock.ts @@ -1,12 +1,22 @@ import { ConfigurationOption } from '@/types/models'; +import { + COOKIE_POLICY_URL, + MAX_COLOR_VAL_NAME_ID, + MIN_COLOR_VAL_NAME_ID, + NEUTRAL_COLOR_VAL_NAME_ID, + OVERLAY_OPACITY_NAME_ID, + SEARCH_DISTANCE_NAME_ID, + SIMPLE_COLOR_VAL_NAME_ID, +} from '@/redux/configuration/configuration.constants'; export const CONFIGURATION_OPTIONS_TYPES_MOCK: string[] = [ - 'MIN_COLOR_VAL', - 'MAX_COLOR_VAL', - 'SIMPLE_COLOR_VAL', - 'NEUTRAL_COLOR_VAL', - 'OVERLAY_OPACITY', - 'SEARCH_DISTANCE', + MIN_COLOR_VAL_NAME_ID, + MAX_COLOR_VAL_NAME_ID, + SIMPLE_COLOR_VAL_NAME_ID, + NEUTRAL_COLOR_VAL_NAME_ID, + OVERLAY_OPACITY_NAME_ID, + SEARCH_DISTANCE_NAME_ID, + COOKIE_POLICY_URL, ]; export const CONFIGURATION_OPTIONS_COLOURS_MOCK: ConfigurationOption[] = [ @@ -64,4 +74,13 @@ export const CONFIGURATION_OPTIONS_COLOURS_MOCK: ConfigurationOption[] = [ value: '10', group: 'Point and click', }, + { + idObject: 41, + type: 'COOKIE_POLICY_URL', + valueType: 'URL', + commonName: 'Privacy policy (url)', + isServerSide: false, + value: 'default-cookie-policy.xhtml', + group: 'Server configuration', + }, ]; diff --git a/src/redux/configuration/configuration.constants.ts b/src/redux/configuration/configuration.constants.ts index 64cad84b74a151e569c9e7f02dd51ef0dc29a06e..7fa5ef0ce9efa4148e36d40964f020d7492c4cca 100644 --- a/src/redux/configuration/configuration.constants.ts +++ b/src/redux/configuration/configuration.constants.ts @@ -7,6 +7,7 @@ export const SEARCH_DISTANCE_NAME_ID = 'SEARCH_DISTANCE'; export const REQUEST_ACCOUNT_EMAIL = 'REQUEST_ACCOUNT_EMAIL'; export const TERMS_OF_SERVICE_ID = 'TERMS_OF_USE'; export const MATOMO_URL = 'MATOMO_URL'; +export const COOKIE_POLICY_URL = 'COOKIE_POLICY_URL'; export const LEGEND_FILE_NAMES_IDS = [ 'LEGEND_FILE_1', diff --git a/src/redux/configuration/configuration.mock.ts b/src/redux/configuration/configuration.mock.ts index 07ee6ea998096fcad4a62772d50845021e074e18..4edae637b04ab9f9d37eb37bd1c08a7553958955 100644 --- a/src/redux/configuration/configuration.mock.ts +++ b/src/redux/configuration/configuration.mock.ts @@ -30,6 +30,8 @@ export const CONFIGURATION_INITIAL_STORE_MOCKS: ConfigurationState = { [CONFIGURATION_OPTIONS_TYPES_MOCK[2]]: CONFIGURATION_OPTIONS_COLOURS_MOCK[2], [CONFIGURATION_OPTIONS_TYPES_MOCK[3]]: CONFIGURATION_OPTIONS_COLOURS_MOCK[3], [CONFIGURATION_OPTIONS_TYPES_MOCK[4]]: CONFIGURATION_OPTIONS_COLOURS_MOCK[4], + [CONFIGURATION_OPTIONS_TYPES_MOCK[5]]: CONFIGURATION_OPTIONS_COLOURS_MOCK[5], + [CONFIGURATION_OPTIONS_TYPES_MOCK[6]]: CONFIGURATION_OPTIONS_COLOURS_MOCK[6], }, loading: 'idle', error: DEFAULT_ERROR, diff --git a/src/redux/configuration/configuration.selectors.ts b/src/redux/configuration/configuration.selectors.ts index 176847d40d89b9e31dd50f1fe1e8fb3657c14b94..01a9eb4c2d3437da7a252dc310d1353b020b490a 100644 --- a/src/redux/configuration/configuration.selectors.ts +++ b/src/redux/configuration/configuration.selectors.ts @@ -19,6 +19,7 @@ import { SEARCH_DISTANCE_NAME_ID, REQUEST_ACCOUNT_EMAIL, TERMS_OF_SERVICE_ID, + COOKIE_POLICY_URL, } from './configuration.constants'; import { ConfigurationHandlersIds, ConfigurationImageHandlersIds } from './configuration.types'; @@ -69,6 +70,11 @@ export const termsOfServiceValSelector = createSelector( state => configurationAdapterSelectors.selectById(state, TERMS_OF_SERVICE_ID)?.value, ); +export const cookiePolicyUrlSelector = createSelector( + configurationOptionsSelector, + state => configurationAdapterSelectors.selectById(state, COOKIE_POLICY_URL)?.value, +); + export const defaultLegendImagesSelector = createSelector(configurationOptionsSelector, state => LEGEND_FILE_NAMES_IDS.map( legendNameId => configurationAdapterSelectors.selectById(state, legendNameId)?.value, diff --git a/src/services/pluginsManager/map/triggerSearch/searchByCoordinates.ts b/src/services/pluginsManager/map/triggerSearch/searchByCoordinates.ts index 207c91c02692e871a917dd463f7e5e111e5eb21f..6b7a9b9df402651327afef1f245c34fe9f747679 100644 --- a/src/services/pluginsManager/map/triggerSearch/searchByCoordinates.ts +++ b/src/services/pluginsManager/map/triggerSearch/searchByCoordinates.ts @@ -12,8 +12,10 @@ import { Coordinates } from './triggerSearch.types'; export const searchByCoordinates = async ( coordinates: Coordinates, modelId: number, + shouldConsiderZoomLevel: boolean, hasFitBounds?: boolean, fitBoundsZoom?: number, + considerZoomLevel?: number, ): Promise<void> => { const { dispatch, getState } = store; // side-effect below is to prevent complications with data update - old data may conflict with new data @@ -28,6 +30,8 @@ export const searchByCoordinates = async ( const searchResults = await getElementsByPoint({ point: coordinates, currentModelId: modelId, + shouldConsiderZoomLevel, + considerZoomLevel, }); if (!searchResults || searchResults?.length === SIZE_OF_EMPTY_ARRAY) { diff --git a/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts b/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts index bdec855900cbde830daca6eb1343a424e40087fc..abb470aa4b1fc5e1738a4b2962b0b36b2c168431 100644 --- a/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts +++ b/src/services/pluginsManager/map/triggerSearch/triggerSearch.test.ts @@ -197,7 +197,7 @@ describe('triggerSearch', () => { x: 545.8013, y: 500.9926, }, - searchDistance: undefined, + searchDistance: '10', zoom: 5, }); }); diff --git a/src/services/pluginsManager/map/triggerSearch/triggerSearch.ts b/src/services/pluginsManager/map/triggerSearch/triggerSearch.ts index 8a69f07b6570907182960db7bd2e770d5b6e6644..363a01553ff227d30676d7384e0b3fc91265fd6b 100644 --- a/src/services/pluginsManager/map/triggerSearch/triggerSearch.ts +++ b/src/services/pluginsManager/map/triggerSearch/triggerSearch.ts @@ -28,6 +28,6 @@ export async function triggerSearch(params: SearchParams): Promise<void> { throw new Error(ERROR_INVALID_MODEL_ID_TYPE); } - searchByCoordinates(params.coordinates, params.modelId, params.fitBounds, params.zoom); + searchByCoordinates(params.coordinates, params.modelId, false, params.fitBounds, params.zoom); } } diff --git a/src/utils/search/getElementsByCoordinates.test.ts b/src/utils/search/getElementsByCoordinates.test.ts index cf2a56fa4b8be87addbede3d14a1971310bf5f7f..d24ece7cd88fbe2fdd5f6f6a9b62f229ea668a6c 100644 --- a/src/utils/search/getElementsByCoordinates.test.ts +++ b/src/utils/search/getElementsByCoordinates.test.ts @@ -18,7 +18,11 @@ describe('getElementsByPoint - utils', () => { .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, currentModelId)) .reply(HttpStatusCode.Ok, elementSearchResultFixture); - const response = await getElementsByPoint({ point, currentModelId }); + const response = await getElementsByPoint({ + point, + currentModelId, + shouldConsiderZoomLevel: false, + }); expect(response).toEqual(elementSearchResultFixture); }); @@ -27,7 +31,11 @@ describe('getElementsByPoint - utils', () => { .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, currentModelId)) .reply(HttpStatusCode.Ok, { randomProperty: 'randomValue' }); - const response = await getElementsByPoint({ point, currentModelId }); + const response = await getElementsByPoint({ + point, + currentModelId, + shouldConsiderZoomLevel: false, + }); expect(response).toEqual(undefined); }); @@ -36,7 +44,11 @@ describe('getElementsByPoint - utils', () => { .onGet(apiPath.getSingleBioEntityContentsStringWithCoordinates(point, currentModelId)) .reply(HttpStatusCode.Ok, []); - const response = await getElementsByPoint({ point, currentModelId }); + const response = await getElementsByPoint({ + point, + currentModelId, + shouldConsiderZoomLevel: false, + }); expect(response).toEqual([]); }); }); diff --git a/src/utils/search/getElementsByCoordinates.ts b/src/utils/search/getElementsByCoordinates.ts index af110ac0f2bf6e3813d09daa697427bf445a93da..70e392de47fcf2be6aabd9ee800069b60c682a58 100644 --- a/src/utils/search/getElementsByCoordinates.ts +++ b/src/utils/search/getElementsByCoordinates.ts @@ -1,25 +1,97 @@ import { elementSearchResult } from '@/models/elementSearchResult'; import { apiPath } from '@/redux/apiPath'; -import { axiosInstance } from '@/services/api/utils/axiosInstance'; +import { axiosInstance, axiosInstanceNewAPI } from '@/services/api/utils/axiosInstance'; import { Point } from '@/types/map'; -import { ElementSearchResult } from '@/types/models'; +import { BioEntity, ElementSearchResult } from '@/types/models'; import { z } from 'zod'; +import { ONE, ZERO } from '@/constants/common'; import { validateDataUsingZodSchema } from '../validateDataUsingZodSchema'; +interface FirstVisibleParentArgs { + bioEntity: BioEntity; + considerZoomLevel: number; +} + +export const getFirstVisibleParent = async ({ + bioEntity, + considerZoomLevel, +}: FirstVisibleParentArgs): Promise<BioEntity> => { + let parentId = bioEntity.complex; + if (!parentId) { + parentId = bioEntity.compartment; + } + if (parentId) { + const parentResponse = await axiosInstanceNewAPI.get<BioEntity>( + apiPath.getElementById(parentId, bioEntity.model), + ); + const parent = parentResponse.data; + if (parseInt(parent.visibilityLevel, 10) > Math.ceil(considerZoomLevel)) { + return getFirstVisibleParent({ + bioEntity: parent, + considerZoomLevel, + }); + } + return parent; + } + // eslint-disable-next-line no-console + console.log(`Cannot find visible parent for object. (zoomLevel=${considerZoomLevel})`, bioEntity); + return bioEntity; +}; + interface Args { point: Point; currentModelId: number; + shouldConsiderZoomLevel: boolean; + considerZoomLevel?: number; } +const FRACTIONAL_ZOOM_AT_WHICH_IMAGE_LAYER_CHANGE = 0.415; + export const getElementsByPoint = async ({ point, currentModelId, + shouldConsiderZoomLevel, + considerZoomLevel, }: Args): Promise<ElementSearchResult[] | undefined> => { + let result: ElementSearchResult[]; const response = await axiosInstance.get<ElementSearchResult[]>( apiPath.getSingleBioEntityContentsStringWithCoordinates(point, currentModelId), ); - const isDataValid = validateDataUsingZodSchema(response.data, z.array(elementSearchResult)); - return isDataValid ? response.data : undefined; + if (!isDataValid) { + return undefined; + } + result = response.data; + + if (shouldConsiderZoomLevel && result.length > ZERO && result[ZERO].type === 'ALIAS') { + const elementResponse = await axiosInstanceNewAPI.get<BioEntity>( + apiPath.getElementById(result[ZERO].id, result[ZERO].modelId), + ); + const element = elementResponse.data; + if ( + parseInt(element.visibilityLevel, 10) - (ONE - FRACTIONAL_ZOOM_AT_WHICH_IMAGE_LAYER_CHANGE) > + (considerZoomLevel || Number.MAX_SAFE_INTEGER) + ) { + const visibleParent = await getFirstVisibleParent({ + bioEntity: element, + considerZoomLevel: considerZoomLevel || Number.MAX_SAFE_INTEGER, + }); + let id: number; + if (typeof visibleParent.id === 'string') { + id = parseInt(visibleParent.id, 10); + } else { + id = visibleParent.id; + } + result = [ + { + id, + type: 'ALIAS', + modelId: visibleParent.model, + }, + ]; + } + } + + return result; };