Skip to content
Snippets Groups Projects
Commit f0aecede authored by Adrian Orłów's avatar Adrian Orłów :fire:
Browse files

Merge branch 'MIN-183-add-fit-bounds' into 'development'

feat: Add location button and zoom to pins business logic (MIN-184)

Closes MIN-184

See merge request !103
parents 30c9117f 4f556e59
No related branches found
No related tags found
2 merge requests!223reset the pin numbers before search results are fetch (so the results will be...,!103feat: Add location button and zoom to pins business logic (MIN-184)
Pipeline #84408 passed
Showing
with 600 additions and 71 deletions
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_4_190)">
<path d="M15 12H12V15H9V12H6V9H9V6H12V9H15V12Z" fill="#8E92A1"/>
<path d="M23.5605 19.9395L19.5 15.879C20.4798 14.2558 20.9985 12.396 21 10.5C21 4.71 16.2885 0 10.5 0C4.7115 0 0 4.71 0 10.5C0 16.29 4.7115 21 10.5 21C12.3962 20.9994 14.2562 20.4808 15.879 19.5L19.9395 23.5605C20.0785 23.7003 20.2437 23.8112 20.4257 23.8869C20.6077 23.9626 20.8029 24.0016 21 24.0016C21.1971 24.0016 21.3923 23.9626 21.5743 23.8869C21.7563 23.8112 21.9215 23.7003 22.0605 23.5605L23.5605 22.0605C23.6999 21.9213 23.8105 21.756 23.886 21.574C23.9615 21.3921 24.0003 21.197 24.0003 21C24.0003 20.803 23.9615 20.6079 23.886 20.426C23.8105 20.244 23.6999 20.0787 23.5605 19.9395V19.9395ZM10.5 18C8.51068 17.9998 6.60291 17.2094 5.19639 15.8026C3.78987 14.3957 2.9998 12.4878 3 10.4985C3.0002 8.50918 3.79064 6.60141 5.19745 5.19489C6.60425 3.78837 8.51218 2.9983 10.5015 2.9985C12.4908 2.9987 14.3986 3.78915 15.8051 5.19595C17.2116 6.60275 18.0017 8.51068 18.0015 10.5C18.0013 12.4893 17.2109 14.3971 15.8041 15.8036C14.3972 17.2101 12.4893 18.0002 10.5 18V18Z" fill="#8E92A1"/>
</g>
<defs>
<clipPath id="clip0_4_190">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_4_194)">
<path d="M6 9H15V12H6V9Z" fill="#8E92A1"/>
<path d="M23.5605 19.9395L19.5 15.879C20.4798 14.2558 20.9985 12.396 21 10.5C21 4.71 16.2885 0 10.5 0C4.7115 0 0 4.71 0 10.5C0 16.29 4.7115 21 10.5 21C12.3962 20.9994 14.2562 20.4808 15.879 19.5L19.9395 23.5605C20.0785 23.7003 20.2437 23.8112 20.4257 23.8869C20.6077 23.9626 20.8029 24.0016 21 24.0016C21.1971 24.0016 21.3923 23.9626 21.5743 23.8869C21.7563 23.8112 21.9215 23.7003 22.0605 23.5605L23.5605 22.0605C23.6999 21.9213 23.8105 21.756 23.886 21.574C23.9615 21.3921 24.0003 21.197 24.0003 21C24.0003 20.803 23.9615 20.6079 23.886 20.426C23.8105 20.244 23.6999 20.0787 23.5605 19.9395V19.9395ZM10.5 18C8.51068 17.9998 6.60291 17.2094 5.19639 15.8026C3.78987 14.3957 2.9998 12.4878 3 10.4985C3.0002 8.50918 3.79064 6.60141 5.19745 5.19489C6.60425 3.78837 8.51218 2.9983 10.5015 2.9985C12.4908 2.9987 14.3986 3.78915 15.8051 5.19595C17.2116 6.60275 18.0017 8.51068 18.0015 10.5C18.0013 12.4893 17.2109 14.3971 15.8041 15.8036C14.3972 17.2101 12.4893 18.0002 10.5 18V18Z" fill="#8E92A1"/>
</g>
<defs>
<clipPath id="clip0_4_194">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>
import { store } from '@/redux/store';
import { MapInstanceProvider } from '@/utils/context/mapInstanceContext';
import { ReactNode } from 'react';
import { Provider } from 'react-redux';
import { store } from '@/redux/store';
interface AppWrapperProps {
children: ReactNode;
}
export const AppWrapper = ({ children }: AppWrapperProps): JSX.Element => (
<Provider store={store}>{children}</Provider>
<MapInstanceProvider>
<Provider store={store}>{children}</Provider>
</MapInstanceProvider>
);
/* eslint-disable no-magic-numbers */
import { FIRST_ARRAY_ELEMENT } from '@/constants/common';
import { AppDispatch, RootState } from '@/redux/store';
import { MAP_DATA_INITIAL_STATE } from '@/redux/map/map.constants';
import { INITIAL_STORE_STATE_MOCK } from '@/redux/root/root.fixtures';
import { AppDispatch, RootState, StoreType } from '@/redux/store';
import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
import { InitialStoreState } from '@/utils/testing/getReduxWrapperWithStore';
import { render, screen } from '@testing-library/react';
import {
InitialStoreState,
getReduxWrapperWithStore,
} from '@/utils/testing/getReduxWrapperWithStore';
import { act, render, screen } from '@testing-library/react';
import Map from 'ol/Map';
import { MockStoreEnhanced } from 'redux-mock-store';
import { MapAdditionalActions } from './MapAdditionalActions.component';
import { useVisibleBioEntitiesPolygonCoordinates } from './utils/useVisibleBioEntitiesPolygonCoordinates';
const setBounds = jest.fn();
jest.mock('../../../utils/map/useSetBounds', () => ({
_esModule: true,
useSetBounds: (): jest.Mock => setBounds,
}));
jest.mock('./utils/useVisibleBioEntitiesPolygonCoordinates', () => ({
_esModule: true,
useVisibleBioEntitiesPolygonCoordinates: jest.fn(),
}));
const useVisibleBioEntitiesPolygonCoordinatesMock =
useVisibleBioEntitiesPolygonCoordinates as jest.Mock;
setBounds.mockImplementation(() => {});
const renderComponent = (
initialStore?: InitialStoreState,
......@@ -22,36 +47,55 @@ const renderComponent = (
);
};
const renderComponentWithMapInstance = (initialStore?: InitialStoreState): { store: StoreType } => {
const dummyElement = document.createElement('div');
const mapInstance = new Map({ target: dummyElement });
const { Wrapper, store } = getReduxWrapperWithStore(initialStore, {
mapInstanceContextValue: {
mapInstance,
setMapInstance: () => {},
},
});
return (
render(
<Wrapper>
<MapAdditionalActions />
</Wrapper>,
),
{
store,
}
);
};
describe('MapAdditionalActions - component', () => {
describe('when always', () => {
beforeEach(() => {
renderComponent();
renderComponent(INITIAL_STORE_STATE_MOCK);
});
it('should render zoom in button', () => {
const image = screen.getByAltText('zoom in button icon');
const button = image.closest('button');
const button = screen.getByTestId('zoom-in-button');
expect(button).toBeInTheDocument();
});
it('should render zoom out button', () => {
const image = screen.getByAltText('zoom out button icon');
const button = image.closest('button');
const button = screen.getByTestId('zoom-out-button');
expect(button).toBeInTheDocument();
});
it('should render location button', () => {
const image = screen.getByAltText('location button icon');
const button = image.closest('button');
const button = screen.getByTestId('location-button');
expect(button).toBeInTheDocument();
});
});
describe('when clicked on zoom in button', () => {
it('should dispatch varyPositionZoom action with valid delta', () => {
const { store } = renderComponent();
const image = screen.getByAltText('zoom in button icon');
const button = image.closest('button');
const { store } = renderComponent(INITIAL_STORE_STATE_MOCK);
const button = screen.getByTestId('zoom-in-button');
button!.click();
const actions = store.getActions();
......@@ -64,9 +108,8 @@ describe('MapAdditionalActions - component', () => {
describe('when clicked on zoom in button', () => {
it('should dispatch varyPositionZoom action with valid delta', () => {
const { store } = renderComponent();
const image = screen.getByAltText('zoom out button icon');
const button = image.closest('button');
const { store } = renderComponent(INITIAL_STORE_STATE_MOCK);
const button = screen.getByTestId('zoom-out-button');
button!.click();
const actions = store.getActions();
......@@ -77,7 +120,40 @@ describe('MapAdditionalActions - component', () => {
});
});
describe.skip('when clicked on location button', () => {
// TODO: implelemnt test
describe('when clicked on location button', () => {
it('setBounds should be called', () => {
useVisibleBioEntitiesPolygonCoordinatesMock.mockImplementation(() => [
[128, 128],
[192, 192],
]);
renderComponentWithMapInstance({
map: {
data: {
...MAP_DATA_INITIAL_STATE,
size: {
width: 256,
height: 256,
tileSize: 256,
minZoom: 1,
maxZoom: 1,
},
},
loading: 'idle',
error: {
name: '',
message: '',
},
openedMaps: [],
},
});
const button = screen.getByTestId('location-button');
act(() => {
button!.click();
});
expect(setBounds).toHaveBeenCalled();
});
});
});
import locationIcon from '@/assets/vectors/icons/location.svg';
import magnifierZoomInIcon from '@/assets/vectors/icons/magnifier-zoom-in.svg';
import magnifierZoomOutIcon from '@/assets/vectors/icons/magnifier-zoom-out.svg';
import Image from 'next/image';
import { Icon } from '@/shared/Icon';
import { twMerge } from 'tailwind-merge';
import { useAddtionalActions } from './utils/useAdditionalActions';
......@@ -19,24 +16,27 @@ export const MapAdditionalActions = (): JSX.Element => {
type="button"
className="flex h-12 w-12 items-center justify-center rounded-full bg-white"
onClick={zoomInToBioEntities}
data-testid="location-button"
>
<Image src={locationIcon} alt="location button icon" height={28} width={28} />
<Icon className="h-[28px] w-[28px]" name="location" />
</button>
<div className="flex h-auto w-12 flex-col items-center justify-center rounded-full bg-white py-2">
<button
type="button"
className="flex h-12 w-12 items-center justify-center"
onClick={zoomIn}
data-testid="zoom-in-button"
>
<Image src={magnifierZoomInIcon} alt="zoom in button icon" height={24} width={24} />
<Icon className="h-[24px] w-[24px]" name="magnifier-zoom-in" />
</button>
<div className="h-px w-12 bg-[#F1F1F1]" />
<button
type="button"
className="flex h-12 w-12 items-center justify-center"
onClick={zoomOut}
data-testid="zoom-out-button"
>
<Image src={magnifierZoomOutIcon} alt="zoom out button icon" height={24} width={24} />
<Icon className="h-[24px] w-[24px]" name="magnifier-zoom-out" />
</button>
</div>
</div>
......
/* eslint-disable no-magic-numbers */
import { FIRST_ARRAY_ELEMENT } from '@/constants/common';
import { MAP_DATA_INITIAL_STATE } from '@/redux/map/map.constants';
import { INITIAL_STORE_STATE_MOCK } from '@/redux/root/root.fixtures';
import { getReduxStoreWithActionsListener } from '@/utils/testing/getReduxStoreActionsListener';
import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
import { renderHook } from '@testing-library/react';
import Map from 'ol/Map';
import { useAddtionalActions } from './useAdditionalActions';
import { useVisibleBioEntitiesPolygonCoordinates } from './useVisibleBioEntitiesPolygonCoordinates';
jest.mock('./useVisibleBioEntitiesPolygonCoordinates', () => ({
_esModule: true,
useVisibleBioEntitiesPolygonCoordinates: jest.fn(),
}));
const useVisibleBioEntitiesPolygonCoordinatesMock =
useVisibleBioEntitiesPolygonCoordinates as jest.Mock;
describe('useAddtionalActions - hook', () => {
describe('on zoomIn', () => {
it('should dispatch varyPositionZoom action with valid delta', () => {
const { Wrapper, store } = getReduxStoreWithActionsListener();
const { Wrapper, store } = getReduxStoreWithActionsListener(INITIAL_STORE_STATE_MOCK);
const {
result: {
current: { zoomIn },
......@@ -27,7 +41,7 @@ describe('useAddtionalActions - hook', () => {
describe('on zoomOut', () => {
it('should dispatch varyPositionZoom action with valid delta', () => {
const { Wrapper, store } = getReduxStoreWithActionsListener();
const { Wrapper, store } = getReduxStoreWithActionsListener(INITIAL_STORE_STATE_MOCK);
const {
result: {
current: { zoomOut },
......@@ -47,6 +61,79 @@ describe('useAddtionalActions - hook', () => {
});
describe('on zoomInToBioEntities', () => {
// TODO: implelemnt test
describe('when there are valid polygon coordinates', () => {
beforeEach(() => {
useVisibleBioEntitiesPolygonCoordinatesMock.mockImplementation(() => [
[128, 128],
[192, 192],
]);
});
it('should return valid results', () => {
const dummyElement = document.createElement('div');
const mapInstance = new Map({ target: dummyElement });
const { Wrapper } = getReduxWrapperWithStore(
{
map: {
data: {
...MAP_DATA_INITIAL_STATE,
size: {
width: 256,
height: 256,
tileSize: 256,
minZoom: 1,
maxZoom: 1,
},
},
loading: 'idle',
error: {
name: '',
message: '',
},
openedMaps: [],
},
},
{
mapInstanceContextValue: {
mapInstance,
setMapInstance: () => {},
},
},
);
const {
result: {
current: { zoomInToBioEntities },
},
} = renderHook(() => useAddtionalActions(), {
wrapper: Wrapper,
});
expect(zoomInToBioEntities()).toStrictEqual({
extent: [128, 128, 192, 192],
options: { maxZoom: 1, padding: [128, 128, 128, 128], size: undefined },
// size is real size on the screen, so it'll be undefined in the jest
});
});
});
describe('when there are no polygon coordinates', () => {
beforeEach(() => {
useVisibleBioEntitiesPolygonCoordinatesMock.mockImplementation(() => undefined);
});
it('should return undefined', () => {
const { Wrapper } = getReduxStoreWithActionsListener(INITIAL_STORE_STATE_MOCK);
const {
result: {
current: { zoomInToBioEntities },
},
} = renderHook(() => useAddtionalActions(), {
wrapper: Wrapper,
});
expect(zoomInToBioEntities()).toBeUndefined();
});
});
});
});
import { varyPositionZoom } from '@/redux/map/map.slice';
import { SetBoundsResult, useSetBounds } from '@/utils/map/useSetBounds';
import { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { MAP_ZOOM_IN_DELTA, MAP_ZOOM_OUT_DELTA } from '../MappAdditionalActions.constants';
import { useVisibleBioEntitiesPolygonCoordinates } from './useVisibleBioEntitiesPolygonCoordinates';
interface UseAddtionalActionsResult {
zoomIn(): void;
......@@ -11,6 +13,16 @@ interface UseAddtionalActionsResult {
export const useAddtionalActions = (): UseAddtionalActionsResult => {
const dispatch = useDispatch();
const setBounds = useSetBounds();
const polygonCoordinates = useVisibleBioEntitiesPolygonCoordinates();
const zoomInToBioEntities = (): SetBoundsResult | undefined => {
if (!polygonCoordinates) {
return undefined;
}
return setBounds(polygonCoordinates);
};
const varyZoomByDelta = useCallback(
(delta: number) => {
......@@ -22,6 +34,6 @@ export const useAddtionalActions = (): UseAddtionalActionsResult => {
return {
zoomIn: () => varyZoomByDelta(MAP_ZOOM_IN_DELTA),
zoomOut: () => varyZoomByDelta(MAP_ZOOM_OUT_DELTA),
zoomInToBioEntities: (): void => {},
zoomInToBioEntities,
};
};
import { drugsFixture } from '@/models/fixtures/drugFixtures';
/* eslint-disable no-magic-numbers */
import { bioEntityContentFixture } from '@/models/fixtures/bioEntityContentsFixture';
import { chemicalsFixture } from '@/models/fixtures/chemicalsFixture';
import { modelsFixture } from '@/models/fixtures/modelsFixture';
import { BIOENTITY_INITIAL_STATE_MOCK } from '@/redux/bioEntity/bioEntity.mock';
import { DRAWER_INITIAL_STATE } from '@/redux/drawer/drawer.constants';
import { INITIAL_STORE_STATE_MOCK } from '@/redux/root/root.fixtures';
import { RootState } from '@/redux/store';
import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
import { renderHook } from '@testing-library/react';
import { CHEMICALS_INITIAL_STATE_MOCK } from '../../../../redux/chemicals/chemicals.mock';
import { DRUGS_INITIAL_STATE_MOCK } from '../../../../redux/drugs/drugs.mock';
import { DEFAULT_POSITION, MAIN_MAP, MAP_INITIAL_STATE } from '../../../../redux/map/map.constants';
import { MODELS_INITIAL_STATE_MOCK } from '../../../../redux/models/models.mock';
import { useVisibleBioEntitiesPolygonCoordinates } from './useVisibleBioEntitiesPolygonCoordinates';
/* key elements of the state:
- this state simulates situation where there is:
-- one searched element
-- of currently selected map
-- for each content/chemicals/drugs data set
- the key differences in this states are x/y/z coordinates of element's bioEntities
*/
const getInitalState = (
{ hideElements }: { hideElements: boolean } = { hideElements: false },
): RootState => {
const elementsLimit = hideElements ? 0 : 1;
return {
...INITIAL_STORE_STATE_MOCK,
drawer: {
...DRAWER_INITIAL_STATE,
searchDrawerState: {
...DRAWER_INITIAL_STATE.searchDrawerState,
selectedSearchElement: 'search',
},
},
models: {
...MODELS_INITIAL_STATE_MOCK,
data: [
{
...modelsFixture[0],
idObject: 5052,
},
],
},
map: {
...MAP_INITIAL_STATE,
data: {
...MAP_INITIAL_STATE.data,
modelId: 5052,
size: {
width: 256,
height: 256,
tileSize: 256,
minZoom: 1,
maxZoom: 1,
},
},
openedMaps: [{ modelId: 5052, modelName: MAIN_MAP, lastPosition: DEFAULT_POSITION }],
},
bioEntity: {
...BIOENTITY_INITIAL_STATE_MOCK,
data: [
{
searchQueryElement: 'search',
data: [
{
...bioEntityContentFixture,
bioEntity: {
...bioEntityContentFixture.bioEntity,
model: 5052,
x: 16,
y: 16,
z: 1,
},
},
].slice(0, elementsLimit),
loading: 'succeeded',
error: { message: '', name: '' },
},
],
},
chemicals: {
...CHEMICALS_INITIAL_STATE_MOCK,
data: [
{
searchQueryElement: 'search',
data: [
{
...chemicalsFixture[0],
targets: [
{
...chemicalsFixture[0].targets[0],
targetElements: [
{
...chemicalsFixture[0].targets[0].targetElements[0],
model: 5052,
x: 32,
y: 32,
z: 1,
},
],
},
],
},
].slice(0, elementsLimit),
loading: 'succeeded',
error: { message: '', name: '' },
},
{
searchQueryElement: 'not-search',
data: [
{
...chemicalsFixture[0],
targets: [
{
...chemicalsFixture[0].targets[0],
targetElements: [
{
...chemicalsFixture[0].targets[0].targetElements[0],
model: 5052,
x: 8,
y: 2,
z: 9,
},
],
},
],
},
].slice(0, elementsLimit),
loading: 'succeeded',
error: { message: '', name: '' },
},
],
},
drugs: {
...DRUGS_INITIAL_STATE_MOCK,
data: [
{
searchQueryElement: 'search',
data: [
{
...drugsFixture[0],
targets: [
{
...drugsFixture[0].targets[0],
targetElements: [
{
...drugsFixture[0].targets[0].targetElements[0],
model: 5052,
x: 128,
y: 128,
z: 1,
},
],
},
],
},
].slice(0, elementsLimit),
loading: 'succeeded',
error: { message: '', name: '' },
},
{
searchQueryElement: 'not-search',
data: [
{
...drugsFixture[0],
targets: [
{
...drugsFixture[0].targets[0],
targetElements: [
{
...drugsFixture[0].targets[0].targetElements[0],
model: 5052,
x: 100,
y: 50,
z: 4,
},
],
},
],
},
].slice(0, elementsLimit),
loading: 'succeeded',
error: { message: '', name: '' },
},
],
},
};
};
describe('useVisibleBioEntitiesPolygonCoordinates - hook', () => {
describe('when allVisibleBioEntities is empty', () => {
const { Wrapper } = getReduxWrapperWithStore(getInitalState({ hideElements: true }));
const { result } = renderHook(() => useVisibleBioEntitiesPolygonCoordinates(), {
wrapper: Wrapper,
});
it('should return undefined', () => {
expect(result.current).toBe(undefined);
});
});
describe('when allVisibleBioEntities has data', () => {
const { Wrapper } = getReduxWrapperWithStore(getInitalState());
const { result } = renderHook(() => useVisibleBioEntitiesPolygonCoordinates(), {
wrapper: Wrapper,
});
it('should return undefined', () => {
expect(result.current).toStrictEqual([
[-17532820, -0],
[0, 17532820],
]);
});
});
});
import { allVisibleBioEntitiesSelector } from '@/redux/bioEntity/bioEntity.selectors';
import { Point } from '@/types/map';
import { usePointToProjection } from '@/utils/map/usePointToProjection';
import { isPointValid } from '@/utils/point/isPointValid';
import { Coordinate } from 'ol/coordinate';
import { useMemo } from 'react';
import { useSelector } from 'react-redux';
const VALID_POLYGON_COORDINATES_LENGTH = 2;
export const useVisibleBioEntitiesPolygonCoordinates = (): Coordinate[] | undefined => {
const allVisibleBioEntities = useSelector(allVisibleBioEntitiesSelector);
const pointToProjection = usePointToProjection();
const polygonPoints = useMemo((): Point[] => {
const allX = allVisibleBioEntities.map(({ x }) => x);
const allY = allVisibleBioEntities.map(({ y }) => y);
const minX = Math.min(...allX);
const maxX = Math.max(...allX);
const minY = Math.min(...allY);
const maxY = Math.max(...allY);
const points = [
{
x: minX,
y: maxY,
},
{
x: maxX,
y: minY,
},
];
return points.filter(isPointValid);
}, [allVisibleBioEntities]);
const polygonCoordinates = useMemo(
() => polygonPoints.map(point => pointToProjection(point)),
[polygonPoints, pointToProjection],
);
if (polygonCoordinates.length !== VALID_POLYGON_COORDINATES_LENGTH) {
return undefined;
}
return polygonCoordinates;
};
import Map from 'ol/Map';
import View from 'ol/View';
import BaseLayer from 'ol/layer/Base';
export type MapInstance = Map | undefined;
export type MapConfig = {
view: View;
layers: BaseLayer[];
......
/* eslint-disable no-magic-numbers */
import { MapInstance } from '@/types/map';
import { useEffect } from 'react';
import { MapConfig, MapInstance } from '../../MapViewer.types';
import { MapConfig } from '../../MapViewer.types';
import { useOlMapOverlaysLayer } from './overlaysLayer/useOlMapOverlaysLayer';
import { useOlMapPinsLayer } from './pinsLayer/useOlMapPinsLayer';
import { useOlMapReactionsLayer } from './reactionsLayer/useOlMapReactionsLayer';
......
/* eslint-disable no-magic-numbers */
import { OPTIONS } from '@/constants/map';
import { mapDataInitialPositionSelector } from '@/redux/map/map.selectors';
import { Point } from '@/types/map';
import { MapInstance, Point } from '@/types/map';
import { usePointToProjection } from '@/utils/map/usePointToProjection';
import { View } from 'ol';
import { useEffect, useMemo } from 'react';
import { useSelector } from 'react-redux';
import { MapConfig, MapInstance } from '../../MapViewer.types';
import { MapConfig } from '../../MapViewer.types';
interface UseOlMapViewInput {
mapInstance: MapInstance;
......
......@@ -2,16 +2,16 @@ import { OPTIONS } from '@/constants/map';
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { mapDataSizeSelector } from '@/redux/map/map.selectors';
import { currentModelIdSelector } from '@/redux/models/models.selectors';
import { MapInstance } from '@/types/map';
import { View } from 'ol';
import { unByKey } from 'ol/Observable';
import { Coordinate } from 'ol/coordinate';
import { Pixel } from 'ol/pixel';
import { useEffect, useRef } from 'react';
import { useSelector } from 'react-redux';
import { useDebouncedCallback } from 'use-debounce';
import { Pixel } from 'ol/pixel';
import { Coordinate } from 'ol/coordinate';
import { MapInstance } from '../../MapViewer.types';
import { onMapSingleClick } from './mapSingleClick/onMapSingleClick';
import { onMapRightClick } from './mapRightClick/onMapRightClick';
import { onMapSingleClick } from './mapSingleClick/onMapSingleClick';
import { onMapPositionChange } from './onMapPositionChange';
interface UseOlMapListenersInput {
......
import { MapInstance } from '@/types/map';
import { useMapInstance } from '@/utils/context/mapInstanceContext';
import Map from 'ol/Map';
import React, { MutableRefObject, useEffect, useState } from 'react';
import { MapInstance } from '../MapViewer.types';
import { Zoom } from 'ol/control';
import React, { MutableRefObject, useEffect } from 'react';
import { useOlMapLayers } from './config/useOlMapLayers';
import { useOlMapView } from './config/useOlMapView';
import { useOlMapListeners } from './listeners/useOlMapListeners';
......@@ -17,7 +19,7 @@ type UseOlMap = (input?: UseOlMapInput) => UseOlMapOutput;
export const useOlMap: UseOlMap = ({ target } = {}) => {
const mapRef = React.useRef<null | HTMLDivElement>(null);
const [mapInstance, setMapInstance] = useState<MapInstance>(undefined);
const { mapInstance, setMapInstance } = useMapInstance();
const view = useOlMapView({ mapInstance });
useOlMapLayers({ mapInstance });
useOlMapListeners({ view, mapInstance });
......@@ -32,8 +34,15 @@ export const useOlMap: UseOlMap = ({ target } = {}) => {
target: target || mapRef.current,
});
// remove zoom controls as we are using our own
map.getControls().forEach(mapControl => {
if (mapControl instanceof Zoom) {
map.removeControl(mapControl);
}
});
setMapInstance(currentMap => currentMap || map);
}, [target]);
}, [target, setMapInstance]);
return {
mapRef,
......
import { z } from 'zod';
/* This schema is used only for local Point objects, it's NOT returned from backend */
export const mapPointSchema = z.object({
x: z.number().finite().nonnegative(),
y: z.number().finite().nonnegative(),
z: z.number().finite().nonnegative().optional(),
});
......@@ -3,11 +3,13 @@ import { rootSelector } from '@/redux/root/root.selectors';
import { MultiSearchData } from '@/types/fetchDataState';
import { BioEntity, BioEntityContent, MapModel } from '@/types/models';
import { createSelector } from '@reduxjs/toolkit';
import { searchedChemicalsBioEntitesOfCurrentMapSelector } from '../chemicals/chemicals.selectors';
import { currentSelectedBioEntityIdSelector } from '../contextMenu/contextMenu.selector';
import {
currentSearchedBioEntityId,
currentSelectedSearchElement,
} from '../drawer/drawer.selectors';
import { currentSelectedBioEntityIdSelector } from '../contextMenu/contextMenu.selector';
import { searchedDrugsBioEntitesOfCurrentMapSelector } from '../drugs/drugs.selectors';
import { currentModelIdSelector, modelsDataSelector } from '../models/models.selectors';
export const bioEntitySelector = createSelector(rootSelector, state => state.bioEntity);
......@@ -105,3 +107,12 @@ export const bioEntitiesPerModelSelector = createSelector(
);
},
);
export const allVisibleBioEntitiesSelector = createSelector(
searchedBioEntitesSelectorOfCurrentMap,
searchedChemicalsBioEntitesOfCurrentMapSelector,
searchedDrugsBioEntitesOfCurrentMapSelector,
(content, chemicals, drugs): BioEntity[] => {
return [content, chemicals, drugs].flat();
},
);
......@@ -15,6 +15,9 @@ import { CloseIcon } from '@/shared/Icon/Icons/CloseIcon';
import { Pin } from '@/shared/Icon/Icons/Pin';
import type { IconTypes } from '@/types/iconTypes';
import { LocationIcon } from './Icons/LocationIcon';
import { MaginfierZoomInIcon } from './Icons/MagnifierZoomIn';
import { MaginfierZoomOutIcon } from './Icons/MagnifierZoomOut';
export interface IconProps {
className?: string;
......@@ -37,6 +40,9 @@ const icons = {
page: PageIcon,
plugin: PluginIcon,
close: CloseIcon,
location: LocationIcon,
'magnifier-zoom-in': MaginfierZoomInIcon,
'magnifier-zoom-out': MaginfierZoomOutIcon,
} as const;
export const Icon = ({ name, className = '', ...rest }: IconProps): JSX.Element => {
......
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.9998 9.33073C11.4215 9.33073 9.33317 11.4191 9.33317 13.9974C9.33317 16.5757 11.4215 18.6641 13.9998 18.6641C16.5782 18.6641 18.6665 16.5757 18.6665 13.9974C18.6665 11.4191 16.5782 9.33073 13.9998 9.33073ZM24.4298 12.8307C24.1656 10.4649 23.1047 8.25919 21.4214 6.57587C19.738 4.89255 17.5324 3.83166 15.1665 3.5674V1.16406H12.8332V3.5674C10.4673 3.83166 8.26163 4.89255 6.57831 6.57587C4.895 8.25919 3.8341 10.4649 3.56984 12.8307H1.1665V15.1641H3.56984C3.8341 17.5299 4.895 19.7356 6.57831 21.4189C8.26163 23.1022 10.4673 24.1631 12.8332 24.4274V26.8307H15.1665V24.4274C17.5324 24.1631 19.738 23.1022 21.4214 21.4189C23.1047 19.7356 24.1656 17.5299 24.4298 15.1641H26.8332V12.8307H24.4298V12.8307ZM13.9998 22.1641C9.48484 22.1641 5.83317 18.5124 5.83317 13.9974C5.83317 9.4824 9.48484 5.83073 13.9998 5.83073C18.5148 5.83073 22.1665 9.4824 22.1665 13.9974C22.1665 18.5124 18.5148 22.1641 13.9998 22.1641Z" fill="#8E92A1"/>
</svg>
interface LocationIconProps {
className?: string;
}
export const LocationIcon = ({ className }: LocationIconProps): JSX.Element => (
<svg
width="28"
height="28"
viewBox="0 0 28 28"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
d="M13.9998 9.33073C11.4215 9.33073 9.33317 11.4191 9.33317 13.9974C9.33317 16.5757 11.4215 18.6641 13.9998 18.6641C16.5782 18.6641 18.6665 16.5757 18.6665 13.9974C18.6665 11.4191 16.5782 9.33073 13.9998 9.33073ZM24.4298 12.8307C24.1656 10.4649 23.1047 8.25919 21.4214 6.57587C19.738 4.89255 17.5324 3.83166 15.1665 3.5674V1.16406H12.8332V3.5674C10.4673 3.83166 8.26163 4.89255 6.57831 6.57587C4.895 8.25919 3.8341 10.4649 3.56984 12.8307H1.1665V15.1641H3.56984C3.8341 17.5299 4.895 19.7356 6.57831 21.4189C8.26163 23.1022 10.4673 24.1631 12.8332 24.4274V26.8307H15.1665V24.4274C17.5324 24.1631 19.738 23.1022 21.4214 21.4189C23.1047 19.7356 24.1656 17.5299 24.4298 15.1641H26.8332V12.8307H24.4298V12.8307ZM13.9998 22.1641C9.48484 22.1641 5.83317 18.5124 5.83317 13.9974C5.83317 9.4824 9.48484 5.83073 13.9998 5.83073C18.5148 5.83073 22.1665 9.4824 22.1665 13.9974C22.1665 18.5124 18.5148 22.1641 13.9998 22.1641Z"
fill="#8E92A1"
/>
</svg>
);
interface MaginfierZoomInProps {
className?: string;
}
export const MaginfierZoomInIcon = ({ className }: MaginfierZoomInProps): JSX.Element => (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g clipPath="url(#clip0_4_190)">
<path d="M15 12H12V15H9V12H6V9H9V6H12V9H15V12Z" fill="#8E92A1" />
<path
d="M23.5605 19.9395L19.5 15.879C20.4798 14.2558 20.9985 12.396 21 10.5C21 4.71 16.2885 0 10.5 0C4.7115 0 0 4.71 0 10.5C0 16.29 4.7115 21 10.5 21C12.3962 20.9994 14.2562 20.4808 15.879 19.5L19.9395 23.5605C20.0785 23.7003 20.2437 23.8112 20.4257 23.8869C20.6077 23.9626 20.8029 24.0016 21 24.0016C21.1971 24.0016 21.3923 23.9626 21.5743 23.8869C21.7563 23.8112 21.9215 23.7003 22.0605 23.5605L23.5605 22.0605C23.6999 21.9213 23.8105 21.756 23.886 21.574C23.9615 21.3921 24.0003 21.197 24.0003 21C24.0003 20.803 23.9615 20.6079 23.886 20.426C23.8105 20.244 23.6999 20.0787 23.5605 19.9395V19.9395ZM10.5 18C8.51068 17.9998 6.60291 17.2094 5.19639 15.8026C3.78987 14.3957 2.9998 12.4878 3 10.4985C3.0002 8.50918 3.79064 6.60141 5.19745 5.19489C6.60425 3.78837 8.51218 2.9983 10.5015 2.9985C12.4908 2.9987 14.3986 3.78915 15.8051 5.19595C17.2116 6.60275 18.0017 8.51068 18.0015 10.5C18.0013 12.4893 17.2109 14.3971 15.8041 15.8036C14.3972 17.2101 12.4893 18.0002 10.5 18V18Z"
fill="#8E92A1"
/>
</g>
<defs>
<clipPath id="clip0_4_190">
<rect width="24" height="24" fill="white" />
</clipPath>
</defs>
</svg>
);
interface MaginfierZoomOutProps {
className?: string;
}
export const MaginfierZoomOutIcon = ({ className }: MaginfierZoomOutProps): JSX.Element => (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<g clipPath="url(#clip0_4_194)">
<path d="M6 9H15V12H6V9Z" fill="#8E92A1" />
<path
d="M23.5605 19.9395L19.5 15.879C20.4798 14.2558 20.9985 12.396 21 10.5C21 4.71 16.2885 0 10.5 0C4.7115 0 0 4.71 0 10.5C0 16.29 4.7115 21 10.5 21C12.3962 20.9994 14.2562 20.4808 15.879 19.5L19.9395 23.5605C20.0785 23.7003 20.2437 23.8112 20.4257 23.8869C20.6077 23.9626 20.8029 24.0016 21 24.0016C21.1971 24.0016 21.3923 23.9626 21.5743 23.8869C21.7563 23.8112 21.9215 23.7003 22.0605 23.5605L23.5605 22.0605C23.6999 21.9213 23.8105 21.756 23.886 21.574C23.9615 21.3921 24.0003 21.197 24.0003 21C24.0003 20.803 23.9615 20.6079 23.886 20.426C23.8105 20.244 23.6999 20.0787 23.5605 19.9395V19.9395ZM10.5 18C8.51068 17.9998 6.60291 17.2094 5.19639 15.8026C3.78987 14.3957 2.9998 12.4878 3 10.4985C3.0002 8.50918 3.79064 6.60141 5.19745 5.19489C6.60425 3.78837 8.51218 2.9983 10.5015 2.9985C12.4908 2.9987 14.3986 3.78915 15.8051 5.19595C17.2116 6.60275 18.0017 8.51068 18.0015 10.5C18.0013 12.4893 17.2109 14.3971 15.8041 15.8036C14.3972 17.2101 12.4893 18.0002 10.5 18V18Z"
fill="#8E92A1"
/>
</g>
<defs>
<clipPath id="clip0_4_194">
<rect width="24" height="24" fill="white" />
</clipPath>
</defs>
</svg>
);
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment