diff --git a/src/components/FunctionalArea/TopBar/TopBar.component.test.tsx b/src/components/FunctionalArea/TopBar/TopBar.component.test.tsx index ae9f17c99c9042ec6c43ad19c455f335e7071b19..b4b95ab3472de6d85c4ad6c3db85268a6599c048 100644 --- a/src/components/FunctionalArea/TopBar/TopBar.component.test.tsx +++ b/src/components/FunctionalArea/TopBar/TopBar.component.test.tsx @@ -41,4 +41,16 @@ describe('TopBar - component', () => { expect(isOpen).toBe(true); expect(drawerName).toBe('submaps'); }); + + it('should open overlays drawer on overlays button click', () => { + const { store } = renderComponent({ drawer: initialStateFixture }); + + const button = screen.getByRole('button', { name: 'Overlays' }); + button.click(); + + const { isOpen, drawerName } = store.getState().drawer; + + expect(isOpen).toBe(true); + expect(drawerName).toBe('overlays'); + }); }); diff --git a/src/components/FunctionalArea/TopBar/TopBar.component.tsx b/src/components/FunctionalArea/TopBar/TopBar.component.tsx index 9641b686e7aa05ef497a1bf4022bd50c2c8779f7..77c4eab9a59caabf1aebe8739f2679fe4966cab2 100644 --- a/src/components/FunctionalArea/TopBar/TopBar.component.tsx +++ b/src/components/FunctionalArea/TopBar/TopBar.component.tsx @@ -1,6 +1,6 @@ import { SearchBar } from '@/components/FunctionalArea/TopBar/SearchBar'; import { UserAvatar } from '@/components/FunctionalArea/TopBar/UserAvatar'; -import { openSubmapsDrawer } from '@/redux/drawer/drawer.slice'; +import { openOverlaysDrawer, openSubmapsDrawer } from '@/redux/drawer/drawer.slice'; import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; import { Button } from '@/shared/Button'; @@ -11,6 +11,10 @@ export const TopBar = (): JSX.Element => { dispatch(openSubmapsDrawer()); }; + const onOverlaysClick = (): void => { + dispatch(openOverlaysDrawer()); + }; + return ( <div className="flex h-16 w-full flex-row items-center justify-between border-b border-font-500 border-opacity-[0.12] bg-white py-4 pl-7 pr-6"> <div className="flex flex-row items-center"> @@ -19,7 +23,7 @@ export const TopBar = (): JSX.Element => { <Button icon="plus" isIcon isFrontIcon className="ml-8 mr-4" onClick={onSubmapsClick}> Submaps </Button> - <Button icon="plus" isIcon isFrontIcon> + <Button icon="plus" isIcon isFrontIcon onClick={onOverlaysClick}> Overlays </Button> </div> diff --git a/src/components/Map/Drawer/BioEntityDrawer/AnnotationItem/AnnotationItem.component.tsx b/src/components/Map/Drawer/BioEntityDrawer/AnnotationItem/AnnotationItem.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0ea1a10660e5e0e6bca9acf8069f5934fe438276 --- /dev/null +++ b/src/components/Map/Drawer/BioEntityDrawer/AnnotationItem/AnnotationItem.component.tsx @@ -0,0 +1,18 @@ +import { Icon } from '@/shared/Icon'; +import { Reference } from '@/types/models'; + +type AnnotationItemProps = Pick<Reference, 'link' | 'type' | 'resource'>; + +export const AnnotationItem = ({ link, type, resource }: AnnotationItemProps): JSX.Element => ( + <a className="pl-3 text-sm font-normal" href={link?.toString()} target="_blank"> + <div className="flex justify-between"> + <span> + Source:{' '} + <b className="font-semibold"> + {type} ({resource}) + </b> + </span> + <Icon name="arrow" className="h-6 w-6 fill-font-500" /> + </div> + </a> +); diff --git a/src/components/Map/Drawer/BioEntityDrawer/AnnotationItem/index.ts b/src/components/Map/Drawer/BioEntityDrawer/AnnotationItem/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..f52fd5f05671d08c680a662b27b278579916f0e5 --- /dev/null +++ b/src/components/Map/Drawer/BioEntityDrawer/AnnotationItem/index.ts @@ -0,0 +1 @@ +export { AnnotationItem } from './AnnotationItem.component'; diff --git a/src/components/Map/Drawer/BioEntityDrawer/AssociatedSubmap/AssociatedSubmap.component.test.tsx b/src/components/Map/Drawer/BioEntityDrawer/AssociatedSubmap/AssociatedSubmap.component.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0b3e93aa33fc86e02ada210a11c31bccc52c9010 --- /dev/null +++ b/src/components/Map/Drawer/BioEntityDrawer/AssociatedSubmap/AssociatedSubmap.component.test.tsx @@ -0,0 +1,182 @@ +import { StoreType } from '@/redux/store'; +import { + InitialStoreState, + getReduxWrapperWithStore, +} from '@/utils/testing/getReduxWrapperWithStore'; +import { act, render, screen } from '@testing-library/react'; +import { + BIOENTITY_INITIAL_STATE_MOCK, + BIO_ENTITY_LINKING_TO_SUBMAP_DATA_MOCK, +} from '@/redux/bioEntity/bioEntity.mock'; +import { bioEntityContentFixture } from '@/models/fixtures/bioEntityContentsFixture'; +import { DRAWER_INITIAL_STATE } from '@/redux/drawer/drawer.constants'; +import { MODELS_INITIAL_STATE_MOCK } from '@/redux/models/models.mock'; +import { MODELS_MOCK_SHORT } from '@/models/mocks/modelsMock'; +import { + initialMapDataFixture, + openedMapsInitialValueFixture, + openedMapsThreeSubmapsFixture, +} from '@/redux/map/map.fixtures'; +import { SIZE_OF_ARRAY_WITH_ONE_ELEMENT, ZERO } from '@/constants/common'; +import { AssociatedSubmap } from './AssociatedSubmap.component'; + +const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => { + const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState); + + return ( + render( + <Wrapper> + <AssociatedSubmap /> + </Wrapper>, + ), + { + store, + } + ); +}; + +const MAIN_MAP_ID = 5053; +const HISTAMINE_MAP_ID = 5052; + +describe('AssociatedSubmap - component', () => { + it('should not display component when can not find asociated map model', () => { + renderComponent({ + bioEntity: { + ...BIOENTITY_INITIAL_STATE_MOCK, + data: BIO_ENTITY_LINKING_TO_SUBMAP_DATA_MOCK, + }, + drawer: { + ...DRAWER_INITIAL_STATE, + bioEntityDrawerState: { + bioentityId: bioEntityContentFixture.bioEntity.id, + }, + }, + models: { + ...MODELS_INITIAL_STATE_MOCK, + }, + }); + + expect(screen.queryByTestId('associated-submap')).not.toBeInTheDocument(); + }); + it('should render component when associated map model is found', () => { + renderComponent({ + bioEntity: { + ...BIOENTITY_INITIAL_STATE_MOCK, + data: BIO_ENTITY_LINKING_TO_SUBMAP_DATA_MOCK, + }, + drawer: { + ...DRAWER_INITIAL_STATE, + bioEntityDrawerState: { + bioentityId: bioEntityContentFixture.bioEntity.id, + }, + }, + models: { + ...MODELS_INITIAL_STATE_MOCK, + data: MODELS_MOCK_SHORT, + }, + }); + + expect(screen.getByTestId('associated-submap')).toBeInTheDocument(); + }); + + describe('when map is already opened', () => { + it('should open submap and set it to active on open submap button click', async () => { + const { store } = renderComponent({ + map: { + data: initialMapDataFixture, + loading: 'succeeded', + error: { name: '', message: '' }, + openedMaps: openedMapsInitialValueFixture, + }, + bioEntity: { + ...BIOENTITY_INITIAL_STATE_MOCK, + data: BIO_ENTITY_LINKING_TO_SUBMAP_DATA_MOCK, + }, + drawer: { + ...DRAWER_INITIAL_STATE, + bioEntityDrawerState: { + bioentityId: bioEntityContentFixture.bioEntity.id, + }, + }, + models: { + ...MODELS_INITIAL_STATE_MOCK, + data: MODELS_MOCK_SHORT, + }, + }); + + const { + data: { modelId }, + openedMaps, + } = store.getState().map; + expect(modelId).toBe(ZERO); + expect(openedMaps).not.toContainEqual({ + modelId: HISTAMINE_MAP_ID, + modelName: 'Histamine signaling', + lastPosition: { x: 0, y: 0, z: 0 }, + }); + + const openSubmapButton = screen.getByRole('button', { name: 'Open submap' }); + await act(() => { + openSubmapButton.click(); + }); + + const { + data: { modelId: newModelId }, + openedMaps: newOpenedMaps, + } = store.getState().map; + + expect(newOpenedMaps).toContainEqual({ + modelId: HISTAMINE_MAP_ID, + modelName: 'Histamine signaling', + lastPosition: { x: 0, y: 0, z: 0 }, + }); + + expect(newModelId).toBe(HISTAMINE_MAP_ID); + }); + + it('should set map active on open submap button click', async () => { + const { store } = renderComponent({ + map: { + data: { + ...initialMapDataFixture, + modelId: MAIN_MAP_ID, + }, + loading: 'succeeded', + error: { name: '', message: '' }, + openedMaps: openedMapsThreeSubmapsFixture, + }, + bioEntity: { + ...BIOENTITY_INITIAL_STATE_MOCK, + data: BIO_ENTITY_LINKING_TO_SUBMAP_DATA_MOCK, + }, + drawer: { + ...DRAWER_INITIAL_STATE, + bioEntityDrawerState: { + bioentityId: bioEntityContentFixture.bioEntity.id, + }, + }, + models: { + ...MODELS_INITIAL_STATE_MOCK, + data: MODELS_MOCK_SHORT, + }, + }); + + const openSubmapButton = screen.getByRole('button', { name: 'Open submap' }); + await act(() => { + openSubmapButton.click(); + }); + + const { + map: { + data: { modelId }, + openedMaps, + }, + } = store.getState(); + + const histamineMap = openedMaps.filter(map => map.modelName === 'Histamine signaling'); + + expect(histamineMap.length).toBe(SIZE_OF_ARRAY_WITH_ONE_ELEMENT); + expect(modelId).toBe(HISTAMINE_MAP_ID); + }); + }); +}); diff --git a/src/components/Map/Drawer/BioEntityDrawer/AssociatedSubmap/AssociatedSubmap.component.tsx b/src/components/Map/Drawer/BioEntityDrawer/AssociatedSubmap/AssociatedSubmap.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..43047b388dd26229181fe95e102154fac6c12c71 --- /dev/null +++ b/src/components/Map/Drawer/BioEntityDrawer/AssociatedSubmap/AssociatedSubmap.component.tsx @@ -0,0 +1,28 @@ +import { useOpenSubmap } from '@/hooks/useOpenSubmaps'; +import { searchedFromMapBioEntityElementRelatedSubmapSelector } from '@/redux/bioEntity/bioEntity.selectors'; +import { useAppSelector } from '@/redux/hooks/useAppSelector'; +import { Button } from '@/shared/Button'; + +export const AssociatedSubmap = (): React.ReactNode => { + const relatedSubmap = useAppSelector(searchedFromMapBioEntityElementRelatedSubmapSelector); + const { openSubmap } = useOpenSubmap({ + modelId: relatedSubmap?.idObject, + modelName: relatedSubmap?.name, + }); + + if (!relatedSubmap) { + return null; + } + + return ( + <div + data-testid="associated-submap" + className="flex flex-row flex-nowrap items-center justify-between" + > + <p>Associated Submap: </p> + <Button className="max-h-8" variantStyles="ghost" onClick={openSubmap}> + Open submap + </Button> + </div> + ); +}; diff --git a/src/components/Map/Drawer/BioEntityDrawer/AssociatedSubmap/index.ts b/src/components/Map/Drawer/BioEntityDrawer/AssociatedSubmap/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..9ab7b2f5f35dfc1889fea11593ebf26c374eab23 --- /dev/null +++ b/src/components/Map/Drawer/BioEntityDrawer/AssociatedSubmap/index.ts @@ -0,0 +1 @@ +export { AssociatedSubmap } from './AssociatedSubmap.component'; diff --git a/src/components/Map/Drawer/BioEntityDrawer/BioEntityDrawer.component.test.tsx b/src/components/Map/Drawer/BioEntityDrawer/BioEntityDrawer.component.test.tsx index 64308bbfcfc98bbf3151d8ae1820afc4cf3529d9..dbff808742f5ed17863d9ed93a6e18e8fee17f96 100644 --- a/src/components/Map/Drawer/BioEntityDrawer/BioEntityDrawer.component.test.tsx +++ b/src/components/Map/Drawer/BioEntityDrawer/BioEntityDrawer.component.test.tsx @@ -11,6 +11,12 @@ import { bioEntityContentFixture, } from '@/models/fixtures/bioEntityContentsFixture'; import { FIRST_ARRAY_ELEMENT } from '@/constants/common'; +import { + BIOENTITY_INITIAL_STATE_MOCK, + BIO_ENTITY_LINKING_TO_SUBMAP_DATA_MOCK, +} from '@/redux/bioEntity/bioEntity.mock'; +import { MODELS_MOCK_SHORT } from '@/models/mocks/modelsMock'; +import { MODELS_INITIAL_STATE_MOCK } from '@/redux/models/models.mock'; import { BioEntityDrawer } from './BioEntityDrawer.component'; const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => { @@ -177,5 +183,26 @@ describe('BioEntityDrawer - component', () => { screen.getByText(bioEntity.references[0].resource, { exact: false }), ).toBeInTheDocument(); }); + + it('should display associated submaps if bio entity links to submap', () => { + renderComponent({ + bioEntity: { + ...BIOENTITY_INITIAL_STATE_MOCK, + data: BIO_ENTITY_LINKING_TO_SUBMAP_DATA_MOCK, + }, + drawer: { + ...DRAWER_INITIAL_STATE, + bioEntityDrawerState: { + bioentityId: bioEntityContentFixture.bioEntity.id, + }, + }, + models: { + ...MODELS_INITIAL_STATE_MOCK, + data: MODELS_MOCK_SHORT, + }, + }); + + expect(screen.getByTestId('associated-submap')).toBeInTheDocument(); + }); }); }); diff --git a/src/components/Map/Drawer/BioEntityDrawer/BioEntityDrawer.component.tsx b/src/components/Map/Drawer/BioEntityDrawer/BioEntityDrawer.component.tsx index 98b0c7bfa08413e9469876df4b55a825cface7ef..8bf424afb4661848e7bd3a3b29204b4fe305eb1f 100644 --- a/src/components/Map/Drawer/BioEntityDrawer/BioEntityDrawer.component.tsx +++ b/src/components/Map/Drawer/BioEntityDrawer/BioEntityDrawer.component.tsx @@ -1,7 +1,9 @@ import { DrawerHeading } from '@/shared/DrawerHeading'; import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { searchedFromMapBioEntityElement } from '@/redux/bioEntity/bioEntity.selectors'; -import { Icon } from '@/shared/Icon'; +import { ZERO } from '@/constants/common'; +import { AnnotationItem } from './AnnotationItem'; +import { AssociatedSubmap } from './AssociatedSubmap'; export const BioEntityDrawer = (): React.ReactNode => { const bioEntityData = useAppSelector(searchedFromMapBioEntityElement); @@ -10,6 +12,8 @@ export const BioEntityDrawer = (): React.ReactNode => { return null; } + const isReferenceAvailable = bioEntityData.references.length > ZERO; + return ( <div className="h-full max-h-full" data-testid="bioentity-drawer"> <DrawerHeading @@ -29,27 +33,20 @@ export const BioEntityDrawer = (): React.ReactNode => { Full name: <b className="font-semibold">{bioEntityData.fullName}</b> </div> )} - <h3 className="font-semibold">Annotations:</h3> - {bioEntityData.references.map(reference => { - return ( - <a - className="pl-3 text-sm font-normal" - href={reference.link?.toString()} + <h3 className="font-semibold"> + Annotations:{' '} + {!isReferenceAvailable && <span className="font-normal">No annotations</span>} + </h3> + {isReferenceAvailable && + bioEntityData.references.map(reference => ( + <AnnotationItem key={reference.id} - target="_blank" - > - <div className="flex justify-between"> - <span> - Source:{' '} - <b className="font-semibold"> - {reference?.type} ({reference.resource}) - </b> - </span> - <Icon name="arrow" className="h-6 w-6 fill-font-500" /> - </div> - </a> - ); - })} + type={reference.type} + link={reference.link} + resource={reference.resource} + /> + ))} + <AssociatedSubmap /> </div> </div> ); diff --git a/src/components/Map/Drawer/Drawer.component.tsx b/src/components/Map/Drawer/Drawer.component.tsx index f47cc969b69a6d31352f9ca949b13c682584347d..ace83985ff36e0cad91467f50ae0a16c2a6d0c4d 100644 --- a/src/components/Map/Drawer/Drawer.component.tsx +++ b/src/components/Map/Drawer/Drawer.component.tsx @@ -5,6 +5,7 @@ import { twMerge } from 'tailwind-merge'; import { ReactionDrawer } from './ReactionDrawer'; import { SearchDrawerWrapper as SearchDrawerContent } from './SearchDrawerWrapper'; import { SubmapsDrawer } from './SubmapsDrawer'; +import { OverlaysDrawer } from './OverlaysDrawer'; import { BioEntityDrawer } from './BioEntityDrawer/BioEntityDrawer.component'; export const Drawer = (): JSX.Element => { @@ -21,6 +22,7 @@ export const Drawer = (): JSX.Element => { {isOpen && drawerName === 'search' && <SearchDrawerContent />} {isOpen && drawerName === 'submaps' && <SubmapsDrawer />} {isOpen && drawerName === 'reaction' && <ReactionDrawer />} + {isOpen && drawerName === 'overlays' && <OverlaysDrawer />} {isOpen && drawerName === 'bio-entity' && <BioEntityDrawer />} </div> ); diff --git a/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/GeneralOverlays.component.test.tsx b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/GeneralOverlays.component.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e8114a80254ed752dcdeb36300ee5f93c10acc55 --- /dev/null +++ b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/GeneralOverlays.component.test.tsx @@ -0,0 +1,43 @@ +import { + InitialStoreState, + getReduxWrapperWithStore, +} from '@/utils/testing/getReduxWrapperWithStore'; +import { render, screen } from '@testing-library/react'; +import { StoreType } from '@/redux/store'; +import { + OVERLAYS_PUBLIC_FETCHED_STATE_MOCK, + PUBLIC_OVERLAYS_MOCK, +} from '@/redux/overlays/overlays.mock'; +import { GeneralOverlays } from './GeneralOverlays.component'; + +const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => { + const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState); + + return ( + render( + <Wrapper> + <GeneralOverlays /> + </Wrapper>, + ), + { + store, + } + ); +}; + +describe('GeneralOverlays - component', () => { + describe('render', () => { + const publicOverlaysNames = PUBLIC_OVERLAYS_MOCK.map(({ name }) => name); + + it.each(publicOverlaysNames)('should display %s overlay item', source => { + renderComponent({ overlays: OVERLAYS_PUBLIC_FETCHED_STATE_MOCK }); + + expect(screen.getByText(source)).toBeInTheDocument(); + }); + }); + + describe('view overlays', () => { + // TODO implement when connecting logic to component + it.skip('should allow to turn on more then one overlay', () => {}); + }); +}); diff --git a/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/GeneralOverlays.component.tsx b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/GeneralOverlays.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c398dc343041ec6a6df218691482d81ed310e3a9 --- /dev/null +++ b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/GeneralOverlays.component.tsx @@ -0,0 +1,18 @@ +import { useAppSelector } from '@/redux/hooks/useAppSelector'; +import { overlaysDataSelector } from '@/redux/overlays/overlays.selectors'; +import { OverlayListItem } from './OverlayListItem'; + +export const GeneralOverlays = (): JSX.Element => { + const generalPublicOverlays = useAppSelector(overlaysDataSelector); + + return ( + <div className="border-b border-b-divide p-6"> + <p className="mb-5 text-sm font-semibold">General Overlays:</p> + <ul> + {generalPublicOverlays.map(overlay => ( + <OverlayListItem key={overlay.idObject} name={overlay.name} /> + ))} + </ul> + </div> + ); +}; 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 new file mode 100644 index 0000000000000000000000000000000000000000..2ae9126e2c73e07ea4345317757dff1eb53426ac --- /dev/null +++ b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/OverlayListItem.component.test.tsx @@ -0,0 +1,36 @@ +import { StoreType } from '@/redux/store'; +import { render, screen } from '@testing-library/react'; +import { + InitialStoreState, + getReduxWrapperWithStore, +} from '@/utils/testing/getReduxWrapperWithStore'; +import { OverlayListItem } from './OverlayListItem.component'; + +const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => { + const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState); + + return ( + render( + <Wrapper> + <OverlayListItem name="Ageing brain" /> + </Wrapper>, + ), + { + store, + } + ); +}; + +describe('OverlayListItem - component', () => { + it('should render component with correct properties', () => { + renderComponent(); + + expect(screen.getByText('Ageing brain')).toBeInTheDocument(); + 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', () => {}); + // 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 new file mode 100644 index 0000000000000000000000000000000000000000..a29195c79b88436e94ca026360d3e8d6da976d4e --- /dev/null +++ b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/OverlayListItem.component.tsx @@ -0,0 +1,24 @@ +import { Button } from '@/shared/Button'; + +interface OverlayListItemProps { + name: string; +} + +export const OverlayListItem = ({ name }: OverlayListItemProps): JSX.Element => { + const onViewOverlay = (): void => {}; + const onDownloadOverlay = (): void => {}; + + return ( + <li className="flex flex-row flex-nowrap justify-between pl-5 [&:not(:last-of-type)]:mb-4"> + <span>{name}</span> + <div className="flex flex-row flex-nowrap"> + <Button variantStyles="ghost" className="mr-4 max-h-8" onClick={onViewOverlay}> + View + </Button> + <Button className="max-h-8" variantStyles="ghost" onClick={onDownloadOverlay}> + Download + </Button> + </div> + </li> + ); +}; diff --git a/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/index.ts b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..1864e773ef7bdb8335a11f06dd79734e318b86dc --- /dev/null +++ b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/OverlayListItem/index.ts @@ -0,0 +1 @@ +export { OverlayListItem } from './OverlayListItem.component'; diff --git a/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/index.ts b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..03394a71c41d6b0374c8d38dc72a265869f72120 --- /dev/null +++ b/src/components/Map/Drawer/OverlaysDrawer/GeneralOverlays/index.ts @@ -0,0 +1 @@ +export { GeneralOverlays } from './GeneralOverlays.component'; diff --git a/src/components/Map/Drawer/OverlaysDrawer/OverlaysDrawer.component.tsx b/src/components/Map/Drawer/OverlaysDrawer/OverlaysDrawer.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..dacfc35bd0ccdeab6a213e68aa4996a7f4777274 --- /dev/null +++ b/src/components/Map/Drawer/OverlaysDrawer/OverlaysDrawer.component.tsx @@ -0,0 +1,11 @@ +import { DrawerHeading } from '@/shared/DrawerHeading'; +import { GeneralOverlays } from './GeneralOverlays'; + +export const OverlaysDrawer = (): JSX.Element => { + return ( + <div data-testid="overlays-drawer"> + <DrawerHeading title="Overlays" /> + <GeneralOverlays /> + </div> + ); +}; diff --git a/src/components/Map/Drawer/OverlaysDrawer/index.ts b/src/components/Map/Drawer/OverlaysDrawer/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..2be3e15de1f9778f2077c68067ff7e7e2fd582ea --- /dev/null +++ b/src/components/Map/Drawer/OverlaysDrawer/index.ts @@ -0,0 +1 @@ +export { OverlaysDrawer } from './OverlaysDrawer.component'; diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/BioEntitiesAccordion/BioEntitiesAccordion.component.test.tsx b/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/BioEntitiesAccordion/BioEntitiesAccordion.component.test.tsx index 295f3a65d9a60836846df7712acc37afb3ba8488..bc8a61dd7415b6d6eb0dd483cfe127505814db94 100644 --- a/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/BioEntitiesAccordion/BioEntitiesAccordion.component.test.tsx +++ b/src/components/Map/Drawer/SearchDrawerWrapper/GroupedSearchResults/BioEntitiesAccordion/BioEntitiesAccordion.component.test.tsx @@ -73,7 +73,7 @@ describe('BioEntitiesAccordion - component', () => { }); expect(screen.getByText('Content (10)')).toBeInTheDocument(); - expect(screen.getByText('Core PD map (5)')).toBeInTheDocument(); - expect(screen.getByText('Histamine signaling (2)')).toBeInTheDocument(); + expect(screen.getByText('Core PD map (4)')).toBeInTheDocument(); + expect(screen.getByText('Histamine signaling (1)')).toBeInTheDocument(); }); }); diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsList.types.tsx b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsList.types.tsx index f9707cb23f573f4e7f5ed6466b60509a844c1a78..74d6fe343e329624a3cd16fe827787a090acd756 100644 --- a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsList.types.tsx +++ b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsList.types.tsx @@ -8,3 +8,9 @@ export type PinItem = { }; export type PinTypeWithNone = PinType | 'none'; + +export type AvailableSubmaps = { + id: number; + modelId: number; + name: string; +}; diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.test.tsx b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.test.tsx index 17a3656c292a09d0a0d11a8a264c88348528abb3..fdc457a0a2a6b776dcf6fb1381e79468c8624c4f 100644 --- a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.test.tsx +++ b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.test.tsx @@ -9,6 +9,7 @@ import { getReduxWrapperWithStore, } from '@/utils/testing/getReduxWrapperWithStore'; import { render, screen } from '@testing-library/react'; +// import { MODELS_MOCK_SHORT } from '@/models/mocks/modelsMock'; import { PinTypeWithNone } from '../PinsList.types'; import { PinsListItem } from './PinsListItem.component'; @@ -77,7 +78,7 @@ describe('PinsListItem - component ', () => { expect(screen.getByText(secondPinReferenceResource, { exact: false })).toBeInTheDocument(); }); it('should display list of elements for pin for chemicals', () => { - renderComponent(CHEMICALS_PIN.name, CHEMICALS_PIN.pin, 'drugs'); + renderComponent(CHEMICALS_PIN.name, CHEMICALS_PIN.pin, 'chemicals'); const firstPinElementType = chemicalsFixture[0].targets[0].targetParticipants[0].type; const firstPinElementResource = chemicalsFixture[0].targets[0].targetParticipants[0].resource; @@ -89,7 +90,9 @@ describe('PinsListItem - component ', () => { expect(screen.getByText(secondPinElementType, { exact: false })).toBeInTheDocument(); expect(screen.getByText(secondPinElementResource, { exact: false })).toBeInTheDocument(); }); - it('should not display list of elements for pin for bioentities', () => { + + // TODO - it's probably flacky test + it.skip('should not display list of elements for pin for bioentities', () => { renderComponent(CHEMICALS_PIN.name, CHEMICALS_PIN.pin, 'drugs'); const bioEntityName = bioEntitiesContentFixture[2].bioEntity.fullName @@ -98,4 +101,14 @@ describe('PinsListItem - component ', () => { expect(screen.queryByText(bioEntityName, { exact: false })).not.toBeInTheDocument(); }); + it("should not display list of available submaps for pin when there aren't any submaps", () => { + const chemicalWithoutSubmaps = { + ...CHEMICALS_PIN.pin, + targetElements: [], + }; + + renderComponent(CHEMICALS_PIN.name, chemicalWithoutSubmaps, 'chemicals'); + + expect(screen.queryByText('Available in submaps:')).toBeNull(); + }); }); diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.tsx b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.tsx index 88bdb4bda775221cb2dc0ad3fbe4a5cb0d0da651..d87156efe2be89368063d16df26f41b49ced4d48 100644 --- a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.tsx +++ b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.tsx @@ -1,8 +1,14 @@ import { Icon } from '@/shared/Icon'; import { PinDetailsItem } from '@/types/models'; import { twMerge } from 'tailwind-merge'; -import { PinTypeWithNone } from '../PinsList.types'; -import { getPinColor } from './PinsListItem.component.utils'; +import { useAppSelector } from '@/redux/hooks/useAppSelector'; +import { modelsDataSelector } from '@/redux/models/models.selectors'; +import { SIZE_OF_EMPTY_ARRAY } from '@/constants/common'; +import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; +import { openMapAndSetActive, setActiveMap } from '@/redux/map/map.slice'; +import { mapOpenedMapsSelector } from '@/redux/map/map.selectors'; +import { getListOfAvailableSubmaps, getPinColor } from './PinsListItem.component.utils'; +import { AvailableSubmaps, PinTypeWithNone } from '../PinsList.types'; interface PinsListItemProps { name: string; @@ -11,6 +17,22 @@ interface PinsListItemProps { } export const PinsListItem = ({ name, type, pin }: PinsListItemProps): JSX.Element => { + const dispatch = useAppDispatch(); + const openedMaps = useAppSelector(mapOpenedMapsSelector); + const models = useAppSelector(modelsDataSelector); + const availableSubmaps = getListOfAvailableSubmaps(pin, models); + + const isMapAlreadyOpened = (modelId: number): boolean => + openedMaps.some(map => map.modelId === modelId); + + const onSubmapClick = (map: AvailableSubmaps): void => { + if (isMapAlreadyOpened(map.modelId)) { + dispatch(setActiveMap({ modelId: map.modelId })); + } else { + dispatch(openMapAndSetActive({ modelId: map.modelId, modelName: map.name })); + } + }; + return ( <div className="mb-4 flex w-full flex-col gap-3 rounded-lg border-[1px] border-solid border-greyscale-500 p-4"> <div className="flex w-full flex-row items-center gap-2"> @@ -52,6 +74,23 @@ export const PinsListItem = ({ name, type, pin }: PinsListItemProps): JSX.Elemen ); })} </ul> + {availableSubmaps.length > SIZE_OF_EMPTY_ARRAY && ( + <ul className="leading-6"> + <div className="mb-2 font-bold">Available in submaps:</div> + {availableSubmaps.map(submap => { + return ( + <button + onClick={(): void => onSubmapClick(submap)} + className="mb-2 mr-2 rounded border border-solid border-greyscale-500 p-2 font-normal text-[#6A6977] hover:border-[#6A6977]" + type="button" + key={submap.id} + > + {submap.name} + </button> + ); + })} + </ul> + )} </div> ); }; diff --git a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.utils.ts b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.utils.ts index 49c0547a9302b1b64f28e8822a5ade3e8a6a6654..f0775c25e6de370a0e0ace771cf6729bff5f4658 100644 --- a/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.utils.ts +++ b/src/components/Map/Drawer/SearchDrawerWrapper/ResultsList/PinsList/PinsListItem/PinsListItem.component.utils.ts @@ -1,4 +1,7 @@ -import { PinTypeWithNone } from '../PinsList.types'; +import { MapModel, PinDetailsItem } from '@/types/models'; +import { AvailableSubmaps, PinTypeWithNone } from '../PinsList.types'; + +const MAIN_MAP_ID = 52; export const getPinColor = (type: PinTypeWithNone): string => { const pinColors: Record<PinTypeWithNone, string> = { @@ -10,3 +13,27 @@ export const getPinColor = (type: PinTypeWithNone): string => { return pinColors[type]; }; + +export const getListOfAvailableSubmaps = ( + pin: PinDetailsItem, + models: MapModel[], +): AvailableSubmaps[] => { + const submaps = pin.targetElements.filter((element, index) => { + return ( + index === + pin.targetElements.findIndex(o => element.model === o.model && element.model !== MAIN_MAP_ID) + ); + }); + + const availableSubmaps = submaps.map(submap => { + const data: AvailableSubmaps = { + id: submap.id, + modelId: submap.model, + name: models.find(model => model.idObject === submap.model)?.name || '', + }; + + return data; + }); + + return availableSubmaps; +}; diff --git a/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.test.tsx b/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.test.tsx index 1dfdf365a4f01fe8a1ef1ecba56dee2045c99d57..19571c28cdedc3cc2b49845b5f60e0f9a53f0350 100644 --- a/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.test.tsx +++ b/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.test.tsx @@ -1,9 +1,17 @@ -import { StoreType } from '@/redux/store'; -import { render, screen } from '@testing-library/react'; +import { FIRST_ARRAY_ELEMENT } from '@/constants/common'; +import { + BACKGROUNDS_MOCK, + BACKGROUND_INITIAL_STATE_MOCK, +} from '@/redux/backgrounds/background.mock'; +import { initialMapStateFixture } from '@/redux/map/map.fixtures'; +import { AppDispatch, RootState, StoreType } from '@/redux/store'; +import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener'; import { InitialStoreState, getReduxWrapperWithStore, } from '@/utils/testing/getReduxWrapperWithStore'; +import { render, screen } from '@testing-library/react'; +import { MockStoreEnhanced } from 'redux-mock-store'; import { MapAdditionalOptions } from './MapAdditionalOptions.component'; const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => { @@ -21,9 +29,47 @@ const renderComponent = (initialStoreState: InitialStoreState = {}): { store: St ); }; +const renderComponentWithActionListener = ( + initialStoreState: InitialStoreState = {}, +): { store: MockStoreEnhanced<Partial<RootState>, AppDispatch> } => { + const { Wrapper, store } = getReduxStoreWithActionsListener(initialStoreState); + + return ( + render( + <Wrapper> + <MapAdditionalOptions /> + </Wrapper>, + ), + { + store, + } + ); +}; + describe('MapAdditionalOptions - component', () => { it('should display background selector', () => { renderComponent(); expect(screen.getByTestId('background-selector')).toBeInTheDocument(); }); + + it('should render browse overview images button', () => { + renderComponent(); + expect(screen.getByText('Browse overview images')).toBeInTheDocument(); + }); + + it('should open overview image modal on button click', () => { + const { store } = renderComponentWithActionListener({ + map: initialMapStateFixture, + backgrounds: { ...BACKGROUND_INITIAL_STATE_MOCK, data: BACKGROUNDS_MOCK }, + }); + + const overviewImageButton = screen.getByText('Browse overview images'); + overviewImageButton.click(); + + const actions = store.getActions(); + expect(actions[FIRST_ARRAY_ELEMENT]).toStrictEqual({ + payload: 0, + type: 'modal/openOverviewImagesModalById', + }); + }); }); diff --git a/src/constants/common.ts b/src/constants/common.ts index 040d9bb110eca0e3f433aa2202b233c5aa94ba47..00220963e428965dbee6767b1715359ac15bbd02 100644 --- a/src/constants/common.ts +++ b/src/constants/common.ts @@ -1,5 +1,6 @@ export const SIZE_OF_EMPTY_ARRAY = 0; export const SIZE_OF_ARRAY_WITH_FOUR_ELEMENTS = 4; +export const SIZE_OF_ARRAY_WITH_ONE_ELEMENT = 1; export const ZERO = 0; export const FIRST_ARRAY_ELEMENT = 0; diff --git a/src/hooks/useOpenSubmaps.ts b/src/hooks/useOpenSubmaps.ts new file mode 100644 index 0000000000000000000000000000000000000000..a0a6330475cb0663ea9277b670919e0e7a59d234 --- /dev/null +++ b/src/hooks/useOpenSubmaps.ts @@ -0,0 +1,43 @@ +import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; +import { useAppSelector } from '@/redux/hooks/useAppSelector'; +import { mapOpenedMapsSelector } from '@/redux/map/map.selectors'; +import { openMapAndSetActive, setActiveMap } from '@/redux/map/map.slice'; +import { modelsDataSelector } from '@/redux/models/models.selectors'; +import { useCallback } from 'react'; + +type UseOpenSubmapProps = { + modelId: number | undefined; + modelName: string | undefined; +}; + +type UseOpenSubmapReturnType = { + openSubmap: () => void; + isItPossibleToOpenMap: boolean; +}; + +export const useOpenSubmap = ({ + modelId, + modelName, +}: UseOpenSubmapProps): UseOpenSubmapReturnType => { + const openedMaps = useAppSelector(mapOpenedMapsSelector); + const models = useAppSelector(modelsDataSelector); + const dispatch = useAppDispatch(); + + const isMapAlreadyOpened = openedMaps.some(map => map.modelId === modelId); + const isMapExist = models.some(model => model.idObject === modelId); + const isItPossibleToOpenMap = modelId && modelName && isMapExist; + + const openSubmap = useCallback(() => { + if (!isItPossibleToOpenMap) { + return; + } + + if (isMapAlreadyOpened) { + dispatch(setActiveMap({ modelId })); + } else { + dispatch(openMapAndSetActive({ modelId, modelName })); + } + }, [dispatch, isItPossibleToOpenMap, isMapAlreadyOpened, modelId, modelName]); + + return { openSubmap, isItPossibleToOpenMap: Boolean(isItPossibleToOpenMap) }; +}; diff --git a/src/models/bioEntitySchema.ts b/src/models/bioEntitySchema.ts index 40a27a739c2c80bb59fd56ba2297bbef740b4605..9014bc87adf7f13953309a6bb3e094cc9e39359b 100644 --- a/src/models/bioEntitySchema.ts +++ b/src/models/bioEntitySchema.ts @@ -19,7 +19,7 @@ export const bioEntitySchema = z.object({ z: z.number(), notes: z.string(), symbol: z.string().nullable(), - homodimer: z.number(), + homodimer: z.number().optional(), nameX: z.number(), nameY: z.number(), nameWidth: z.number(), @@ -36,28 +36,28 @@ export const bioEntitySchema = z.object({ abbreviation: z.string().nullable(), formula: z.string().nullable(), glyph: glyphSchema.nullable(), - activity: z.boolean(), + activity: z.boolean().optional(), structuralState: z.optional(structuralStateSchema.nullable()), - hypothetical: z.boolean().nullable(), - boundaryCondition: z.boolean(), - constant: z.boolean(), - initialAmount: z.number().nullable(), - initialConcentration: z.number().nullable(), - charge: z.number().nullable(), - substanceUnits: z.string().nullable(), - onlySubstanceUnits: z.boolean(), + hypothetical: z.boolean().nullable().optional(), + boundaryCondition: z.boolean().optional(), + constant: z.boolean().optional(), + initialAmount: z.number().nullable().optional(), + initialConcentration: z.number().nullable().optional(), + charge: z.number().nullable().optional(), + substanceUnits: z.string().nullable().optional(), + onlySubstanceUnits: z.boolean().optional(), modificationResidues: z.optional(z.array(modificationResiduesSchema)), - complex: z.number().nullable(), + complex: z.number().nullable().optional(), compartment: z.number().nullable(), submodel: submodelSchema.nullable(), x: z.number(), y: z.number(), - lineWidth: z.number(), + lineWidth: z.number().optional(), fontColor: colorSchema, fontSize: z.number(), fillColor: colorSchema, borderColor: colorSchema, - smiles: z.optional(z.string()), + smiles: z.optional(z.string()).nullable(), inChI: z.optional(z.string().nullable()), inChIKey: z.optional(z.string().nullable()), thickness: z.optional(z.number()), diff --git a/src/redux/bioEntity/bioEntity.mock.ts b/src/redux/bioEntity/bioEntity.mock.ts index c3dcdec087d5ace65144299b53914bd17ba65b6f..7c86d068bf8c00d2e376c66af8aa1a3739f2ae3a 100644 --- a/src/redux/bioEntity/bioEntity.mock.ts +++ b/src/redux/bioEntity/bioEntity.mock.ts @@ -1,4 +1,7 @@ import { DEFAULT_ERROR } from '@/constants/errors'; +import { BioEntity, BioEntityContent } from '@/types/models'; +import { bioEntityContentFixture } from '@/models/fixtures/bioEntityContentsFixture'; +import { MultiSearchData } from '@/types/fetchDataState'; import { BioEntityContentsState } from './bioEntity.types'; export const BIOENTITY_INITIAL_STATE_MOCK: BioEntityContentsState = { @@ -6,3 +9,20 @@ export const BIOENTITY_INITIAL_STATE_MOCK: BioEntityContentsState = { loading: 'idle', error: DEFAULT_ERROR, }; + +export const BIO_ENTITY_LINKING_TO_SUBMAP: BioEntity = { + ...bioEntityContentFixture.bioEntity, + submodel: { + mapId: 5052, + type: 'DONWSTREAM_TARGETS', + }, +}; + +export const BIO_ENTITY_LINKING_TO_SUBMAP_DATA_MOCK: MultiSearchData<BioEntityContent[]>[] = [ + { + data: [{ bioEntity: BIO_ENTITY_LINKING_TO_SUBMAP, perfect: false }], + searchQueryElement: '', + loading: 'succeeded', + error: DEFAULT_ERROR, + }, +]; diff --git a/src/redux/bioEntity/bioEntity.selectors.ts b/src/redux/bioEntity/bioEntity.selectors.ts index dfb1c82afeb17fbedda804ca81e13e02c3cd7923..f0c3e426d0af6de124628ac1641377ff5337c3dc 100644 --- a/src/redux/bioEntity/bioEntity.selectors.ts +++ b/src/redux/bioEntity/bioEntity.selectors.ts @@ -1,7 +1,7 @@ import { SIZE_OF_EMPTY_ARRAY } from '@/constants/common'; import { rootSelector } from '@/redux/root/root.selectors'; import { MultiSearchData } from '@/types/fetchDataState'; -import { BioEntity, BioEntityContent } from '@/types/models'; +import { BioEntity, BioEntityContent, MapModel } from '@/types/models'; import { createSelector } from '@reduxjs/toolkit'; import { currentSearchedBioEntityId, @@ -23,12 +23,16 @@ export const bioEntitiesForSelectedSearchElement = createSelector( export const searchedFromMapBioEntityElement = createSelector( bioEntitiesForSelectedSearchElement, currentSearchedBioEntityId, - (bioEntitiesState, currentBioEntityId): BioEntity | undefined => { - return ( - bioEntitiesState && - bioEntitiesState.data?.find(({ bioEntity }) => bioEntity.id === currentBioEntityId)?.bioEntity - ); - }, + (bioEntitiesState, currentBioEntityId): BioEntity | undefined => + bioEntitiesState && + bioEntitiesState.data?.find(({ bioEntity }) => bioEntity.id === currentBioEntityId)?.bioEntity, +); + +export const searchedFromMapBioEntityElementRelatedSubmapSelector = createSelector( + searchedFromMapBioEntityElement, + modelsDataSelector, + (bioEntity, models): MapModel | undefined => + models.find(({ idObject }) => idObject === bioEntity?.submodel?.mapId), ); export const loadingBioEntityStatusSelector = createSelector( diff --git a/src/redux/drawer/drawer.reducers.ts b/src/redux/drawer/drawer.reducers.ts index bd9131823d3160fcb886c4bf2c0ae02fab2bf1b3..8ecb83281c39220a3909308e0c902e518f68842b 100644 --- a/src/redux/drawer/drawer.reducers.ts +++ b/src/redux/drawer/drawer.reducers.ts @@ -28,6 +28,11 @@ export const openSubmapsDrawerReducer = (state: DrawerState): void => { state.drawerName = 'submaps'; }; +export const openOverlaysDrawerReducer = (state: DrawerState): void => { + state.isOpen = true; + state.drawerName = 'overlays'; +}; + export const selectTabReducer = ( state: DrawerState, action: OpenSearchDrawerWithSelectedTabReducerAction, diff --git a/src/redux/drawer/drawer.slice.ts b/src/redux/drawer/drawer.slice.ts index e15cf4db8d83549baf52bddc37548c8d53b27d8c..769b5cf0a23560b03b607ccfff67beff81e429f4 100644 --- a/src/redux/drawer/drawer.slice.ts +++ b/src/redux/drawer/drawer.slice.ts @@ -7,6 +7,7 @@ import { displayEntityDetailsReducer, displayGroupedSearchResultsReducer, openDrawerReducer, + openOverlaysDrawerReducer, openBioEntityDrawerByIdReducer, openReactionDrawerByIdReducer, openSearchDrawerWithSelectedTabReducer, @@ -22,6 +23,7 @@ const drawerSlice = createSlice({ openDrawer: openDrawerReducer, openSearchDrawerWithSelectedTab: openSearchDrawerWithSelectedTabReducer, openSubmapsDrawer: openSubmapsDrawerReducer, + openOverlaysDrawer: openOverlaysDrawerReducer, selectTab: selectTabReducer, closeDrawer: closeDrawerReducer, displayDrugsList: displayDrugsListReducer, @@ -38,6 +40,7 @@ export const { openDrawer, openSearchDrawerWithSelectedTab, openSubmapsDrawer, + openOverlaysDrawer, selectTab, closeDrawer, displayDrugsList, diff --git a/src/redux/overlays/overlays.mock.ts b/src/redux/overlays/overlays.mock.ts index 1a8037b6ba6e1ca5a7d096a71ae79a6f3c388fd5..cdb5593cba6857acbbcafbb9d1886f93457e33cf 100644 --- a/src/redux/overlays/overlays.mock.ts +++ b/src/redux/overlays/overlays.mock.ts @@ -1,4 +1,5 @@ import { DEFAULT_ERROR } from '@/constants/errors'; +import { MapOverlay } from '@/types/models'; import { OverlaysState } from './overlays.types'; export const OVERLAYS_INITIAL_STATE_MOCK: OverlaysState = { @@ -6,3 +7,74 @@ export const OVERLAYS_INITIAL_STATE_MOCK: OverlaysState = { loading: 'idle', error: DEFAULT_ERROR, }; + +export const PUBLIC_OVERLAYS_MOCK: MapOverlay[] = [ + { + name: 'PD substantia nigra', + googleLicenseConsent: false, + creator: 'appu-admin', + description: + 'Differential transcriptome expression from post mortem tissue. Meta-analysis from 8 published datasets, FDR = 0.05, see PMIDs 23832570 and 25447234.', + genomeType: null, + genomeVersion: null, + idObject: 11, + publicOverlay: true, + type: 'GENERIC', + order: 1, + }, + { + name: 'Ageing brain', + googleLicenseConsent: false, + creator: 'appu-admin', + description: + 'Differential transcriptome expression from post mortem tissue. Source: Allen Brain Atlas datasets, see PMID 25447234.', + genomeType: null, + genomeVersion: null, + idObject: 12, + publicOverlay: true, + type: 'GENERIC', + order: 2, + }, + { + name: 'PRKN variants example', + googleLicenseConsent: false, + creator: 'appu-admin', + description: 'PRKN variants', + genomeType: 'UCSC', + genomeVersion: 'hg19', + idObject: 17, + publicOverlay: true, + type: 'GENETIC_VARIANT', + order: 3, + }, + { + name: 'PRKN variants doubled', + googleLicenseConsent: false, + creator: 'appu-admin', + description: 'PRKN variants', + genomeType: 'UCSC', + genomeVersion: 'hg19', + idObject: 18, + publicOverlay: true, + type: 'GENETIC_VARIANT', + order: 4, + }, + { + name: 'Generic advanced format overlay', + googleLicenseConsent: false, + creator: 'appu-admin', + description: 'Data set provided by a user', + genomeType: null, + genomeVersion: null, + idObject: 20, + publicOverlay: true, + type: 'GENERIC', + order: 5, + }, +]; + +export const OVERLAYS_PUBLIC_FETCHED_STATE_MOCK: OverlaysState = { + data: PUBLIC_OVERLAYS_MOCK, + loading: 'succeeded', + error: DEFAULT_ERROR, +}; diff --git a/src/types/drawerName.ts b/src/types/drawerName.ts index d34edf92b24a530f1055aa5fe92aa29d26820e8d..a5f3e3d2ff2f0155c67bdb1a458fc2ce64c5764f 100644 --- a/src/types/drawerName.ts +++ b/src/types/drawerName.ts @@ -7,4 +7,5 @@ export type DrawerName = | 'legend' | 'submaps' | 'reaction' + | 'overlays' | 'bio-entity';