diff --git a/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/OverlayListItem.component.test.tsx b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/OverlayListItem.component.test.tsx index 21e54427711c91ac3d689caeb5a9a89a11ee6594..dd3cc6eaab9b49e7ce522f2b28f833d29d3f1bca 100644 --- a/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/OverlayListItem.component.test.tsx +++ b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/OverlayListItem.component.test.tsx @@ -1,11 +1,28 @@ import { StoreType } from '@/redux/store'; -import { render, screen } from '@testing-library/react'; +import { act, render, screen } from '@testing-library/react'; import { InitialStoreState, getReduxWrapperWithStore, } from '@/utils/testing/getReduxWrapperWithStore'; +import { + BACKGROUNDS_MOCK, + BACKGROUND_INITIAL_STATE_MOCK, +} from '@/redux/backgrounds/background.mock'; +import { initialMapStateFixture } from '@/redux/map/map.fixtures'; +import { mockNetworkNewAPIResponse } from '@/utils/mockNetworkResponse'; +import { OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK } from '@/redux/overlayBioEntity/overlayBioEntity.mock'; +import { HttpStatusCode } from 'axios'; +import { overlayBioEntityFixture } from '@/models/fixtures/overlayBioEntityFixture'; +import { apiPath } from '@/redux/apiPath'; +import { CORE_PD_MODEL_MOCK } from '@/models/mocks/modelsMock'; +import { MODELS_INITIAL_STATE_MOCK } from '@/redux/models/models.mock'; +import { parseOverlayBioEntityToOlRenderingFormat } from '@/redux/overlayBioEntity/overlayBioEntity.utils'; import { OverlayListItem } from './OverlayListItem.component'; +const mockedAxiosNewClient = mockNetworkNewAPIResponse(); +const DEFAULT_BACKGROUND_ID = 0; +const EMPTY_BACKGROUND_ID = 15; + const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => { const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState); @@ -29,8 +46,31 @@ describe('OverlayListItem - component', () => { expect(screen.getByRole('button', { name: 'View' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Download' })).toBeInTheDocument(); }); - // TODO implement when connecting logic to component - it.skip('should trigger view overlays on view button click', () => {}); + + it('should trigger view overlays on view button click and switch background to Empty if available', async () => { + const OVERLAY_ID = 21; + const { store } = renderComponent({ + map: initialMapStateFixture, + backgrounds: { ...BACKGROUND_INITIAL_STATE_MOCK, data: BACKGROUNDS_MOCK }, + overlayBioEntity: OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK, + models: { ...MODELS_INITIAL_STATE_MOCK, data: [CORE_PD_MODEL_MOCK] }, + }); + mockedAxiosNewClient + .onGet(apiPath.getOverlayBioEntity({ overlayId: OVERLAY_ID, modelId: 5053 })) + .reply(HttpStatusCode.Ok, overlayBioEntityFixture); + + expect(store.getState().map.data.backgroundId).toBe(DEFAULT_BACKGROUND_ID); + + const ViewButton = screen.getByRole('button', { name: 'View' }); + await act(() => { + ViewButton.click(); + }); + + expect(store.getState().map.data.backgroundId).toBe(EMPTY_BACKGROUND_ID); + expect(store.getState().overlayBioEntity.data).toEqual( + parseOverlayBioEntityToOlRenderingFormat(overlayBioEntityFixture, OVERLAY_ID), + ); + }); // TODO implement when connecting logic to component it.skip('should trigger download overlay to PC on download button click', () => {}); }); diff --git a/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/OverlayListItem.component.tsx b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/OverlayListItem.component.tsx index b77463860a142d0266331ddeaab7eb59463faa03..20f173fe7eb91fc7cf480e7cab43a30bc0a0187e 100644 --- a/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/OverlayListItem.component.tsx +++ b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/OverlayListItem.component.tsx @@ -1,6 +1,7 @@ import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; import { getOverlayBioEntityForAllModels } from '@/redux/overlayBioEntity/overlayBioEntity.thunk'; import { Button } from '@/shared/Button'; +import { useEmptyBackground } from './hooks/useEmptyBackground'; interface OverlayListItemProps { name: string; @@ -10,8 +11,10 @@ interface OverlayListItemProps { export const OverlayListItem = ({ name, overlayId }: OverlayListItemProps): JSX.Element => { const onDownloadOverlay = (): void => {}; const dispatch = useAppDispatch(); + const { setBackgroundtoEmptyIfAvailable } = useEmptyBackground(); const onViewOverlay = (): void => { + setBackgroundtoEmptyIfAvailable(); dispatch(getOverlayBioEntityForAllModels({ overlayId })); }; diff --git a/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/hooks/useEmptyBackground.test.ts b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/hooks/useEmptyBackground.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..9c58dfc3baa4207cb6af954a6662ddeb0a16ea92 --- /dev/null +++ b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/hooks/useEmptyBackground.test.ts @@ -0,0 +1,43 @@ +import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; +import { initialMapStateFixture } from '@/redux/map/map.fixtures'; +import { + BACKGROUNDS_MOCK, + BACKGROUND_INITIAL_STATE_MOCK, +} from '@/redux/backgrounds/background.mock'; +import { renderHook } from '@testing-library/react'; +import { useEmptyBackground } from './useEmptyBackground'; + +const DEFAULT_BACKGROUND_ID = 0; +const EMPTY_BACKGROUND_ID = 15; + +describe('useEmptyBackground - hook', () => { + describe('returns setEmptyBackground function', () => { + it('should not set background to "Empty" if its not available', () => { + const { Wrapper, store } = getReduxWrapperWithStore({ + map: initialMapStateFixture, + backgrounds: BACKGROUND_INITIAL_STATE_MOCK, + }); + const { result } = renderHook(() => useEmptyBackground(), { wrapper: Wrapper }); + + expect(store.getState().map.data.backgroundId).toBe(DEFAULT_BACKGROUND_ID); + + result.current.setBackgroundtoEmptyIfAvailable(); + + expect(store.getState().map.data.backgroundId).toBe(DEFAULT_BACKGROUND_ID); + }); + + it('should set background to "Empty" if its available', () => { + const { Wrapper, store } = getReduxWrapperWithStore({ + map: initialMapStateFixture, + backgrounds: { ...BACKGROUND_INITIAL_STATE_MOCK, data: BACKGROUNDS_MOCK }, + }); + const { result } = renderHook(() => useEmptyBackground(), { wrapper: Wrapper }); + + expect(store.getState().map.data.backgroundId).toBe(DEFAULT_BACKGROUND_ID); + + result.current.setBackgroundtoEmptyIfAvailable(); + + expect(store.getState().map.data.backgroundId).toBe(EMPTY_BACKGROUND_ID); + }); + }); +}); diff --git a/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/hooks/useEmptyBackground.ts b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/hooks/useEmptyBackground.ts new file mode 100644 index 0000000000000000000000000000000000000000..2536fa84e049dd7dc659040a96933d3287639436 --- /dev/null +++ b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/hooks/useEmptyBackground.ts @@ -0,0 +1,22 @@ +import { useCallback } from 'react'; +import { emptyBackgroundIdSelector } from '@/redux/backgrounds/background.selectors'; +import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; +import { useAppSelector } from '@/redux/hooks/useAppSelector'; +import { setMapBackground } from '@/redux/map/map.slice'; + +type UseEmptyBackgroundReturn = { + setBackgroundtoEmptyIfAvailable: () => void; +}; + +export const useEmptyBackground = (): UseEmptyBackgroundReturn => { + const dispatch = useAppDispatch(); + const emptyBackgroundId = useAppSelector(emptyBackgroundIdSelector); + + const setBackgroundtoEmptyIfAvailable = useCallback(() => { + if (emptyBackgroundId) { + dispatch(setMapBackground(emptyBackgroundId)); + } + }, [dispatch, emptyBackgroundId]); + + return { setBackgroundtoEmptyIfAvailable }; +}; diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayGeometryFeature.test.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayGeometryFeature.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..8ee122219c608c7e6bedd00e1fd1e269e54255b1 --- /dev/null +++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayGeometryFeature.test.ts @@ -0,0 +1,49 @@ +import { createOverlayGeometryFeature } from './createOverlayGeometryFeature'; + +describe('createOverlayGeometryFeature', () => { + it('should create a feature with the correct geometry and style', () => { + const xMin = 0; + const yMin = 0; + const xMax = 10; + const yMax = 10; + const colorHexString = '#FF0000'; + + const feature = createOverlayGeometryFeature([xMin, yMin, xMax, yMax], colorHexString); + + expect(feature.getGeometry()!.getCoordinates()).toEqual([ + [ + [xMin, yMin], + [xMin, yMax], + [xMax, yMax], + [xMax, yMin], + [xMin, yMin], + ], + ]); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - getStyle() is not typed + expect(feature.getStyle().getFill().getColor()).toEqual(colorHexString); + }); + + it('should create a feature with the correct geometry and style when using a different color', () => { + const xMin = -5; + const yMin = -5; + const xMax = 5; + const yMax = 5; + const colorHexString = '#00FF00'; + + const feature = createOverlayGeometryFeature([xMin, yMin, xMax, yMax], colorHexString); + + expect(feature.getGeometry()!.getCoordinates()).toEqual([ + [ + [xMin, yMin], + [xMin, yMax], + [xMax, yMax], + [xMax, yMin], + [xMin, yMin], + ], + ]); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - getStyle() is not typed + expect(feature.getStyle().getFill().getColor()).toEqual(colorHexString); + }); +}); diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayGeometryFeature.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayGeometryFeature.ts new file mode 100644 index 0000000000000000000000000000000000000000..90887721e354c6af5a68d1368cc74ee74d86181f --- /dev/null +++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/createOverlayGeometryFeature.ts @@ -0,0 +1,13 @@ +import { Fill, Style } from 'ol/style'; +import { fromExtent } from 'ol/geom/Polygon'; +import Feature from 'ol/Feature'; +import type Polygon from 'ol/geom/Polygon'; + +export const createOverlayGeometryFeature = ( + [xMin, yMin, xMax, yMax]: number[], + color: string, +): Feature<Polygon> => { + const feature = new Feature({ geometry: fromExtent([xMin, yMin, xMax, yMax]) }); + feature.setStyle(new Style({ fill: new Fill({ color }) })); + return feature; +}; diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/getColorByAvailableProperties.test.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/getColorByAvailableProperties.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..6b71e37ee7f09fe9fb74e4aab6a6e9bc7bf420a0 --- /dev/null +++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/getColorByAvailableProperties.test.ts @@ -0,0 +1,73 @@ +import { OverlayBioEntityRender } from '@/types/OLrendering'; +import { getColorByAvailableProperties } from './getColorByAvailableProperties'; + +describe('getColorByAvailableProperties', () => { + const ENTITY: OverlayBioEntityRender = { + id: 0, + modelId: 0, + x1: 0, + y1: 0, + x2: 0, + y2: 0, + width: 0, + height: 0, + value: null, + overlayId: 0, + color: null, + }; + + const getHexTricolorGradientColorWithAlpha = jest.fn().mockReturnValue('#FFFFFF'); + const defaultColor = '#000000'; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should return the result of getHexTricolorGradientColorWithAlpha if entity has a value equal to 0', () => { + const entity = { ...ENTITY, value: 0 }; + const result = getColorByAvailableProperties( + entity, + getHexTricolorGradientColorWithAlpha, + defaultColor, + ); + + expect(result).toEqual('#FFFFFF'); + expect(getHexTricolorGradientColorWithAlpha).toHaveBeenCalledWith(entity.value); + }); + + it('should return the result of getHexTricolorGradientColorWithAlpha if entity has a value', () => { + const entity = { ...ENTITY, value: -0.2137 }; + const result = getColorByAvailableProperties( + entity, + getHexTricolorGradientColorWithAlpha, + defaultColor, + ); + + expect(result).toEqual('#FFFFFF'); + expect(getHexTricolorGradientColorWithAlpha).toHaveBeenCalledWith(entity.value); + }); + + it('should return the result of convertDecimalToHex if entity has a color', () => { + const entity = { ...ENTITY, color: { rgb: -65536, alpha: 0 } }; // red color + + const result = getColorByAvailableProperties( + entity, + getHexTricolorGradientColorWithAlpha, + defaultColor, + ); + + expect(result).toEqual('#ff0000'); + expect(getHexTricolorGradientColorWithAlpha).not.toHaveBeenCalled(); + }); + + it('should return the default color if entity has neither a value nor a color', () => { + const result = getColorByAvailableProperties( + ENTITY, + getHexTricolorGradientColorWithAlpha, + defaultColor, + ); + + expect(result).toEqual('#000000'); + expect(getHexTricolorGradientColorWithAlpha).not.toHaveBeenCalled(); + }); +}); diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/getColorByAvailableProperties.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/getColorByAvailableProperties.ts new file mode 100644 index 0000000000000000000000000000000000000000..b7ac985fcb8f3f6328b5d72e7f9f7e66faffe07d --- /dev/null +++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/getColorByAvailableProperties.ts @@ -0,0 +1,18 @@ +import { ZERO } from '@/constants/common'; +import type { GetHex3ColorGradientColorWithAlpha } from '@/hooks/useTriColorLerp'; +import { OverlayBioEntityRender } from '@/types/OLrendering'; +import { convertDecimalToHex } from '@/utils/convert/convertDecimalToHex'; + +export const getColorByAvailableProperties = ( + entity: OverlayBioEntityRender, + getHexTricolorGradientColorWithAlpha: GetHex3ColorGradientColorWithAlpha, + defaultColor: string, +): string => { + if (typeof entity.value === 'number') { + return getHexTricolorGradientColorWithAlpha(entity.value || ZERO); + } + if (entity.color) { + return convertDecimalToHex(entity.color.rgb); + } + return defaultColor; +}; diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/getOverlayFeatures.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/getOverlayFeatures.ts index 60a3ea8adbdfce2b0a66b90c2b3b514fb3fde9a7..a2a7fb430edee5f165119643802bda01c8477cce 100644 --- a/src/components/Map/MapViewer/utils/config/overlaysLayer/getOverlayFeatures.ts +++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/getOverlayFeatures.ts @@ -1,28 +1,30 @@ -import { ZERO } from '@/constants/common'; -import { GetHex3ColorGradientColorWithAlpha } from '@/hooks/useTriColorLerp'; +import type { GetHex3ColorGradientColorWithAlpha } from '@/hooks/useTriColorLerp'; import { OverlayBioEntityRender } from '@/types/OLrendering'; import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection'; -import Feature from 'ol/Feature'; -import Polygon, { fromExtent } from 'ol/geom/Polygon'; -import { Fill, Style } from 'ol/style'; +import type Feature from 'ol/Feature'; +import type Polygon from 'ol/geom/Polygon'; +import { createOverlayGeometryFeature } from './createOverlayGeometryFeature'; +import { getColorByAvailableProperties } from './getColorByAvailableProperties'; -export const getOverlayFeatures = ( - bioEntities: OverlayBioEntityRender[], - pointToProjection: UsePointToProjectionResult, - getHex3ColorGradientColorWithAlpha: GetHex3ColorGradientColorWithAlpha, -): Feature<Polygon>[] => - bioEntities.map(entity => { - const feature = new Feature({ - geometry: fromExtent([ +type GetOverlayFeaturesProps = { + bioEntities: OverlayBioEntityRender[]; + pointToProjection: UsePointToProjectionResult; + getHex3ColorGradientColorWithAlpha: GetHex3ColorGradientColorWithAlpha; + defaultColor: string; +}; + +export const getOverlayFeatures = ({ + bioEntities, + pointToProjection, + getHex3ColorGradientColorWithAlpha, + defaultColor, +}: GetOverlayFeaturesProps): Feature<Polygon>[] => + bioEntities.map(entity => + createOverlayGeometryFeature( + [ ...pointToProjection({ x: entity.x1, y: entity.y1 }), ...pointToProjection({ x: entity.x2, y: entity.y2 }), - ]), - }); - feature.setStyle( - new Style({ - fill: new Fill({ color: getHex3ColorGradientColorWithAlpha(entity.value || ZERO) }), - }), - ); - - return feature; - }); + ], + getColorByAvailableProperties(entity, getHex3ColorGradientColorWithAlpha, defaultColor), + ), + ); diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/useOlMapOverlaysLayer.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/useOlMapOverlaysLayer.ts index 73fddda3ed2aecb8b3bc392ee7bad92135b4313e..011dac0370767f058d4ff3744fc52365c9eec692 100644 --- a/src/components/Map/MapViewer/utils/config/overlaysLayer/useOlMapOverlaysLayer.ts +++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/useOlMapOverlaysLayer.ts @@ -10,12 +10,18 @@ import { getOverlayFeatures } from './getOverlayFeatures'; export const useOlMapOverlaysLayer = (): VectorLayer<VectorSource<Geometry>> => { const pointToProjection = usePointToProjection(); - const { getHex3ColorGradientColorWithAlpha } = useTriColorLerp(); + const { getHex3ColorGradientColorWithAlpha, defaultColorHex } = useTriColorLerp(); const bioEntities = useAppSelector(overlayBioEntitiesForCurrentModelSelector); const features = useMemo( - () => getOverlayFeatures(bioEntities, pointToProjection, getHex3ColorGradientColorWithAlpha), - [bioEntities, getHex3ColorGradientColorWithAlpha, pointToProjection], + () => + getOverlayFeatures({ + bioEntities, + pointToProjection, + getHex3ColorGradientColorWithAlpha, + defaultColor: defaultColorHex, + }), + [bioEntities, getHex3ColorGradientColorWithAlpha, pointToProjection, defaultColorHex], ); const vectorSource = useMemo(() => { diff --git a/src/constants/backgrounds.ts b/src/constants/backgrounds.ts new file mode 100644 index 0000000000000000000000000000000000000000..1bfb46194f374830b2b9ba89d807cbec4dc658fa --- /dev/null +++ b/src/constants/backgrounds.ts @@ -0,0 +1 @@ +export const EMPTY_BACKGROUND_NAME = 'Empty'; diff --git a/src/constants/hexColors.ts b/src/constants/hexColors.ts new file mode 100644 index 0000000000000000000000000000000000000000..1a81cfbc01e74b9ae6e76a778e65a73dd7ee76e5 --- /dev/null +++ b/src/constants/hexColors.ts @@ -0,0 +1 @@ +export const WHITE_HEX_OPACITY_0 = '#00000000'; diff --git a/src/hooks/useTriColorLerp.ts b/src/hooks/useTriColorLerp.ts index aeff150a81cc2008d9e669b06fcfc2f1841001d2..f872b09c506edbf4b8c50c3e70d02e1a860d12f2 100644 --- a/src/hooks/useTriColorLerp.ts +++ b/src/hooks/useTriColorLerp.ts @@ -1,24 +1,30 @@ +import { useCallback } from 'react'; +import { WHITE_HEX_OPACITY_0 } from '@/constants/hexColors'; import { maxColorValSelector, minColorValSelector, neutralColorValSelector, overlayOpacitySelector, + simpleColorValSelector, } from '@/redux/configuration/configuration.selectors'; import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { getHexTricolorGradientColorWithAlpha } from '@/utils/convert/getHexTricolorGradientColorWithAlpha'; -import { useCallback } from 'react'; +import { ONE } from '@/constants/common'; +import { addAlphaToHexString } from '../utils/convert/addAlphaToHexString'; export type GetHex3ColorGradientColorWithAlpha = (position: number) => string; type UseTriColorLerpReturn = { getHex3ColorGradientColorWithAlpha: GetHex3ColorGradientColorWithAlpha; + defaultColorHex: string; }; export const useTriColorLerp = (): UseTriColorLerpReturn => { const minColorValHexString = useAppSelector(minColorValSelector) || ''; const maxColorValHexString = useAppSelector(maxColorValSelector) || ''; const neutralColorValHexString = useAppSelector(neutralColorValSelector) || ''; - const overlayOpacityValue = useAppSelector(overlayOpacitySelector) || ''; + const overlayOpacityValue = useAppSelector(overlayOpacitySelector) || ONE; + const simpleColorValue = useAppSelector(simpleColorValSelector) || WHITE_HEX_OPACITY_0; const getHex3ColorGradientColorWithAlpha = useCallback( (position: number) => @@ -32,5 +38,7 @@ export const useTriColorLerp = (): UseTriColorLerpReturn => { [minColorValHexString, neutralColorValHexString, maxColorValHexString, overlayOpacityValue], ); - return { getHex3ColorGradientColorWithAlpha }; + const defaultColorHex = addAlphaToHexString(simpleColorValue, Number(overlayOpacityValue)); + + return { getHex3ColorGradientColorWithAlpha, defaultColorHex }; }; diff --git a/src/models/fixtures/overlayBioEntityFixture.ts b/src/models/fixtures/overlayBioEntityFixture.ts new file mode 100644 index 0000000000000000000000000000000000000000..da0c6da654ba996874863860034ebf6856762054 --- /dev/null +++ b/src/models/fixtures/overlayBioEntityFixture.ts @@ -0,0 +1,10 @@ +import { ZOD_SEED } from '@/constants'; +import { z } from 'zod'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { createFixture } from 'zod-fixture'; +import { overlayBioEntitySchema } from '../overlayBioEntitySchema'; + +export const overlayBioEntityFixture = createFixture(z.array(overlayBioEntitySchema), { + seed: ZOD_SEED, + array: { min: 3, max: 3 }, +}); diff --git a/src/models/mocks/modelsMock.ts b/src/models/mocks/modelsMock.ts index 96cd8bf94a4a098980ee270c78be6a17f95349ea..5254ae65fb2158e7e999e4cebce4229742aa139c 100644 --- a/src/models/mocks/modelsMock.ts +++ b/src/models/mocks/modelsMock.ts @@ -457,3 +457,21 @@ export const MODELS_MOCK_SHORT: MapModel[] = [ maxZoom: 5, }, ]; + +export const CORE_PD_MODEL_MOCK: MapModel = { + idObject: 5053, + width: 26779.25, + height: 13503.0, + defaultCenterX: null, + defaultCenterY: null, + description: '', + name: 'Core PD map', + defaultZoomLevel: null, + tileSize: 256, + references: [], + authors: [], + creationDate: null, + modificationDates: [], + minZoom: 2, + maxZoom: 9, +}; diff --git a/src/redux/backgrounds/background.selectors.ts b/src/redux/backgrounds/background.selectors.ts index 596301e1262dc5ed73303636d8fe7acd87da1c94..b8443ab5545da2f894ca43fe284669b56fcdf038 100644 --- a/src/redux/backgrounds/background.selectors.ts +++ b/src/redux/backgrounds/background.selectors.ts @@ -1,4 +1,5 @@ import { createSelector } from '@reduxjs/toolkit'; +import { EMPTY_BACKGROUND_NAME } from '@/constants/backgrounds'; import { mapDataSelector } from '../map/map.selectors'; import { rootSelector } from '../root/root.selectors'; @@ -36,3 +37,10 @@ export const currentBackgroundImagePathSelector = createSelector( currentBackgroundImageSelector, image => (image ? image.path : ''), ); + +export const emptyBackgroundIdSelector = createSelector(backgroundsDataSelector, backgrounds => { + const emptyBackground = backgrounds?.find( + background => background.name === EMPTY_BACKGROUND_NAME, + ); + return emptyBackground?.id; +}); diff --git a/src/redux/configuration/configuration.selectors.ts b/src/redux/configuration/configuration.selectors.ts index cb54cee21c5b04295f5e3c3ac6043b300c52e378..7a694a44779048afc22d05ab9b406f8f970fd153 100644 --- a/src/redux/configuration/configuration.selectors.ts +++ b/src/redux/configuration/configuration.selectors.ts @@ -6,6 +6,7 @@ import { MIN_COLOR_VAL_NAME_ID, NEUTRAL_COLOR_VAL_NAME_ID, OVERLAY_OPACITY_NAME_ID, + SIMPLE_COLOR_VAL_NAME_ID, } from './configuration.constants'; const configurationSelector = createSelector(rootSelector, state => state.configuration); @@ -31,3 +32,8 @@ export const overlayOpacitySelector = createSelector( configurationSelector, state => configurationAdapterSelectors.selectById(state, OVERLAY_OPACITY_NAME_ID)?.value, ); + +export const simpleColorValSelector = createSelector( + configurationSelector, + state => configurationAdapterSelectors.selectById(state, SIMPLE_COLOR_VAL_NAME_ID)?.value, +); diff --git a/src/redux/overlayBioEntity/overlayBioEntity.utils.ts b/src/redux/overlayBioEntity/overlayBioEntity.utils.ts index b4231f7009178096bf194a63d1a1ac9090df0376..b875e1bba425bc9f7f9a08617d120cdd8ed4a4c8 100644 --- a/src/redux/overlayBioEntity/overlayBioEntity.utils.ts +++ b/src/redux/overlayBioEntity/overlayBioEntity.utils.ts @@ -18,6 +18,7 @@ export const parseOverlayBioEntityToOlRenderingFormat = ( height: entity.left.height, value: entity.right.value, overlayId, + color: entity.right.color, }); } return acc; diff --git a/src/types/OLrendering.ts b/src/types/OLrendering.ts index 71ce6cdbde57f28c44692b9a84341c91610d1667..3a651659743e46e286dda16845ea676fcfa4e314 100644 --- a/src/types/OLrendering.ts +++ b/src/types/OLrendering.ts @@ -1,3 +1,5 @@ +import { Color } from './models'; + export type OverlayBioEntityRender = { id: number; modelId: number; @@ -9,4 +11,5 @@ export type OverlayBioEntityRender = { height: number; value: number | null; overlayId: number; + color: Color | null; }; diff --git a/src/types/models.ts b/src/types/models.ts index 885c7fa719b2d4c1b74a0d9feb1067babad327d8..017169d84d671d555470f779311992388fb5ccda 100644 --- a/src/types/models.ts +++ b/src/types/models.ts @@ -2,6 +2,7 @@ import { bioEntityContentSchema } from '@/models/bioEntityContentSchema'; import { bioEntityResponseSchema } from '@/models/bioEntityResponseSchema'; import { bioEntitySchema } from '@/models/bioEntitySchema'; import { chemicalSchema } from '@/models/chemicalSchema'; +import { colorSchema } from '@/models/colorSchema'; import { configurationOptionSchema } from '@/models/configurationOptionSchema'; import { disease } from '@/models/disease'; import { drugSchema } from '@/models/drugSchema'; @@ -39,3 +40,4 @@ export type ElementSearchResult = z.infer<typeof elementSearchResult>; export type ElementSearchResultType = z.infer<typeof elementSearchResultType>; export type ConfigurationOption = z.infer<typeof configurationOptionSchema>; export type OverlayBioEntity = z.infer<typeof overlayBioEntitySchema>; +export type Color = z.infer<typeof colorSchema>; diff --git a/src/utils/convert/getHexTricolorGradientColorWithAlpha.ts b/src/utils/convert/getHexTricolorGradientColorWithAlpha.ts index d142a465000a35a1fa0bab78585a3482e88d82d5..eacefc8d7390de1a3cc1eed31f01a59d734e2044 100644 --- a/src/utils/convert/getHexTricolorGradientColorWithAlpha.ts +++ b/src/utils/convert/getHexTricolorGradientColorWithAlpha.ts @@ -1,3 +1,4 @@ +import { WHITE_HEX_OPACITY_0 } from '@/constants/hexColors'; import { interpolateThreeColors } from '../lerp/interpolateThreeColors'; import { addAlphaToHexString } from './addAlphaToHexString'; import { hexToRgb } from './hexToRgb'; @@ -11,8 +12,6 @@ export type GetHexTricolorGradientColorWithAlphaProps = { position: number; }; -const WHITE_HEX_OPACITY_0 = '#00000000'; - export const getHexTricolorGradientColorWithAlpha = ({ leftColor, middleColor, diff --git a/src/utils/convert/hexToRgb.test.ts b/src/utils/convert/hexToRgb.test.ts index dcd10d2058baca58b373afb73d762d7a3ef7da3c..4801c8cc7670ea597606c37925f62699c0ec217c 100644 --- a/src/utils/convert/hexToRgb.test.ts +++ b/src/utils/convert/hexToRgb.test.ts @@ -3,7 +3,7 @@ import { expandHexToFullFormatIfItsShorthanded, hexToRgb } from './hexToRgb'; describe('expandHexToFullFormatIfItsShorthanded', () => { it('should expand short-handed hex string to full format', () => { const result = expandHexToFullFormatIfItsShorthanded('#abc'); - expect(result).toBe('aabbcc'); + expect(result).toBe('#aabbcc'); }); it('should not modify full-format hex string', () => { @@ -13,12 +13,12 @@ describe('expandHexToFullFormatIfItsShorthanded', () => { it('should handle hex string without leading #', () => { const result = expandHexToFullFormatIfItsShorthanded('abc'); - expect(result).toBe('aabbcc'); + expect(result).toBe('#aabbcc'); }); it('should return original string if it does not match short-hand regex', () => { const result = expandHexToFullFormatIfItsShorthanded('invalid'); - expect(result).toBe('invalid'); + expect(result).toBe('#invalid'); }); }); diff --git a/src/utils/convert/hexToRgb.ts b/src/utils/convert/hexToRgb.ts index 316c0c6436c19edd590db660765f183fe78a6197..e9b4544be1b962d55fd97ff7ee2e70b816eccd5f 100644 --- a/src/utils/convert/hexToRgb.ts +++ b/src/utils/convert/hexToRgb.ts @@ -4,7 +4,10 @@ export const expandHexToFullFormatIfItsShorthanded = (hexString: string): string const fullHexString = hexString.replace(SHORT_HAND_REGEX, (m, r, g, b) => { return r + r + g + g + b + b; }); - return fullHexString; + const fullHexStringWithPrefix = fullHexString.startsWith('#') + ? fullHexString + : `#${fullHexString}`; + return fullHexStringWithPrefix; }; const FULL_HEX_REGEX = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i;