Skip to content
Snippets Groups Projects
Commit 9a0e3fc9 authored by Miłosz Grocholewski's avatar Miłosz Grocholewski
Browse files

Merge branch 'feat/MIN-69-search-by-coordinates' into 'development'

feat(vector-map): add searching by coordinates

Closes MIN-69

See merge request !300
parents 1f02a48f 9484fc3a
No related branches found
No related tags found
1 merge request!300feat(vector-map): add searching by coordinates
Pipeline #97742 passed
Showing
with 529 additions and 138 deletions
...@@ -29,7 +29,6 @@ export const BioEntityDrawer = (): React.ReactNode => { ...@@ -29,7 +29,6 @@ export const BioEntityDrawer = (): React.ReactNode => {
const commentsData = useAppSelector(currentDrawerElementCommentsSelector); const commentsData = useAppSelector(currentDrawerElementCommentsSelector);
const relatedSubmap = useAppSelector(currentDrawerBioEntityRelatedSubmapSelector); const relatedSubmap = useAppSelector(currentDrawerBioEntityRelatedSubmapSelector);
const currentTargetId = bioEntityData?.id ? `${TARGET_PREFIX}:${bioEntityData.id}` : ''; const currentTargetId = bioEntityData?.id ? `${TARGET_PREFIX}:${bioEntityData.id}` : '';
const fetchChemicalsForTarget = (): void => { const fetchChemicalsForTarget = (): void => {
dispatch(getChemicalsForBioEntityDrawerTarget(currentTargetId)); dispatch(getChemicalsForBioEntityDrawerTarget(currentTargetId));
}; };
......
/* eslint-disable no-magic-numbers */
import { leftClickHandleAlias } from '@/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleAlias';
import { openBioEntityDrawerById, selectTab } from '@/redux/drawer/drawer.slice';
import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds';
import { Feature } from 'ol';
jest.mock('../../../../../../services/pluginsManager/map/triggerSearch/searchFitBounds');
const eventBusDispatchEventSpy = jest.spyOn(PluginsEventBus, 'dispatchEvent');
describe('leftClickHandleAlias', () => {
let dispatch: jest.Mock;
const modelId = 1;
const hasFitBounds = true;
const feature = new Feature({ type: 'ALIAS', id: 1 });
beforeEach(() => {
jest.clearAllMocks();
dispatch = jest.fn();
});
it('dispatches getMultiBioEntityByIds, selectTab, and openBioEntityDrawerById, then triggers PluginsEventBus and searchFitBounds when hasFitBounds is true', async () => {
const mockBioEntities = [{ id: 1, name: 'BioEntity 1' }];
dispatch = jest.fn(() => ({
unwrap: jest.fn().mockResolvedValue(mockBioEntities),
}));
await leftClickHandleAlias(dispatch, hasFitBounds)(feature, modelId);
expect(dispatch).toHaveBeenCalledTimes(3);
expect(dispatch).toHaveBeenCalledWith(selectTab('1'));
expect(dispatch).toHaveBeenCalledWith(openBioEntityDrawerById(1));
expect(eventBusDispatchEventSpy).toHaveBeenCalledWith('onSearch', {
type: 'bioEntity',
searchValues: [{ id: 1, modelId, type: 'ALIAS' }],
results: [
mockBioEntities.map(bioEntity => ({
perfect: true,
bioEntity,
})),
],
});
expect(searchFitBounds).toHaveBeenCalled();
});
it('does not call searchFitBounds when hasFitBounds is false', async () => {
const mockBioEntities = [{ id: 1, name: 'BioEntity 1' }];
dispatch.mockImplementation(() => ({
unwrap: jest.fn().mockResolvedValue(mockBioEntities),
}));
await leftClickHandleAlias(dispatch, false)(feature, modelId);
expect(searchFitBounds).not.toHaveBeenCalled();
});
});
import { openBioEntityDrawerById, selectTab } from '@/redux/drawer/drawer.slice';
import { AppDispatch } from '@/redux/store';
import { getMultiBioEntityByIds } from '@/redux/bioEntity/thunks/getMultiBioEntity';
import { FeatureLike } from 'ol/Feature';
import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus';
import { searchFitBounds } from '@/services/pluginsManager/map/triggerSearch/searchFitBounds';
/* prettier-ignore */
export const leftClickHandleAlias =
(dispatch: AppDispatch, hasFitBounds = false) =>
async (feature: FeatureLike, modelId: number): Promise<void> => {
const id = feature.get('id');
const bioEntities = await dispatch(
getMultiBioEntityByIds({
elementsToFetch: [{ elementId: id, type: 'ALIAS', modelId, addNumbersToEntityNumber: true }],
}),
).unwrap();
dispatch(selectTab(`${id}`));
dispatch(openBioEntityDrawerById(id));
PluginsEventBus.dispatchEvent('onSearch', {
type: 'bioEntity',
searchValues: [{ id, modelId, type: 'ALIAS' }],
results: [
bioEntities.map(bioEntity => {
return { perfect: true, bioEntity };
}),
],
});
if (hasFitBounds) {
searchFitBounds();
}
};
/* eslint-disable no-magic-numbers */
import { leftClickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleReaction';
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';
const mockedAxiosClient = mockNetworkNewAPIResponse();
jest.mock('../../../../../../services/pluginsManager/map/triggerSearch/searchFitBounds');
const eventBusDispatchEventSpy = jest.spyOn(PluginsEventBus, 'dispatchEvent');
describe('leftClickHandleReaction', () => {
let dispatch: jest.Mock;
let modelId = 1;
let reactionId = 1;
const hasFitBounds = true;
const feature = new Feature({ type: 'REACTION', id: 1 });
beforeEach(() => {
jest.clearAllMocks();
dispatch = jest.fn();
});
it('dispatches getMultiBioEntityByIds, selectTab, and openBioEntityDrawerById, then triggers PluginsEventBus and searchFitBounds when hasFitBounds is true', async () => {
dispatch = jest.fn(() => {
return {
unwrap: jest.fn().mockResolvedValue([]),
payload: {
data: [reactionsFixture[0]],
},
};
});
reactionId = reactionsFixture[0].id;
modelId = reactionsFixture[0].modelId;
mockedAxiosClient
.onGet(apiPath.getReactionByIdInNewApi(reactionId, modelId))
.reply(HttpStatusCode.Ok, bioEntityFixture);
[
...reactionsFixture[0].products,
...reactionsFixture[0].reactants,
...reactionsFixture[0].modifiers,
].forEach(element => {
mockedAxiosClient
.onGet(
apiPath.getElementById('aliasId' in element ? element.aliasId : element.element, modelId),
)
.reply(HttpStatusCode.Ok, bioEntityFixture);
});
await leftClickHandleReaction(dispatch, hasFitBounds)(feature, modelId);
expect(dispatch).toHaveBeenCalledTimes(4);
expect(dispatch).toHaveBeenCalledWith(openReactionDrawerById(reactionId));
expect(dispatch).toHaveBeenCalledWith(selectTab(''));
expect(eventBusDispatchEventSpy).toHaveBeenCalled();
expect(searchFitBounds).toHaveBeenCalled();
});
it('does not call searchFitBounds when hasFitBounds is false', async () => {
const mockBioEntities = [{ id: 1, name: 'BioEntity 1' }];
dispatch.mockImplementation(() => ({
unwrap: jest.fn().mockResolvedValue(mockBioEntities),
}));
await leftClickHandleReaction(dispatch, false)(feature, modelId);
expect(searchFitBounds).not.toHaveBeenCalled();
});
});
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';
/* prettier-ignore */
export const leftClickHandleReaction =
(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) {
return;
}
const reaction = payload.data[FIRST_ARRAY_ELEMENT];
const bioEntitiesIds = getBioEntitiesIdsFromReaction(reaction);
dispatch(openReactionDrawerById(reaction.id));
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: '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: 'REACTION' }],
results: [result]
});
if (hasFitBounds) {
searchFitBounds();
}
}
}
};
/* eslint-disable no-magic-numbers */
import { updateLastClick } from '@/redux/map/map.slice';
import { closeDrawer } from '@/redux/drawer/drawer.slice';
import { resetReactionsData } from '@/redux/reactions/reactions.slice';
import { clearBioEntitiesData } from '@/redux/bioEntity/bioEntity.slice';
import { handleFeaturesClick } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick';
import Map from 'ol/Map';
import { onMapLeftClick } from '@/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/onMapLeftClick';
import { Comment } from '@/types/models';
import { Layer } from 'ol/layer';
import SimpleGeometry from 'ol/geom/SimpleGeometry';
import { Feature } from 'ol';
import * as leftClickHandleAlias from './leftClickHandleAlias';
import * as leftClickHandleReaction from './leftClickHandleReaction';
jest.mock('../../../utils/listeners/mapSingleClick/handleFeaturesClick', () => ({
handleFeaturesClick: jest.fn(),
}));
jest.mock('./leftClickHandleAlias', () => ({
__esModule: true,
...jest.requireActual('./leftClickHandleAlias'),
}));
const leftClickHandleAliasSpy = jest.spyOn(leftClickHandleAlias, 'leftClickHandleAlias');
jest.mock('./leftClickHandleReaction', () => ({
__esModule: true,
...jest.requireActual('./leftClickHandleReaction'),
}));
const leftClickHandleReactionSpy = jest.spyOn(leftClickHandleReaction, 'leftClickHandleReaction');
describe('onMapLeftClick', () => {
const modelId = 1;
const isResultDrawerOpen = true;
const comments: Array<Comment> = [];
let mapInstance: Map;
const event = { coordinate: [100, 50], pixel: [200, 100] };
const mapSize = {
width: 90,
height: 90,
tileSize: 256,
minZoom: 2,
maxZoom: 9,
};
beforeEach(() => {
const dummyElement = document.createElement('div');
mapInstance = new Map({ target: dummyElement });
jest.clearAllMocks();
});
it('dispatches updateLastClick and resets data if no feature at pixel', async () => {
const dispatch = jest.fn();
jest.spyOn(mapInstance, 'forEachFeatureAtPixel').mockImplementation((_, callback) => {
callback(new Feature({}), null as unknown as Layer, null as unknown as SimpleGeometry);
});
await onMapLeftClick(
mapSize,
modelId,
dispatch,
isResultDrawerOpen,
comments,
)(event, mapInstance);
expect(dispatch).toHaveBeenCalledWith(updateLastClick(expect.any(Object)));
expect(dispatch).toHaveBeenCalledWith(closeDrawer());
expect(dispatch).toHaveBeenCalledWith(resetReactionsData());
expect(dispatch).toHaveBeenCalledWith(clearBioEntitiesData());
});
it('calls leftClickHandleAlias if feature type is ALIAS', async () => {
const mockBioEntities = [{ id: 1, name: 'BioEntity 1' }];
const dispatch = jest.fn(() => ({
unwrap: jest.fn().mockResolvedValue(mockBioEntities),
}));
const feature = new Feature({ id: 1, type: 'ALIAS' });
jest.spyOn(mapInstance, 'forEachFeatureAtPixel').mockImplementation((_, callback) => {
callback(feature, null as unknown as Layer, null as unknown as SimpleGeometry);
});
(handleFeaturesClick as jest.Mock).mockReturnValue({ shouldBlockCoordSearch: false });
await onMapLeftClick(
mapSize,
modelId,
dispatch,
isResultDrawerOpen,
comments,
)(event, mapInstance);
expect(leftClickHandleAliasSpy).toHaveBeenCalledWith(dispatch);
});
it('calls leftClickHandleReaction if feature type is REACTION', async () => {
const mockBioEntities = [{ id: 1, name: 'BioEntity 1' }];
const dispatch = jest.fn(() => ({
unwrap: jest.fn().mockResolvedValue(mockBioEntities),
}));
const feature = new Feature({ id: 1, type: 'REACTION' });
jest.spyOn(mapInstance, 'forEachFeatureAtPixel').mockImplementation((_, callback) => {
callback(feature, null as unknown as Layer, null as unknown as SimpleGeometry);
});
(handleFeaturesClick as jest.Mock).mockReturnValue({ shouldBlockCoordSearch: false });
await onMapLeftClick(
mapSize,
modelId,
dispatch,
isResultDrawerOpen,
comments,
)(event, mapInstance);
expect(leftClickHandleReactionSpy).toHaveBeenCalledWith(dispatch);
});
});
import { MapSize } from '@/redux/map/map.types';
import { AppDispatch } from '@/redux/store';
import { Map, MapBrowserEvent } from 'ol';
import { Comment } from '@/types/models';
import { updateLastClick } from '@/redux/map/map.slice';
import { toLonLat } from 'ol/proj';
import { latLngToPoint } from '@/utils/map/latLngToPoint';
import { FeatureLike } from 'ol/Feature';
import { closeDrawer } from '@/redux/drawer/drawer.slice';
import { clearBioEntitiesData } from '@/redux/bioEntity/bioEntity.slice';
import { leftClickHandleAlias } from '@/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleAlias';
import { handleFeaturesClick } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleFeaturesClick';
import { resetReactionsData } from '@/redux/reactions/reactions.slice';
import { leftClickHandleReaction } from '@/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/leftClickHandleReaction';
import { handleDataReset } from '@/components/Map/MapViewer/utils/listeners/mapSingleClick/handleDataReset';
/* prettier-ignore */
export const onMapLeftClick =
(mapSize: MapSize, modelId: number, dispatch: AppDispatch, isResultDrawerOpen: boolean, comments: Comment[]) =>
async ({ coordinate, pixel }: Pick<MapBrowserEvent<UIEvent>, 'coordinate' | 'pixel'>, mapInstance: Map): Promise<void> => {
const [lng, lat] = toLonLat(coordinate);
const point = latLngToPoint([lat, lng], mapSize);
dispatch(updateLastClick({ coordinates: point, modelId }));
let featureAtPixel: FeatureLike | undefined;
mapInstance.forEachFeatureAtPixel(pixel, feature => {
if(feature.get('id') && ['ALIAS', 'REACTION'].includes(feature.get('type'))) {
featureAtPixel = feature;
return true;
}
return false;
}, {hitTolerance: 20});
if(!featureAtPixel) {
if (isResultDrawerOpen) {
dispatch(closeDrawer());
}
dispatch(resetReactionsData());
dispatch(clearBioEntitiesData());
return;
}
const { shouldBlockCoordSearch } = handleFeaturesClick([featureAtPixel], dispatch, comments);
if (shouldBlockCoordSearch) {
return;
}
dispatch(handleDataReset);
const type = featureAtPixel.get('type');
if(type === 'ALIAS') {
await leftClickHandleAlias(dispatch)(featureAtPixel, modelId);
} else if (type === 'REACTION') {
await leftClickHandleReaction(dispatch)(featureAtPixel, modelId);
}
};
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { renderHook } from '@testing-library/react';
import { View } from 'ol';
import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
import { initialMapDataFixture, openedMapsThreeSubmapsFixture } from '@/redux/map/map.fixtures';
import { useOlMapVectorListeners } from '@/components/Map/MapViewer/MapViewerVector/listeners/useOlMapVectorListeners';
import { onMapLeftClick } from '@/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/onMapLeftClick';
jest.mock('./mapLeftClick/onMapLeftClick', () => ({
__esModule: true,
onMapLeftClick: jest.fn(),
}));
jest.mock('use-debounce', () => {
return {
useDebounce: () => {},
useDebouncedCallback: () => {},
};
});
describe('useOlMapVectorListeners - util', () => {
const { Wrapper } = getReduxWrapperWithStore({
map: {
data: { ...initialMapDataFixture },
loading: 'succeeded',
error: { message: '', name: '' },
openedMaps: openedMapsThreeSubmapsFixture,
},
});
beforeEach(() => {
jest.clearAllMocks();
});
describe('on left click event', () => {
it('should run onMapLeftClick event', () => {
const CALLED_ONCE = 1;
const view = new View();
renderHook(() => useOlMapVectorListeners({ mapInstance: undefined }), {
wrapper: Wrapper,
});
view.dispatchEvent('singleclick');
expect(onMapLeftClick).toBeCalledTimes(CALLED_ONCE);
});
});
});
import { OPTIONS } from '@/constants/map';
import { resultDrawerOpen } from '@/redux/drawer/drawer.selectors';
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { mapDataSizeSelector } from '@/redux/map/map.selectors';
import { currentModelIdSelector, vectorRenderingSelector } from '@/redux/models/models.selectors';
import { MapInstance } from '@/types/map';
import { unByKey } from 'ol/Observable';
import { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { useDebouncedCallback } from 'use-debounce';
import { allCommentsSelectorOfCurrentMap } from '@/redux/comment/comment.selectors';
import { onMapLeftClick } from '@/components/Map/MapViewer/MapViewerVector/listeners/mapLeftClick/onMapLeftClick';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
interface UseOlMapListenersInput {
mapInstance: MapInstance;
}
export const useOlMapVectorListeners = ({ mapInstance }: UseOlMapListenersInput): void => {
const mapSize = useSelector(mapDataSizeSelector);
const modelId = useSelector(currentModelIdSelector);
const isResultDrawerOpen = useSelector(resultDrawerOpen);
const dispatch = useAppDispatch();
const comments = useSelector(allCommentsSelectorOfCurrentMap);
const vectorRendering = useAppSelector(vectorRenderingSelector);
const handleMapLeftClick = useDebouncedCallback(
onMapLeftClick(mapSize, modelId, dispatch, isResultDrawerOpen, comments),
OPTIONS.clickPersistTime,
{ leading: false },
);
useEffect(() => {
if (!mapInstance || !vectorRendering) {
return;
}
const key = mapInstance.on('singleclick', event =>
handleMapLeftClick({ coordinate: event.coordinate, pixel: event.pixel }, mapInstance),
);
// eslint-disable-next-line consistent-return
return () => unByKey(key);
}, [mapInstance, handleMapLeftClick, vectorRendering]);
};
...@@ -55,6 +55,7 @@ export const useOlMapReactionsLayer = ({ ...@@ -55,6 +55,7 @@ export const useOlMapReactionsLayer = ({
return []; return [];
} }
const reactionObject = new Reaction({ const reactionObject = new Reaction({
id: reaction.id,
line: reaction.line, line: reaction.line,
products: reaction.products, products: reaction.products,
reactants: reaction.reactants, reactants: reaction.reactants,
...@@ -99,6 +100,7 @@ export const useOlMapReactionsLayer = ({ ...@@ -99,6 +100,7 @@ export const useOlMapReactionsLayer = ({
if (element.sboTerm === 'SBO:0000290') { if (element.sboTerm === 'SBO:0000290') {
const compartmentProps = { const compartmentProps = {
id: element.id,
x: element.x, x: element.x,
y: element.y, y: element.y,
nameX: element.nameX, nameX: element.nameX,
...@@ -134,6 +136,7 @@ export const useOlMapReactionsLayer = ({ ...@@ -134,6 +136,7 @@ export const useOlMapReactionsLayer = ({
if (elementShapes) { if (elementShapes) {
validElements.push( validElements.push(
new MapElement({ new MapElement({
id: element.id,
shapes: elementShapes, shapes: elementShapes,
x: element.x, x: element.x,
y: element.y, y: element.y,
......
/* eslint-disable no-magic-numbers */
import { UsePointToProjectionResult } from '@/utils/map/usePointToProjection';
import {
HorizontalAlign,
VerticalAlign,
} from '@/components/Map/MapViewer/MapViewerVector/MapViewerVector.types';
import BaseMultiPolygon from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/elements/BaseMultiPolygon';
import { Coordinate } from 'ol/coordinate';
import Polygon from 'ol/geom/Polygon';
import { Style } from 'ol/style';
import getFill from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getFill';
import { rgbToHex } from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/rgbToHex';
import getStroke from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/style/getStroke';
import { MapInstance } from '@/types/map';
import { Color } from '@/types/models';
export interface CompartmentProps {
x: number;
y: number;
width: number;
height: number;
thickness: number;
outerWidth: number;
innerWidth: number;
zIndex: number;
text: string;
fontSize: number;
nameX: number;
nameY: number;
nameWidth: number;
nameHeight: number;
fontColor: Color;
nameVerticalAlign: VerticalAlign;
nameHorizontalAlign: HorizontalAlign;
fillColor: Color;
borderColor: Color;
pointToProjection: UsePointToProjectionResult;
mapInstance: MapInstance;
}
export default abstract class Compartment extends BaseMultiPolygon {
outerCoords: Array<Coordinate> = [];
innerCoords: Array<Coordinate> = [];
outerWidth: number;
innerWidth: number;
thickness: number;
constructor({
x,
y,
width,
height,
thickness,
outerWidth,
innerWidth,
zIndex,
text,
fontSize,
nameX,
nameY,
nameWidth,
nameHeight,
fontColor,
nameVerticalAlign,
nameHorizontalAlign,
fillColor,
borderColor,
pointToProjection,
mapInstance,
}: CompartmentProps) {
super({
x,
y,
width,
height,
zIndex,
text,
fontSize,
nameX,
nameY,
nameWidth,
nameHeight,
fontColor,
nameVerticalAlign,
nameHorizontalAlign,
fillColor,
borderColor,
pointToProjection,
});
this.outerWidth = outerWidth;
this.innerWidth = innerWidth;
this.thickness = thickness;
this.getCompartmentCoords();
this.createPolygons();
this.drawText();
this.drawMultiPolygonFeature(mapInstance);
}
protected abstract getCompartmentCoords(): void;
protected createPolygons(): void {
const framePolygon = new Polygon([this.outerCoords, this.innerCoords]);
this.styles.push(
new Style({
geometry: framePolygon,
fill: getFill({ color: rgbToHex({ ...this.fillColor, alpha: 128 }) }),
zIndex: this.zIndex,
}),
);
this.polygons.push(framePolygon);
const outerPolygon = new Polygon([this.outerCoords]);
this.styles.push(
new Style({
geometry: outerPolygon,
stroke: getStroke({ color: rgbToHex(this.borderColor), width: this.outerWidth }),
zIndex: this.zIndex,
}),
);
this.polygons.push(outerPolygon);
const innerPolygon = new Polygon([this.innerCoords]);
this.styles.push(
new Style({
geometry: innerPolygon,
stroke: getStroke({ color: rgbToHex(this.borderColor), width: this.innerWidth }),
fill: getFill({ color: rgbToHex({ ...this.fillColor, alpha: 9 }) }),
zIndex: this.zIndex,
}),
);
this.polygons.push(innerPolygon);
}
}
...@@ -15,6 +15,8 @@ import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shap ...@@ -15,6 +15,8 @@ import getTextCoords from '@/components/Map/MapViewer/MapViewerVector/utils/shap
import { Color } from '@/types/models'; import { Color } from '@/types/models';
export interface BaseMapElementProps { export interface BaseMapElementProps {
type: string;
id: number;
x: number; x: number;
y: number; y: number;
width: number; width: number;
...@@ -35,6 +37,10 @@ export interface BaseMapElementProps { ...@@ -35,6 +37,10 @@ export interface BaseMapElementProps {
} }
export default abstract class BaseMultiPolygon { export default abstract class BaseMultiPolygon {
type: string;
id: number;
x: number; x: number;
y: number; y: number;
...@@ -80,6 +86,8 @@ export default abstract class BaseMultiPolygon { ...@@ -80,6 +86,8 @@ export default abstract class BaseMultiPolygon {
pointToProjection: UsePointToProjectionResult; pointToProjection: UsePointToProjectionResult;
constructor({ constructor({
type,
id,
x, x,
y, y,
width, width,
...@@ -98,6 +106,8 @@ export default abstract class BaseMultiPolygon { ...@@ -98,6 +106,8 @@ export default abstract class BaseMultiPolygon {
borderColor, borderColor,
pointToProjection, pointToProjection,
}: BaseMapElementProps) { }: BaseMapElementProps) {
this.type = type;
this.id = id;
this.x = x; this.x = x;
this.y = y; this.y = y;
this.width = width; this.width = width;
...@@ -159,6 +169,8 @@ export default abstract class BaseMultiPolygon { ...@@ -159,6 +169,8 @@ export default abstract class BaseMultiPolygon {
} }
return 1; return 1;
}, },
id: this.id,
type: this.type,
}); });
this.feature.setStyle(this.getStyle.bind(this)); this.feature.setStyle(this.getStyle.bind(this));
......
...@@ -15,6 +15,7 @@ import { MapInstance } from '@/types/map'; ...@@ -15,6 +15,7 @@ import { MapInstance } from '@/types/map';
import { Color } from '@/types/models'; import { Color } from '@/types/models';
export interface CompartmentProps { export interface CompartmentProps {
id: number;
x: number; x: number;
y: number; y: number;
width: number; width: number;
...@@ -50,6 +51,7 @@ export default abstract class Compartment extends BaseMultiPolygon { ...@@ -50,6 +51,7 @@ export default abstract class Compartment extends BaseMultiPolygon {
thickness: number; thickness: number;
constructor({ constructor({
id,
x, x,
y, y,
width, width,
...@@ -73,6 +75,8 @@ export default abstract class Compartment extends BaseMultiPolygon { ...@@ -73,6 +75,8 @@ export default abstract class Compartment extends BaseMultiPolygon {
mapInstance, mapInstance,
}: CompartmentProps) { }: CompartmentProps) {
super({ super({
type: 'COMPARTMENT',
id,
x, x,
y, y,
width, width,
......
...@@ -40,6 +40,7 @@ describe('MapElement', () => { ...@@ -40,6 +40,7 @@ describe('MapElement', () => {
}), }),
}); });
props = { props = {
id: 1,
x: 0, x: 0,
y: 0, y: 0,
width: 100, width: 100,
......
...@@ -16,6 +16,7 @@ import Compartment from '@/components/Map/MapViewer/MapViewerVector/utils/shapes ...@@ -16,6 +16,7 @@ import Compartment from '@/components/Map/MapViewer/MapViewerVector/utils/shapes
import { Color } from '@/types/models'; import { Color } from '@/types/models';
export type CompartmentCircleProps = { export type CompartmentCircleProps = {
id: number;
x: number; x: number;
y: number; y: number;
width: number; width: number;
...@@ -41,6 +42,7 @@ export type CompartmentCircleProps = { ...@@ -41,6 +42,7 @@ export type CompartmentCircleProps = {
export default class CompartmentCircle extends Compartment { export default class CompartmentCircle extends Compartment {
constructor({ constructor({
id,
x, x,
y, y,
width, width,
...@@ -64,6 +66,7 @@ export default class CompartmentCircle extends Compartment { ...@@ -64,6 +66,7 @@ export default class CompartmentCircle extends Compartment {
mapInstance, mapInstance,
}: CompartmentCircleProps) { }: CompartmentCircleProps) {
super({ super({
id,
x, x,
y, y,
width, width,
......
...@@ -40,6 +40,7 @@ describe('MapElement', () => { ...@@ -40,6 +40,7 @@ describe('MapElement', () => {
}), }),
}); });
props = { props = {
id: 1,
x: 0, x: 0,
y: 0, y: 0,
width: 100, width: 100,
......
...@@ -15,6 +15,7 @@ import getStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/st ...@@ -15,6 +15,7 @@ import getStyle from '@/components/Map/MapViewer/MapViewerVector/utils/shapes/st
import { Color } from '@/types/models'; import { Color } from '@/types/models';
export type CompartmentPathwayProps = { export type CompartmentPathwayProps = {
id: number;
x: number; x: number;
y: number; y: number;
width: number; width: number;
...@@ -40,6 +41,7 @@ export default class CompartmentPathway extends BaseMultiPolygon { ...@@ -40,6 +41,7 @@ export default class CompartmentPathway extends BaseMultiPolygon {
outerWidth: number; outerWidth: number;
constructor({ constructor({
id,
x, x,
y, y,
width, width,
...@@ -61,6 +63,8 @@ export default class CompartmentPathway extends BaseMultiPolygon { ...@@ -61,6 +63,8 @@ export default class CompartmentPathway extends BaseMultiPolygon {
mapInstance, mapInstance,
}: CompartmentPathwayProps) { }: CompartmentPathwayProps) {
super({ super({
type: 'COMPARTMENT',
id,
x, x,
y, y,
width, width,
......
...@@ -38,6 +38,7 @@ describe('MapElement', () => { ...@@ -38,6 +38,7 @@ describe('MapElement', () => {
}), }),
}); });
props = { props = {
id: 1,
x: 0, x: 0,
y: 0, y: 0,
width: 100, width: 100,
......
...@@ -15,6 +15,7 @@ import Compartment from '@/components/Map/MapViewer/MapViewerVector/utils/shapes ...@@ -15,6 +15,7 @@ import Compartment from '@/components/Map/MapViewer/MapViewerVector/utils/shapes
import { Color } from '@/types/models'; import { Color } from '@/types/models';
export type CompartmentSquareProps = { export type CompartmentSquareProps = {
id: number;
x: number; x: number;
y: number; y: number;
width: number; width: number;
...@@ -40,6 +41,7 @@ export type CompartmentSquareProps = { ...@@ -40,6 +41,7 @@ export type CompartmentSquareProps = {
export default class CompartmentSquare extends Compartment { export default class CompartmentSquare extends Compartment {
constructor({ constructor({
id,
x, x,
y, y,
width, width,
...@@ -63,6 +65,7 @@ export default class CompartmentSquare extends Compartment { ...@@ -63,6 +65,7 @@ export default class CompartmentSquare extends Compartment {
mapInstance, mapInstance,
}: CompartmentSquareProps) { }: CompartmentSquareProps) {
super({ super({
id,
x, x,
y, y,
width, width,
......
...@@ -39,6 +39,7 @@ describe('MapElement', () => { ...@@ -39,6 +39,7 @@ describe('MapElement', () => {
}), }),
}); });
props = { props = {
id: 1,
shapes: shapesFixture, shapes: shapesFixture,
x: 0, x: 0,
y: 0, y: 0,
......
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