diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction.test.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction.test.ts index f6aab10a388f5222c21a7830cad72f08172c0fc9..5282e2464e30e3b39011d1bb8cac05c8185a9ad5 100644 --- a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction.test.ts +++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction.test.ts @@ -2,14 +2,13 @@ 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'; -import { FEATURE_TYPE } from '@/constants/features'; import { clickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction'; +import { newReactionFixture } from '@/models/fixtures/newReactionFixture'; const mockedAxiosClient = mockNetworkNewAPIResponse(); jest.mock('../../../../../../services/pluginsManager/map/triggerSearch/searchFitBounds'); @@ -20,7 +19,6 @@ describe('clickHandleReaction', () => { let modelId = 1; let reactionId = 1; const hasFitBounds = true; - const feature = new Feature({ type: FEATURE_TYPE.REACTION, id: 1 }); beforeEach(() => { jest.clearAllMocks(); @@ -54,8 +52,13 @@ describe('clickHandleReaction', () => { ) .reply(HttpStatusCode.Ok, bioEntityFixture); }); - await clickHandleReaction(dispatch, hasFitBounds)(feature, modelId); - expect(dispatch).toHaveBeenCalledTimes(4); + clickHandleReaction(dispatch, hasFitBounds)( + [], + [{ ...newReactionFixture, id: reactionId }], + reactionId, + modelId, + ); + expect(dispatch).toHaveBeenCalledTimes(5); expect(dispatch).toHaveBeenCalledWith(openReactionDrawerById(reactionId)); expect(dispatch).toHaveBeenCalledWith(selectTab('')); expect(eventBusDispatchEventSpy).toHaveBeenCalled(); @@ -68,7 +71,7 @@ describe('clickHandleReaction', () => { unwrap: jest.fn().mockResolvedValue(mockBioEntities), })); - await clickHandleReaction(dispatch, false)(feature, modelId); + clickHandleReaction(dispatch, false)([], [{ ...newReactionFixture, id: 1 }], 1, modelId); expect(searchFitBounds).not.toHaveBeenCalled(); }); diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction.ts index 676d0b4976ea6d85fdec4ecbd50343e5c93f828b..392e4c3eb66f4f79f9179004c16b98abad13e59c 100644 --- a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction.ts +++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction.ts @@ -1,68 +1,56 @@ -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'; +import { BioEntity, ModelElement, NewReaction } from '@/types/models'; import { FEATURE_TYPE } from '@/constants/features'; +import { setMultipleBioEntityContents } from '@/redux/bioEntity/bioEntity.slice'; +import { addNumbersToEntityNumberData } from '@/redux/entityNumber/entityNumber.slice'; +import { setReactions } from '@/redux/reactions/reactions.slice'; +import mapNewReactionToReaction from '@/utils/reaction/mapNewReactionToReaction'; +import { mapReactionToBioEntity } from '@/utils/bioEntity/mapReactionToBioEntity'; +import getModelElementsIdsFromReaction from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/getModelElementsIdsFromReaction'; +import { mapModelElementToBioEntity } from '@/utils/bioEntity/mapModelElementToBioEntity'; /* prettier-ignore */ export const clickHandleReaction = (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) { + ( modelElements: Array<ModelElement>, reactions: Array<NewReaction>, reactionId: number, modelId: number): void => { + + const reactionBioEntities: Array<BioEntity> = []; + const reaction = reactions.find(newReaction => newReaction.id === reactionId); + if(!reaction) { return; } + const modelElementsIds = getModelElementsIdsFromReaction(reaction); + modelElementsIds.forEach(modelElementId => { + const modelElement = modelElements.find(element => + element.id === modelElementId + ); + if(!modelElement) { + return; + } + reactionBioEntities.push(mapModelElementToBioEntity(modelElement)); + }); - const reaction = payload.data[FIRST_ARRAY_ELEMENT]; - - const bioEntitiesIds = getBioEntitiesIdsFromReaction(reaction); - - dispatch(openReactionDrawerById(reaction.id)); + dispatch(openReactionDrawerById(reactionId)); 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: FEATURE_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: FEATURE_TYPE.REACTION }], - results: [result] - }); - - if (hasFitBounds) { - searchFitBounds(); - } - } + const bioEntityReaction = mapReactionToBioEntity(reaction); + dispatch(setMultipleBioEntityContents(reactionBioEntities)); + dispatch(addNumbersToEntityNumberData(reactionBioEntities.map(reactionBioEntity => reactionBioEntity.elementId))); + dispatch(setReactions([mapNewReactionToReaction(reaction)])); + + const result = reactionBioEntities.map((bioEntity) => {return { bioEntity, perfect: true };}); + result.push({ bioEntity: bioEntityReaction, perfect: true }); + PluginsEventBus.dispatchEvent('onSearch', { + type: 'reaction', + searchValues: [{ id: reactionId, modelId, type: FEATURE_TYPE.REACTION }], + results: [result] + }); + + if (hasFitBounds) { + searchFitBounds(); } }; diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/getModelElementsIdsFromReaction.test.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/getModelElementsIdsFromReaction.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..fec3d6343a1cba28fdddde70572504e1f70bceca --- /dev/null +++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/getModelElementsIdsFromReaction.test.ts @@ -0,0 +1,24 @@ +/* eslint-disable no-magic-numbers */ +import { newReactionFixture } from '@/models/fixtures/newReactionFixture'; +import getModelElementsIdsFromReaction from './getModelElementsIdsFromReaction'; + +describe('getModelElementsIdsFromReaction', () => { + it('should return correct model elements ids from given reaction', () => { + const result = getModelElementsIdsFromReaction(newReactionFixture); + expect(result).toEqual([ + ...newReactionFixture.products.map(product => product.element), + ...newReactionFixture.reactants.map(reactant => reactant.element), + ...newReactionFixture.modifiers.map(modifier => modifier.element), + ]); + }); + + it('should return empty array', () => { + const result = getModelElementsIdsFromReaction({ + ...newReactionFixture, + products: [], + reactants: [], + modifiers: [], + }); + expect(result).toEqual([]); + }); +}); diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/getModelElementsIdsFromReaction.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/getModelElementsIdsFromReaction.ts new file mode 100644 index 0000000000000000000000000000000000000000..a3ed0a9fc78968e9d26f183b490628abe04c15e5 --- /dev/null +++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/getModelElementsIdsFromReaction.ts @@ -0,0 +1,7 @@ +import { NewReaction } from '@/types/models'; + +export default function getModelElementsIdsFromReaction(reaction: NewReaction): Array<number> { + return [...reaction.products, ...reaction.reactants, ...reaction.modifiers].map( + bioEntity => bioEntity.element, + ); +} diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.test.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.test.ts index 166b70df395b7909c2d4f368e5b17409c68a7474..373c01d13ecda7c0386bf8b33e662f73ef9c86ca 100644 --- a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.test.ts +++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.test.ts @@ -59,6 +59,8 @@ describe('onMapLeftClick', () => { dispatch, isResultDrawerOpen, comments, + [], + [], )(event, mapInstance); expect(dispatch).toHaveBeenCalledWith(updateLastClick(expect.any(Object))); @@ -84,6 +86,8 @@ describe('onMapLeftClick', () => { dispatch, isResultDrawerOpen, comments, + [], + [], )(event, mapInstance); expect(leftClickHandleAliasSpy).toHaveBeenCalledWith(dispatch); @@ -106,6 +110,8 @@ describe('onMapLeftClick', () => { dispatch, isResultDrawerOpen, comments, + [], + [], )(event, mapInstance); expect(clickHandleReactionSpy).toHaveBeenCalledWith(dispatch); diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.ts index 7b047ddaa2c2cb86233cc390416ec9e97a2e5031..7a2dd125d03f2b0b2a2dbecaab3a3536e7a1bb47 100644 --- a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.ts +++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseLeftClick/onMapLeftClick.ts @@ -1,7 +1,7 @@ import { MapSize } from '@/redux/map/map.types'; import { AppDispatch } from '@/redux/store'; import { Map, MapBrowserEvent } from 'ol'; -import { Comment } from '@/types/models'; +import { Comment, ModelElement, NewReaction } from '@/types/models'; import { updateLastClick } from '@/redux/map/map.slice'; import { toLonLat } from 'ol/proj'; import { latLngToPoint } from '@/utils/map/latLngToPoint'; @@ -17,7 +17,7 @@ import { clickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/ /* prettier-ignore */ export const onMapLeftClick = - (mapSize: MapSize, modelId: number, dispatch: AppDispatch, isResultDrawerOpen: boolean, comments: Comment[]) => + (mapSize: MapSize, modelId: number, dispatch: AppDispatch, isResultDrawerOpen: boolean, comments: Comment[], modelElements: Array<ModelElement>, reactions: Array<NewReaction>) => async ({ coordinate, pixel }: Pick<MapBrowserEvent<UIEvent>, 'coordinate' | 'pixel'>, mapInstance: Map): Promise<void> => { const [lng, lat] = toLonLat(coordinate); const point = latLngToPoint([lat, lng], mapSize); @@ -52,9 +52,10 @@ export const onMapLeftClick = dispatch(handleDataReset); const type = featureAtPixel.get('type'); + const id = featureAtPixel.get('id'); if(type === FEATURE_TYPE.ALIAS) { await leftClickHandleAlias(dispatch)(featureAtPixel, modelId); } else if (type === FEATURE_TYPE.REACTION) { - await clickHandleReaction(dispatch)(featureAtPixel, modelId); + clickHandleReaction(dispatch)(modelElements, reactions, id, modelId); } }; diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.test.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.test.ts index a96f66012f556f575fdba656d003da73944fe79e..4f9a3ade6a6a4c94bffb3123efcbe206494eee75 100644 --- a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.test.ts +++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.test.ts @@ -9,7 +9,8 @@ import VectorLayer from 'ol/layer/Vector'; import VectorSource from 'ol/source/Vector'; import { Feature } from 'ol'; import { FEATURE_TYPE } from '@/constants/features'; -import { modelElementsFixture } from '@/models/fixtures/modelElementsFixture'; +import { modelElementFixture } from '@/models/fixtures/modelElementFixture'; +import { newReactionFixture } from '@/models/fixtures/newReactionFixture'; import { VECTOR_MAP_LAYER_TYPE } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants'; import * as rightClickHandleAlias from './rightClickHandleAlias'; import * as clickHandleReaction from '../clickHandleReaction'; @@ -52,7 +53,6 @@ describe('onMapRightClick', () => { it('calls rightClickHandleAlias if feature type is ALIAS', async () => { const dispatch = jest.fn(); - const modelElement = modelElementsFixture.content[0]; jest.spyOn(mapInstance, 'getAllLayers').mockImplementation((): Layer<Source>[] => { return [vectorLayer]; }); @@ -63,9 +63,15 @@ describe('onMapRightClick', () => { return vectorSource; }); jest.spyOn(vectorSource, 'getClosestFeatureToCoordinate').mockImplementation((): Feature => { - return new Feature({ id: modelElement.id, type: FEATURE_TYPE.ALIAS }); + return new Feature({ id: modelElementFixture.id, type: FEATURE_TYPE.ALIAS }); }); - await onMapRightClick(mapSize, modelId, dispatch, [modelElement])(event, mapInstance); + await onMapRightClick( + mapSize, + modelId, + dispatch, + [modelElementFixture], + [], + )(event, mapInstance); expect(dispatch).toHaveBeenCalledWith(updateLastRightClick(expect.any(Object))); expect(dispatch).toHaveBeenCalledWith(openContextMenu(event.pixel)); @@ -86,7 +92,13 @@ describe('onMapRightClick', () => { jest.spyOn(vectorSource, 'getClosestFeatureToCoordinate').mockImplementation((): Feature => { return new Feature({ id: 1, type: FEATURE_TYPE.REACTION }); }); - await onMapRightClick(mapSize, modelId, dispatch, [])(event, mapInstance); + await onMapRightClick( + mapSize, + modelId, + dispatch, + [], + [{ ...newReactionFixture, id: 1 }], + )(event, mapInstance); expect(dispatch).toHaveBeenCalledWith(updateLastRightClick(expect.any(Object))); expect(dispatch).toHaveBeenCalledWith(openContextMenu(event.pixel)); diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.ts index c4673a83be97cefc8662b8d8fa283f70a92bbea8..902f7d52763fbb88f768c0e854aa55919800ef6c 100644 --- a/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.ts +++ b/src/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick.ts @@ -9,14 +9,14 @@ import { FEATURE_TYPE } from '@/constants/features'; import VectorLayer from 'ol/layer/Vector'; import VectorSource from 'ol/source/Vector'; import { openContextMenu } from '@/redux/contextMenu/contextMenu.slice'; -import { ModelElement } from '@/types/models'; +import { ModelElement, NewReaction } from '@/types/models'; import { rightClickHandleAlias } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/rightClickHandleAlias'; import { clickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/clickHandleReaction'; import { VECTOR_MAP_LAYER_TYPE } from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.constants'; /* prettier-ignore */ export const onMapRightClick = - (mapSize: MapSize, modelId: number, dispatch: AppDispatch, modelElements: Array<ModelElement>) => + (mapSize: MapSize, modelId: number, dispatch: AppDispatch, modelElements: Array<ModelElement>, reactions: Array<NewReaction>) => async ({ coordinate, pixel }: Pick<MapBrowserEvent<UIEvent>, 'coordinate' | 'pixel'>, mapInstance: Map): Promise<void> => { const [lng, lat] = toLonLat(coordinate); @@ -51,6 +51,6 @@ export const onMapRightClick = } await rightClickHandleAlias(dispatch)(id, modelElement); } else if (type === FEATURE_TYPE.REACTION) { - await clickHandleReaction(dispatch)(foundFeature, modelId); + clickHandleReaction(dispatch)(modelElements, reactions, id, modelId); } }; diff --git a/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.ts b/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.ts index 2a0af8eaba4b1bbbd3c40a027da71726d3c76d11..05993a8106fe4592d10b1175ac04feab5979c751 100644 --- a/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.ts +++ b/src/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners.ts @@ -15,6 +15,7 @@ import { Coordinate } from 'ol/coordinate'; import { Pixel } from 'ol/pixel'; import { onMapRightClick } from '@/components/Map/MapViewer/MapViewerVector/listeners/mouseClick/mouseRightClick/onMapRightClick'; import { modelElementsSelector } from '@/redux/modelElements/modelElements.selector'; +import { newReactionsDataSelector } from '@/redux/newReactions/newReactions.selectors'; interface UseOlMapVectorListenersInput { mapInstance: MapInstance; @@ -25,6 +26,7 @@ export const useOlMapVectorListeners = ({ mapInstance }: UseOlMapVectorListeners const modelId = useSelector(currentModelIdSelector); const isResultDrawerOpen = useSelector(resultDrawerOpen); const modelElements = useSelector(modelElementsSelector); + const reactions = useSelector(newReactionsDataSelector); const dispatch = useAppDispatch(); const coordinate = useRef<Coordinate>([]); const pixel = useRef<Pixel>([]); @@ -33,13 +35,21 @@ export const useOlMapVectorListeners = ({ mapInstance }: UseOlMapVectorListeners const vectorRendering = useAppSelector(vectorRenderingSelector); const handleMapLeftClick = useDebouncedCallback( - onMapLeftClick(mapSize, modelId, dispatch, isResultDrawerOpen, comments), + onMapLeftClick( + mapSize, + modelId, + dispatch, + isResultDrawerOpen, + comments, + modelElements?.content || [], + reactions, + ), OPTIONS.clickPersistTime, { leading: false }, ); const handleRightClick = useDebouncedCallback( - onMapRightClick(mapSize, modelId, dispatch, modelElements?.content || []), + onMapRightClick(mapSize, modelId, dispatch, modelElements?.content || [], reactions), OPTIONS.clickPersistTime, { leading: false, diff --git a/src/redux/bioEntity/bioEntity.reducers.ts b/src/redux/bioEntity/bioEntity.reducers.ts index deadfae34a40ce0d9430f8ca3db149da7cf82754..f5f4f94c6594579dc5e0a29c2b5c7b8513f38e37 100644 --- a/src/redux/bioEntity/bioEntity.reducers.ts +++ b/src/redux/bioEntity/bioEntity.reducers.ts @@ -1,7 +1,7 @@ import { DEFAULT_ERROR } from '@/constants/errors'; import { ActionReducerMapBuilder, PayloadAction } from '@reduxjs/toolkit'; import { getBioEntityById } from '@/redux/bioEntity/thunks/getBioEntity'; -import { BioEntityContent } from '@/types/models'; +import { BioEntity, BioEntityContent } from '@/types/models'; import { BIOENTITY_SUBMAP_CONNECTIONS_INITIAL_STATE } from './bioEntity.constants'; import { getBioEntity, getMultiBioEntity } from './bioEntity.thunks'; import { BioEntityContentsState } from './bioEntity.types'; @@ -136,3 +136,23 @@ export const setBioEntityContentsReducer = ( ]; state.loading = 'succeeded'; }; + +export const setMultipleBioEntityContentsReducer = ( + state: BioEntityContentsState, + action: PayloadAction<Array<BioEntity>>, +): void => { + state.data = [ + { + data: action.payload.map(bioEntity => { + return { + bioEntity, + perfect: true, + }; + }), + loading: 'succeeded', + error: DEFAULT_ERROR, + searchQueryElement: 'asd', + }, + ]; + state.loading = 'succeeded'; +}; diff --git a/src/redux/bioEntity/bioEntity.slice.ts b/src/redux/bioEntity/bioEntity.slice.ts index 40f2bbb90fa643bf2572c95f91561a25257f650e..dbd2093c165840fb4c383dbc30eb3dacafe58a8f 100644 --- a/src/redux/bioEntity/bioEntity.slice.ts +++ b/src/redux/bioEntity/bioEntity.slice.ts @@ -6,6 +6,7 @@ import { getMultiBioEntityContentsReducer, getSubmapConnectionsBioEntityReducer, setBioEntityContentsReducer, + setMultipleBioEntityContentsReducer, toggleIsContentTabOpenedReducer, } from './bioEntity.reducers'; @@ -16,6 +17,7 @@ export const bioEntityContentsSlice = createSlice({ clearBioEntitiesData: clearBioEntitiesDataReducer, toggleIsContentTabOpened: toggleIsContentTabOpenedReducer, setBioEntityContents: setBioEntityContentsReducer, + setMultipleBioEntityContents: setMultipleBioEntityContentsReducer, }, extraReducers: builder => { getBioEntityContentsReducer(builder); @@ -24,7 +26,11 @@ export const bioEntityContentsSlice = createSlice({ }, }); -export const { clearBioEntitiesData, toggleIsContentTabOpened, setBioEntityContents } = - bioEntityContentsSlice.actions; +export const { + clearBioEntitiesData, + toggleIsContentTabOpened, + setBioEntityContents, + setMultipleBioEntityContents, +} = bioEntityContentsSlice.actions; export default bioEntityContentsSlice.reducer; diff --git a/src/redux/entityNumber/entityNumber.reducers.ts b/src/redux/entityNumber/entityNumber.reducers.ts index dd26edb93118860639dc4f147267c6d5343d96ad..156621700274d11eaff81e465172644abea4ed42 100644 --- a/src/redux/entityNumber/entityNumber.reducers.ts +++ b/src/redux/entityNumber/entityNumber.reducers.ts @@ -20,7 +20,6 @@ export const addNumbersToEntityNumberDataReducer = ( const newEntityNumber: EntityNumber = Object.fromEntries( uniqueIds.map((id, index) => [id, lastNumber + index]), ); - state.data = { ...newEntityNumber, ...state.data, diff --git a/src/redux/reactions/reactions.reducers.ts b/src/redux/reactions/reactions.reducers.ts index 2ef4a163ec4f235e3c0aefbcadeff1f7fc28a7cb..848b4ab797b6f385871d262030d994b46832727b 100644 --- a/src/redux/reactions/reactions.reducers.ts +++ b/src/redux/reactions/reactions.reducers.ts @@ -1,4 +1,5 @@ -import { ActionReducerMapBuilder } from '@reduxjs/toolkit'; +import { ActionReducerMapBuilder, PayloadAction } from '@reduxjs/toolkit'; +import { Reaction } from '@/types/models'; import { REACTIONS_INITIAL_STATE } from './reactions.constants'; import { getReactionsByIds } from './reactions.thunks'; import { ReactionsState } from './reactions.types'; @@ -26,3 +27,11 @@ export const resetReactionsDataReducer = (state: ReactionsState): void => { state.error = REACTIONS_INITIAL_STATE.error; state.loading = REACTIONS_INITIAL_STATE.loading; }; + +export const setReactionsReducer = ( + state: ReactionsState, + action: PayloadAction<Array<Reaction>>, +): void => { + state.data = action.payload; + state.loading = 'succeeded'; +}; diff --git a/src/redux/reactions/reactions.slice.ts b/src/redux/reactions/reactions.slice.ts index f97e9050155d87e496cb8adada0f8e28f0d0f4ba..97cce84286f91fb7855a7d9bda631d98f0ff049e 100644 --- a/src/redux/reactions/reactions.slice.ts +++ b/src/redux/reactions/reactions.slice.ts @@ -1,18 +1,23 @@ import { createSlice } from '@reduxjs/toolkit'; import { REACTIONS_INITIAL_STATE } from './reactions.constants'; -import { getReactionsReducer, resetReactionsDataReducer } from './reactions.reducers'; +import { + getReactionsReducer, + resetReactionsDataReducer, + setReactionsReducer, +} from './reactions.reducers'; export const reactionsSlice = createSlice({ name: 'reactions', initialState: REACTIONS_INITIAL_STATE, reducers: { resetReactionsData: resetReactionsDataReducer, + setReactions: setReactionsReducer, }, extraReducers: builder => { getReactionsReducer(builder); }, }); -export const { resetReactionsData } = reactionsSlice.actions; +export const { resetReactionsData, setReactions } = reactionsSlice.actions; export default reactionsSlice.reducer; diff --git a/src/utils/bioEntity/mapReactionToBioEntity.ts b/src/utils/bioEntity/mapReactionToBioEntity.ts new file mode 100644 index 0000000000000000000000000000000000000000..0aab81b7588933094cc21149a60c3a688db15e3e --- /dev/null +++ b/src/utils/bioEntity/mapReactionToBioEntity.ts @@ -0,0 +1,19 @@ +import { BioEntity, NewReaction } from '@/types/models'; + +export function mapReactionToBioEntity(reaction: NewReaction): BioEntity { + return { + id: reaction.id, + name: reaction.name, + model: reaction.model, + elementId: reaction.elementId, + references: reaction.references, + z: reaction.z, + notes: reaction.notes, + symbol: reaction.symbol, + visibilityLevel: reaction.visibilityLevel, + synonyms: reaction.synonyms, + abbreviation: reaction.abbreviation, + formula: reaction.formula, + sboTerm: reaction.sboTerm, + } as BioEntity; +} diff --git a/src/utils/reaction/ReactionTypeEnum.ts b/src/utils/reaction/ReactionTypeEnum.ts new file mode 100644 index 0000000000000000000000000000000000000000..a10ec3201fb35409ccbe46260cee3de636c09f42 --- /dev/null +++ b/src/utils/reaction/ReactionTypeEnum.ts @@ -0,0 +1,30 @@ +enum ReactionTypeEnum { + 'SBO:0000013' = 'Catalysis', + 'SBO:0000180' = 'Dissociation', + 'SBO:0000177' = 'Heterodimer association', + 'SBO:0000537' = 'Inhibition', + 'SBO:0000205' = 'Known Transition omitted', + 'SBO:0000594' = 'Modulation', + 'SBO:0000407' = 'Negative influence', + 'SBO:0000459' = 'Physical stimulation', + 'SBO:0000171' = 'Positive influence', + 'SBO:0000632' = 'Reduced modulation', + 'SBO:0000411' = 'Reduced physical stimulation', + 'SBO:0000533' = 'Reduced trigger', + 'SBO:0000176' = 'State transition', + 'SBO:0000183' = 'Transcription', + 'SBO:0000184' = 'Translation', + 'SBO:0000185' = 'Transport', + 'SBO:0000461' = 'Trigger', + 'SBO:0000178' = 'Truncation', + 'SBO:0000462' = 'Unknown catalysis', + 'SBO:0000536' = 'Unknown inhibition', + 'SBO:0000169' = 'Unknown negative influence', + 'SBO:0000172' = 'Unknown positive influence', + 'SBO:0000631' = 'Unknown reduced modulation', + 'SBO:0000170' = 'Unknown reduced physical stimulation', + 'SBO:0000534' = 'Unknown reduced trigger', + 'SBO:0000396' = 'Unknown transition', +} + +export default ReactionTypeEnum; diff --git a/src/utils/reaction/mapNewReactionToReaction.test.ts b/src/utils/reaction/mapNewReactionToReaction.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..23097b83e3ec53126b3216e239a4ae9a8946e463 --- /dev/null +++ b/src/utils/reaction/mapNewReactionToReaction.test.ts @@ -0,0 +1,193 @@ +/* eslint-disable no-magic-numbers */ +import mapNewReactionToReaction from '@/utils/reaction/mapNewReactionToReaction'; + +describe('mapNewReactionToReaction', () => { + const newReaction = { + id: 31141, + notes: '', + idReaction: 're22', + name: '', + reversible: false, + symbol: null, + abbreviation: null, + formula: null, + mechanicalConfidenceScore: null, + lowerBound: null, + upperBound: null, + subsystem: null, + geneProteinReaction: null, + visibilityLevel: '', + z: 45, + synonyms: [], + model: 137, + kinetics: null, + line: { + id: 109668, + width: 1, + color: { + alpha: 255, + rgb: -16777216, + }, + z: 0, + segments: [ + { + x1: 149.31765717927775, + y1: 319.13724818355684, + x2: 142.5553586937381, + y2: 314.86275181644316, + }, + ], + startArrow: { + arrowType: 'NONE', + angle: 2.748893571891069, + lineType: 'SOLID', + length: 15, + }, + endArrow: { + arrowType: 'NONE', + angle: 2.748893571891069, + lineType: 'SOLID', + length: 15, + }, + lineType: 'SOLID', + }, + processCoordinates: null, + modifiers: [], + products: [ + { + id: 85169, + line: { + id: 109670, + width: 1, + color: { + alpha: 255, + rgb: -16777216, + }, + z: 0, + segments: [ + { + x1: 142.5553586937381, + y1: 314.86275181644316, + x2: 122.2063492063492, + y2: 302, + }, + ], + startArrow: { + arrowType: 'NONE', + angle: 2.748893571891069, + lineType: 'SOLID', + length: 15, + }, + endArrow: { + arrowType: 'OPEN', + angle: 2.748893571891069, + lineType: 'SOLID', + length: 15, + }, + lineType: 'SOLID', + }, + stoichiometry: null, + element: 58886, + }, + ], + reactants: [ + { + id: 85168, + line: { + id: 109669, + width: 1, + color: { + alpha: 255, + rgb: -16777216, + }, + z: 0, + segments: [ + { + x1: 169.66666666666666, + y1: 332, + x2: 149.31765717927775, + y2: 319.13724818355684, + }, + ], + startArrow: { + arrowType: 'NONE', + angle: 2.748893571891069, + lineType: 'SOLID', + length: 15, + }, + endArrow: { + arrowType: 'NONE', + angle: 2.748893571891069, + lineType: 'SOLID', + length: 15, + }, + lineType: 'SOLID', + }, + stoichiometry: null, + element: 58872, + }, + ], + operators: [], + elementId: 're22', + references: [], + sboTerm: 'SBO:0000171', + }; + const expectedReaction = { + centerPoint: { + x: 0, + y: 0, + }, + hierarchyVisibilityLevel: '', + id: 31141, + kineticLaw: null, + lines: [ + { + start: { + x: 149.31765717927775, + y: 319.13724818355684, + }, + end: { + x: 142.5553586937381, + y: 314.86275181644316, + }, + type: '', + }, + { + start: { + x: 142.5553586937381, + y: 314.86275181644316, + }, + end: { + x: 122.2063492063492, + y: 302, + }, + type: '', + }, + { + start: { + x: 169.66666666666666, + y: 332, + }, + end: { + x: 149.31765717927775, + y: 319.13724818355684, + }, + type: '', + }, + ], + modelId: 137, + modifiers: [], + name: '', + notes: '', + products: [], + reactants: [], + reactionId: 're22', + references: [], + type: 'Positive influence', + }; + + it('should return correct reaction object from new reaction object', () => { + const result = mapNewReactionToReaction(newReaction); + expect(result).toEqual(expectedReaction); + }); +}); diff --git a/src/utils/reaction/mapNewReactionToReaction.ts b/src/utils/reaction/mapNewReactionToReaction.ts new file mode 100644 index 0000000000000000000000000000000000000000..a5fbd0e4f144430e5adbeeece3230e3baa352e59 --- /dev/null +++ b/src/utils/reaction/mapNewReactionToReaction.ts @@ -0,0 +1,45 @@ +/* eslint-disable no-magic-numbers */ +import { NewReaction, Reaction, ReactionLine } from '@/types/models'; +import ReactionTypeEnum from '@/utils/reaction/ReactionTypeEnum'; + +type ReactionTypeKey = keyof typeof ReactionTypeEnum; + +export default function mapNewReactionToReaction(newReaction: NewReaction): Reaction { + const lines: Array<ReactionLine> = []; + let start; + let end; + newReaction.line.segments.forEach(segment => { + start = { x: segment.x1, y: segment.y1 }; + end = { x: segment.x2, y: segment.y2 }; + lines.push({ start, end, type: '' }); + }); + [ + ...newReaction.products, + ...newReaction.reactants, + ...newReaction.modifiers, + ...newReaction.operators, + ].forEach(element => { + element.line.segments.forEach(segment => { + start = { x: segment.x1, y: segment.y1 }; + end = { x: segment.x2, y: segment.y2 }; + lines.push({ start, end, type: '' }); + }); + }); + + return { + centerPoint: { x: 0, y: 0 }, + hierarchyVisibilityLevel: '', + id: newReaction.id, + kineticLaw: null, + lines, + modelId: newReaction.model, + modifiers: [], + name: '', + notes: newReaction.notes, + products: [], + reactants: [], + reactionId: newReaction.idReaction, + references: newReaction.references, + type: ReactionTypeEnum[newReaction.sboTerm as ReactionTypeKey], + }; +}