From c3e8b48eaf13562b269c101d280dba2957ba2ed7 Mon Sep 17 00:00:00 2001 From: mateusz-winiarczyk <mateusz.winiarczyk@appunite.com> Date: Wed, 20 Mar 2024 13:55:21 +0100 Subject: [PATCH] feat(plugins): data overlays (MIN-222) --- docs/plugins/errors.md | 16 ++ docs/plugins/overlays.md | 79 +++++++++ docs/plugins/project.md | 9 ++ index.d.ts | 18 +++ .../hooks/useEmptyBackground.ts | 3 - .../BackgroundsSelector.component.tsx | 2 - src/redux/map/map.reducers.ts | 4 + .../overlayBioEntity.thunk.ts | 1 - src/services/pluginsManager/errorMessages.ts | 7 + .../addDataOverlay.constants.ts | 2 + .../addDataOverlay/addDataOverlay.test.ts | 124 ++++++++++++++ .../overlays/addDataOverlay/addDataOverlay.ts | 45 ++++++ .../addDataOverlay.utils.test.ts | 39 +++++ .../addDataOverlay/addDataOverlay.utils.ts | 28 ++++ .../map/overlays/addDataOverlay/index.ts | 1 + .../map/overlays/getDataOverlays.test.ts | 70 ++++++++ .../map/overlays/getDataOverlays.ts | 13 ++ .../overlays/getVisibleDataOverlays.test.ts | 59 +++++++ .../map/overlays/getVisibleDataOverlays.ts | 9 ++ .../map/overlays/hideDataOverlay.test.ts | 102 ++++++++++++ .../map/overlays/hideDataOverlay.ts | 26 +++ .../map/overlays/removeDataOverlay.test.ts | 44 +++++ .../map/overlays/removeDataOverlay.ts | 18 +++ .../map/overlays/showDataOverlay/index.ts | 1 + .../setBackgroundtoEmptyIfAvailable.test.ts | 52 ++++++ .../setBackgroundtoEmptyIfAvailable.ts | 12 ++ .../showDataOverlay/showDataOverlay.test.ts | 151 ++++++++++++++++++ .../showDataOverlay/showDataOverlay.ts | 33 ++++ src/services/pluginsManager/pluginsManager.ts | 19 ++- .../pluginsManager/project/data/getApiUrls.ts | 11 ++ 30 files changed, 991 insertions(+), 7 deletions(-) create mode 100644 docs/plugins/overlays.md create mode 100644 src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.constants.ts create mode 100644 src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.test.ts create mode 100644 src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.ts create mode 100644 src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.utils.test.ts create mode 100644 src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.utils.ts create mode 100644 src/services/pluginsManager/map/overlays/addDataOverlay/index.ts create mode 100644 src/services/pluginsManager/map/overlays/getDataOverlays.test.ts create mode 100644 src/services/pluginsManager/map/overlays/getDataOverlays.ts create mode 100644 src/services/pluginsManager/map/overlays/getVisibleDataOverlays.test.ts create mode 100644 src/services/pluginsManager/map/overlays/getVisibleDataOverlays.ts create mode 100644 src/services/pluginsManager/map/overlays/hideDataOverlay.test.ts create mode 100644 src/services/pluginsManager/map/overlays/hideDataOverlay.ts create mode 100644 src/services/pluginsManager/map/overlays/removeDataOverlay.test.ts create mode 100644 src/services/pluginsManager/map/overlays/removeDataOverlay.ts create mode 100644 src/services/pluginsManager/map/overlays/showDataOverlay/index.ts create mode 100644 src/services/pluginsManager/map/overlays/showDataOverlay/setBackgroundtoEmptyIfAvailable.test.ts create mode 100644 src/services/pluginsManager/map/overlays/showDataOverlay/setBackgroundtoEmptyIfAvailable.ts create mode 100644 src/services/pluginsManager/map/overlays/showDataOverlay/showDataOverlay.test.ts create mode 100644 src/services/pluginsManager/map/overlays/showDataOverlay/showDataOverlay.ts create mode 100644 src/services/pluginsManager/project/data/getApiUrls.ts diff --git a/docs/plugins/errors.md b/docs/plugins/errors.md index fd7d29c7..ddbebcf5 100644 --- a/docs/plugins/errors.md +++ b/docs/plugins/errors.md @@ -18,6 +18,22 @@ - **Project does not exist**: This error occurs when the project data is not available. +- **Project ID does not exist**: This error occurs when the project ID is not available. + +## Overlay Errors + +- **Overlay name is not provided**: This error occurs when the name of the overlay is missing or not provided. + +- **Failed to read file**: This error occurs when there is an issue reading the content of a file. Check if it's text file. + +- **Invalid type of fileContent**: This error occurs when the fileContent parameter is of an invalid type. + +- **Overlay with provided id does not exist**: This error occurs when the provided overlay id does not correspond to any existing overlay. + +- **Overlay with provided id is not active**: This error occurs when the provided overlay id corresponds to an overlay that is not currently active. + +- **Overlay with provided id is already active**: This error occurs when the provided overlay id corresponds to an overlay that is already active. + ## Zoom errors - **Provided zoom value exeeds max zoom of ...**: This error occurs when `zoom` param of `setZoom` exeeds max zoom value of the selected map diff --git a/docs/plugins/overlays.md b/docs/plugins/overlays.md new file mode 100644 index 00000000..b03b1ea1 --- /dev/null +++ b/docs/plugins/overlays.md @@ -0,0 +1,79 @@ +### Overlays + +#### Get list of available data overlays + +To get list of available data overlays, plugins can use the `getDataOverlays` method defined in `window.minerva.overlays.data`. This method returns array with all overlays. + +##### Example of getDataOverlays usage: + +```javascript +window.minerva.overlays.data.getDataOverlays(); +``` + +#### Get list of visible data overlays + +To get list of visible data overlays, plugins can use the `getVisibleDataOverlays` method defined in `window.minerva.overlays.data`. This method returns array with all visible data overlays. + +##### Example of getVisibleDataOverlays usage: + +```javascript +window.minerva.overlays.data.getVisibleDataOverlays(); +``` + +#### Show an overlay + +To show an overlay, plugins can use the `showDataOverlay` method defined in `window.minerva.overlays`. This method takes following arguments: + +- overlayId - the ID of the overlay that the plugin wants to show. +- setBackgroundEmpty (optional) - whether `showDataOverlay` should set the background to empty if available when it shows overlay. Its value should be a boolean type. + +##### Example of showDataOverlay usage: + +```javascript +window.minerva.overlays.showDataOverlay(109); + +window.minerva.overlays.showDataOverlay(112, true); +``` + +#### Hide an overlay + +To hide an overlay, plugins can use the `hideDataOverlay` method defined in `window.minerva.overlays`. This method takes one argument: the ID of the overlay that the plugin wants to hide. + +##### Example of showDataOverlay usage: + +```javascript +window.minerva.overlays.hideDataOverlay(109); +``` + +#### Add an overlay + +To add an overlay, plugins can use the `addDataOverlay` method defined in `window.minerva.overlays`. This method takes one argument: the object with the following properties: + +- name (string): The name of the overlay. + +- description (optional string): A description of the overlay. + +- filename (optional string): The filename of the overlay data. + +- fileContent (string or text File): The content of the overlay data. + +- type (optional string): The type of overlay data. + +##### Example of addDataOverlay usage: + +```javascript +window.minerva.overlays.addDataOverlay({ + name: 'Plugin Test', + fileContent: 'plugin test content', +}); +``` + +#### Remove an overlay + +To remove an overlay, plugins can use the `removeDataOverlay` method defined in `window.minerva.overlays`. This method takes one argument: the ID of the overlay that the plugin wants to remove. + +##### Example of removeDataOverlay usage: + +```javascript +window.minerva.overlays.removeDataOverlay(129); +``` diff --git a/docs/plugins/project.md b/docs/plugins/project.md index a972b4a6..3a152c9a 100644 --- a/docs/plugins/project.md +++ b/docs/plugins/project.md @@ -46,3 +46,12 @@ To get organism identifier associated with the project, plugins can use the `get ```javascript window.minerva.project.data.getOrganism(); ``` + +**Get Api Urls:** +To get Api urls associated with the project, plugins can use the `getApiUrls` method defined in `window.minerva.project.data` object. + +##### Example usage of getApiUrls method: + +```javascript +window.minerva.project.data.getApiUrls(); +``` diff --git a/index.d.ts b/index.d.ts index fcac0d8b..f9baf054 100644 --- a/index.d.ts +++ b/index.d.ts @@ -17,6 +17,13 @@ import { getName } from '@/services/pluginsManager/project/data/getName'; import { getOrganism } from '@/services/pluginsManager/project/data/getOrganism'; import { getProjectId } from '@/services/pluginsManager/project/data/getProjectId'; import { getVersion } from '@/services/pluginsManager/project/data/getVersion'; +import { getDataOverlays } from '@/services/pluginsManager/map/overlays/getDataOverlays'; +import { getVisibleDataOverlays } from '@/services/pluginsManager/map/overlays/getVisibleDataOverlays'; +import { showDataOverlay } from '@/services/pluginsManager/map/overlays/showDataOverlay'; +import { hideDataOverlay } from '@/services/pluginsManager/map/overlays/hideDataOverlay'; +import { removeDataOverlay } from '@/services/pluginsManager/map/overlays/removeDataOverlay'; +import { addDataOverlay } from '@/services/pluginsManager/map/overlays/addDataOverlay'; +import { getApiUrls } from '@/services/pluginsManager/project/data/getApiUrls'; type Plugin = { pluginName: string; @@ -64,6 +71,16 @@ declare global { selectOverviewImage: typeof selectOverviewImage; showOverviewImageModal: typeof showOverviewImageModal; }; + overlays: { + data: { + getDataOverlays: typeof getDataOverlays; + getVisibleDataOverlays: typeof getVisibleDataOverlays; + }; + showDataOverlay: typeof showDataOverlay; + hideDataOverlay: typeof hideDataOverlay; + removeDataOverlay: typeof removeDataOverlay; + addDataOverlay: typeof addDataOverlay; + }; project: { data: { getProjectId: typeof getProjectId; @@ -71,6 +88,7 @@ declare global { getVersion: typeof getVersion; getDisease: typeof getDisease; getOrganism: typeof getOrganism; + getApiUrls: typeof getApiUrls; }; }; }; diff --git a/src/components/Map/Drawer/OverlaysDrawer/hooks/useEmptyBackground.ts b/src/components/Map/Drawer/OverlaysDrawer/hooks/useEmptyBackground.ts index fb2eae6c..2536fa84 100644 --- a/src/components/Map/Drawer/OverlaysDrawer/hooks/useEmptyBackground.ts +++ b/src/components/Map/Drawer/OverlaysDrawer/hooks/useEmptyBackground.ts @@ -3,7 +3,6 @@ import { emptyBackgroundIdSelector } from '@/redux/backgrounds/background.select import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; import { useAppSelector } from '@/redux/hooks/useAppSelector'; import { setMapBackground } from '@/redux/map/map.slice'; -import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus'; type UseEmptyBackgroundReturn = { setBackgroundtoEmptyIfAvailable: () => void; @@ -16,8 +15,6 @@ export const useEmptyBackground = (): UseEmptyBackgroundReturn => { const setBackgroundtoEmptyIfAvailable = useCallback(() => { if (emptyBackgroundId) { dispatch(setMapBackground(emptyBackgroundId)); - - PluginsEventBus.dispatchEvent('onBackgroundOverlayChange', emptyBackgroundId); } }, [dispatch, emptyBackgroundId]); diff --git a/src/components/Map/MapAdditionalOptions/BackgroundsSelector/BackgroundsSelector.component.tsx b/src/components/Map/MapAdditionalOptions/BackgroundsSelector/BackgroundsSelector.component.tsx index 7b31ff35..ca0dd733 100644 --- a/src/components/Map/MapAdditionalOptions/BackgroundsSelector/BackgroundsSelector.component.tsx +++ b/src/components/Map/MapAdditionalOptions/BackgroundsSelector/BackgroundsSelector.component.tsx @@ -10,7 +10,6 @@ import { Icon } from '@/shared/Icon'; import { MapBackground } from '@/types/models'; import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; import { setMapBackground } from '@/redux/map/map.slice'; -import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus'; const DEFAULT_TOGGLE_BUTTON_TEXT = 'Background'; @@ -23,7 +22,6 @@ export const BackgroundSelector = (): JSX.Element => { const onItemSelect = (background: MapBackground | undefined | null): void => { if (background) { dispatch(setMapBackground(background.id)); - PluginsEventBus.dispatchEvent('onBackgroundOverlayChange', background.id); } }; diff --git a/src/redux/map/map.reducers.ts b/src/redux/map/map.reducers.ts index b2fbff2d..d1562cad 100644 --- a/src/redux/map/map.reducers.ts +++ b/src/redux/map/map.reducers.ts @@ -144,6 +144,10 @@ export const closeMapAndSetMainMapActiveReducer = ( }; export const setMapBackgroundReducer = (state: MapState, action: SetBackgroundAction): void => { + if (action.payload !== state.data.backgroundId) { + PluginsEventBus.dispatchEvent('onBackgroundOverlayChange', action.payload); + } + state.data.backgroundId = action.payload; }; diff --git a/src/redux/overlayBioEntity/overlayBioEntity.thunk.ts b/src/redux/overlayBioEntity/overlayBioEntity.thunk.ts index 30222f30..956b6751 100644 --- a/src/redux/overlayBioEntity/overlayBioEntity.thunk.ts +++ b/src/redux/overlayBioEntity/overlayBioEntity.thunk.ts @@ -100,7 +100,6 @@ export const getInitOverlays = createAsyncThunk< const emptyBackgroundId = emptyBackgroundIdSelector(state); if (emptyBackgroundId) { dispatch(setMapBackground(emptyBackgroundId)); - PluginsEventBus.dispatchEvent('onBackgroundOverlayChange', emptyBackgroundId); } overlaysId.forEach(id => { diff --git a/src/services/pluginsManager/errorMessages.ts b/src/services/pluginsManager/errorMessages.ts index 753b06f0..a5d46150 100644 --- a/src/services/pluginsManager/errorMessages.ts +++ b/src/services/pluginsManager/errorMessages.ts @@ -3,5 +3,12 @@ export const ERROR_INVALID_QUERY_TYPE = 'Invalid query type. The query should be export const ERROR_INVALID_COORDINATES = 'Invalid coordinates type or values'; export const ERROR_INVALID_MODEL_ID_TYPE = 'Invalid model id type. The model id should be a number'; export const ERROR_PROJECT_NOT_FOUND = 'Project does not exist'; +export const ERROR_PROJECT_ID_NOT_FOUND = 'Project id does not exist'; +export const ERROR_OVERLAY_NAME_NOT_PROVIDED = 'Overlay name is not provided'; +export const ERROR_FAILED_TO_READ_FILE = 'Failed to read file'; +export const ERROR_INVALID_TYPE_FILE_CONTENT = 'Invalid type of fileContent'; +export const ERROR_OVERLAY_ID_NOT_FOUND = 'Overlay with provided id does not exist'; +export const ERROR_OVERLAY_ID_NOT_ACTIVE = 'Overlay with provided id is not active'; +export const ERROR_OVERLAY_ID_ALREADY_ACTIVE = 'Overlay with provided id is already active'; export const ERROR_INVALID_MODEL_ID_TYPE_FOR_RETRIEVAL = 'Unable to retrieve the id of the active map: the modelId is not a number'; diff --git a/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.constants.ts b/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.constants.ts new file mode 100644 index 00000000..2973a0e9 --- /dev/null +++ b/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.constants.ts @@ -0,0 +1,2 @@ +export const DEFAULT_TYPE = 'GENERIC'; +export const DEFAULT_FILE_NAME = 'unknown.txt'; diff --git a/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.test.ts b/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.test.ts new file mode 100644 index 00000000..729d53cb --- /dev/null +++ b/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.test.ts @@ -0,0 +1,124 @@ +import { + createdOverlayFileFixture, + createdOverlayFixture, + uploadedOverlayFileContentFixture, +} from '@/models/fixtures/overlaysFixture'; +import { projectFixture } from '@/models/fixtures/projectFixture'; +import { apiPath } from '@/redux/apiPath'; +import { RootState, store } from '@/redux/store'; +import { mockNetworkResponse } from '@/utils/mockNetworkResponse'; +import { HttpStatusCode } from 'axios'; +import { addOverlay } from '@/redux/overlays/overlays.thunks'; +import { + ERROR_OVERLAY_NAME_NOT_PROVIDED, + ERROR_PROJECT_ID_NOT_FOUND, +} from '@/services/pluginsManager/errorMessages'; +import { addDataOverlay } from './addDataOverlay'; +import { DEFAULT_FILE_NAME, DEFAULT_TYPE } from './addDataOverlay.constants'; + +jest.mock('../../../../../redux/store'); +jest.mock('../../../../../redux/overlays/overlays.thunks'); + +const MOCK_STATE = { + project: { + data: { + ...projectFixture, + }, + loading: 'succeeded', + error: { message: '', name: '' }, + }, +}; + +const mockedAxiosClient = mockNetworkResponse(); + +describe('addDataOverlay', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const getStateSpy = jest.spyOn(store, 'getState'); + + const overlay = { + name: 'Mock Overlay', + description: 'Mock Description', + filename: 'mockFile.txt', + fileContent: 'Mock File Content', + type: 'mockType', + }; + it('should add overlay with provided data', async () => { + mockedAxiosClient + .onPost(apiPath.createOverlayFile()) + .reply(HttpStatusCode.Ok, createdOverlayFileFixture); + + mockedAxiosClient + .onPost(apiPath.uploadOverlayFileContent(createdOverlayFileFixture.id)) + .reply(HttpStatusCode.Ok, uploadedOverlayFileContentFixture); + + mockedAxiosClient + .onPost(apiPath.createOverlay(projectFixture.projectId)) + .reply(HttpStatusCode.Ok, createdOverlayFixture); + + getStateSpy.mockImplementation(() => MOCK_STATE as RootState); + + await addDataOverlay(overlay); + + expect(addOverlay).toHaveBeenCalledWith({ + content: overlay.fileContent, + description: overlay.description, + filename: overlay.filename, + name: overlay.name, + projectId: projectFixture.projectId, + type: overlay.type, + }); + }); + + it('should throw error when project id is not found', async () => { + getStateSpy.mockImplementation( + () => + ({ + project: { + ...MOCK_STATE.project, + data: { + ...MOCK_STATE.project.data, + projectId: '', + }, + }, + }) as RootState, + ); + + await expect(() => addDataOverlay(overlay)).rejects.toThrow(ERROR_PROJECT_ID_NOT_FOUND); + }); + + it('should throw error when overlay name is not provided', async () => { + getStateSpy.mockImplementation(() => MOCK_STATE as RootState); + + const overlayWithoutName = { + ...overlay, + name: '', + }; + + await expect(addDataOverlay(overlayWithoutName)).rejects.toThrow( + ERROR_OVERLAY_NAME_NOT_PROVIDED, + ); + }); + + it('should add overlay with default values when optional parameters are not provided', async () => { + getStateSpy.mockImplementation(() => MOCK_STATE as RootState); + + const overlayWithoutDefaultValues = { + name: 'Mock Overlay', + fileContent: 'Mock File Content', + }; + + await addDataOverlay(overlayWithoutDefaultValues); + + expect(addOverlay).toHaveBeenCalledWith({ + content: overlay.fileContent, + description: '', + filename: DEFAULT_FILE_NAME, + name: overlay.name, + projectId: projectFixture.projectId, + type: DEFAULT_TYPE, + }); + }); +}); diff --git a/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.ts b/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.ts new file mode 100644 index 00000000..d52b3518 --- /dev/null +++ b/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.ts @@ -0,0 +1,45 @@ +import { addOverlay } from '@/redux/overlays/overlays.thunks'; +import { projectIdSelector } from '@/redux/project/project.selectors'; +import { store } from '@/redux/store'; +import { + ERROR_OVERLAY_NAME_NOT_PROVIDED, + ERROR_PROJECT_ID_NOT_FOUND, +} from '@/services/pluginsManager/errorMessages'; +import { DEFAULT_FILE_NAME, DEFAULT_TYPE } from './addDataOverlay.constants'; +import { getOverlayContent } from './addDataOverlay.utils'; + +type AddDataOverlayArgs = { + name: string; + description?: string; + filename?: string; + fileContent: string; + type?: string; +}; + +export const addDataOverlay = async ({ + name, + description, + filename, + fileContent, + type, +}: AddDataOverlayArgs): Promise<void> => { + const { dispatch, getState } = store; + const projectId = projectIdSelector(getState()); + + if (!projectId) throw new Error(ERROR_PROJECT_ID_NOT_FOUND); + + if (!name) throw new Error(ERROR_OVERLAY_NAME_NOT_PROVIDED); + + const content = await getOverlayContent(fileContent); + + dispatch( + addOverlay({ + content, + description: description || '', + filename: filename || DEFAULT_FILE_NAME, + name, + projectId, + type: type || DEFAULT_TYPE, + }), + ); +}; diff --git a/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.utils.test.ts b/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.utils.test.ts new file mode 100644 index 00000000..5190b8c8 --- /dev/null +++ b/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.utils.test.ts @@ -0,0 +1,39 @@ +/* eslint-disable no-magic-numbers */ +import { + ERROR_FAILED_TO_READ_FILE, + ERROR_INVALID_TYPE_FILE_CONTENT, +} from '@/services/pluginsManager/errorMessages'; +import { getOverlayContent } from './addDataOverlay.utils'; + +describe('getOverlayContent', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + it('should return string if fileContent is a string', async () => { + const content = 'This is a string content'; + const result = await getOverlayContent(content); + expect(result).toBe(content); + }); + + it('should return file content if fileContent is a File object', async () => { + const fileContent = 'File content'; + const file = new File([fileContent], 'test.txt', { type: 'text/plain' }); + const result = await getOverlayContent(file); + expect(result).toBe(fileContent); + }); + + it('should throw error if fileContent is neither a string nor a File object', async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await expect(getOverlayContent(123 as any)).rejects.toThrow(ERROR_INVALID_TYPE_FILE_CONTENT); + }); + + it('should throw error if there is an error reading the file', async () => { + const file = new File(['File content'], 'test.txt', { type: 'text/plain' }); + const error = new Error('Failed to read file'); + jest.spyOn(FileReader.prototype, 'readAsText').mockImplementation(() => { + throw error; + }); + + await expect(getOverlayContent(file)).rejects.toThrow(ERROR_FAILED_TO_READ_FILE); + }); +}); diff --git a/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.utils.ts b/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.utils.ts new file mode 100644 index 00000000..c5eb6936 --- /dev/null +++ b/src/services/pluginsManager/map/overlays/addDataOverlay/addDataOverlay.utils.ts @@ -0,0 +1,28 @@ +import { + ERROR_FAILED_TO_READ_FILE, + ERROR_INVALID_TYPE_FILE_CONTENT, +} from '@/services/pluginsManager/errorMessages'; + +const getFileContentFromFile = (file: File): Promise<string> => { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsText(file); + reader.onload = (e): void => { + if (e.target) { + resolve(e.target.result as string); + } else { + reject(new Error(ERROR_FAILED_TO_READ_FILE)); + } + }; + }); +}; + +export const getOverlayContent = async (fileContent: string | File): Promise<string> => { + if (typeof fileContent === 'string') { + return fileContent; + } + if (fileContent instanceof File) { + return getFileContentFromFile(fileContent); + } + throw new Error(ERROR_INVALID_TYPE_FILE_CONTENT); +}; diff --git a/src/services/pluginsManager/map/overlays/addDataOverlay/index.ts b/src/services/pluginsManager/map/overlays/addDataOverlay/index.ts new file mode 100644 index 00000000..48e56a9f --- /dev/null +++ b/src/services/pluginsManager/map/overlays/addDataOverlay/index.ts @@ -0,0 +1 @@ +export { addDataOverlay } from './addDataOverlay'; diff --git a/src/services/pluginsManager/map/overlays/getDataOverlays.test.ts b/src/services/pluginsManager/map/overlays/getDataOverlays.test.ts new file mode 100644 index 00000000..3464c362 --- /dev/null +++ b/src/services/pluginsManager/map/overlays/getDataOverlays.test.ts @@ -0,0 +1,70 @@ +import { + OVERLAYS_PUBLIC_FETCHED_STATE_MOCK, + PUBLIC_OVERLAYS_MOCK, + USER_OVERLAYS_MOCK, +} from '@/redux/overlays/overlays.mock'; +import { RootState, store } from '@/redux/store'; +import { getDataOverlays } from './getDataOverlays'; + +describe('getDataOverlays', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + const getStateSpy = jest.spyOn(store, 'getState'); + + it('should return combined overlays and user overlays if exist', () => { + getStateSpy.mockImplementation( + () => + ({ + overlays: { + ...OVERLAYS_PUBLIC_FETCHED_STATE_MOCK, + userOverlays: { + data: USER_OVERLAYS_MOCK, + error: { message: '', name: '' }, + loading: 'succeeded', + }, + }, + }) as RootState, + ); + + expect(getDataOverlays()).toEqual([...PUBLIC_OVERLAYS_MOCK, ...USER_OVERLAYS_MOCK]); + }); + + it('should return overlays if user overlays are not present', () => { + getStateSpy.mockImplementation( + () => + ({ + overlays: { + ...OVERLAYS_PUBLIC_FETCHED_STATE_MOCK, + userOverlays: { + data: [], + error: { message: '', name: '' }, + loading: 'succeeded', + }, + }, + }) as RootState, + ); + + expect(getDataOverlays()).toEqual(PUBLIC_OVERLAYS_MOCK); + }); + + it('should return empty array if no overlays or user overlays exist', () => { + getStateSpy.mockImplementation( + () => + ({ + overlays: { + ...OVERLAYS_PUBLIC_FETCHED_STATE_MOCK, + data: [], + userOverlays: { + data: [], + error: { message: '', name: '' }, + loading: 'succeeded', + }, + }, + }) as RootState, + ); + + expect(getDataOverlays()).toEqual([]); + }); +}); diff --git a/src/services/pluginsManager/map/overlays/getDataOverlays.ts b/src/services/pluginsManager/map/overlays/getDataOverlays.ts new file mode 100644 index 00000000..7eb9c206 --- /dev/null +++ b/src/services/pluginsManager/map/overlays/getDataOverlays.ts @@ -0,0 +1,13 @@ +import { + overlaysDataSelector, + userOverlaysDataSelector, +} from '@/redux/overlays/overlays.selectors'; +import { store } from '@/redux/store'; +import { MapOverlay } from '@/types/models'; + +export const getDataOverlays = (): MapOverlay[] => { + const overlays = overlaysDataSelector(store.getState()); + const userOverlays = userOverlaysDataSelector(store.getState()) || []; + + return [...overlays, ...userOverlays]; +}; diff --git a/src/services/pluginsManager/map/overlays/getVisibleDataOverlays.test.ts b/src/services/pluginsManager/map/overlays/getVisibleDataOverlays.test.ts new file mode 100644 index 00000000..036a668b --- /dev/null +++ b/src/services/pluginsManager/map/overlays/getVisibleDataOverlays.test.ts @@ -0,0 +1,59 @@ +import { DEFAULT_ERROR } from '@/constants/errors'; +import { overlaysFixture } from '@/models/fixtures/overlaysFixture'; +import { OVERLAYS_INITIAL_STATE_MOCK } from '@/redux/overlays/overlays.mock'; +import { RootState, store } from '@/redux/store'; +import { OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK } from '@/redux/overlayBioEntity/overlayBioEntity.mock'; +import { getVisibleDataOverlays } from './getVisibleDataOverlays'; + +const ACTIVE_OVERLAYS_IDS = overlaysFixture.map(overlay => overlay.idObject); + +describe('getVisibleDataOverlays', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + const getStateSpy = jest.spyOn(store, 'getState'); + + it('should return active overlays', () => { + getStateSpy.mockImplementation( + () => + ({ + overlays: { + ...OVERLAYS_INITIAL_STATE_MOCK, + userOverlays: { + data: overlaysFixture, + loading: 'idle', + error: DEFAULT_ERROR, + }, + }, + + overlayBioEntity: { + ...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK, + overlaysId: ACTIVE_OVERLAYS_IDS, + }, + }) as RootState, + ); + + expect(getVisibleDataOverlays()).toEqual(overlaysFixture); + }); + + it('should return empty array if no active overlays', () => { + getStateSpy.mockImplementation( + () => + ({ + overlays: { + ...OVERLAYS_INITIAL_STATE_MOCK, + userOverlays: { + data: [], + loading: 'idle', + error: DEFAULT_ERROR, + }, + }, + + overlayBioEntity: OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK, + }) as RootState, + ); + + expect(getVisibleDataOverlays()).toEqual([]); + }); +}); diff --git a/src/services/pluginsManager/map/overlays/getVisibleDataOverlays.ts b/src/services/pluginsManager/map/overlays/getVisibleDataOverlays.ts new file mode 100644 index 00000000..6224d3ff --- /dev/null +++ b/src/services/pluginsManager/map/overlays/getVisibleDataOverlays.ts @@ -0,0 +1,9 @@ +import { activeOverlaysSelector } from '@/redux/overlayBioEntity/overlayBioEntity.selector'; +import { store } from '@/redux/store'; +import { MapOverlay } from '@/types/models'; + +export const getVisibleDataOverlays = (): MapOverlay[] => { + const activeOverlays = activeOverlaysSelector(store.getState()); + + return activeOverlays; +}; diff --git a/src/services/pluginsManager/map/overlays/hideDataOverlay.test.ts b/src/services/pluginsManager/map/overlays/hideDataOverlay.test.ts new file mode 100644 index 00000000..3f9a88e4 --- /dev/null +++ b/src/services/pluginsManager/map/overlays/hideDataOverlay.test.ts @@ -0,0 +1,102 @@ +/* eslint-disable no-magic-numbers */ +import { RootState, store } from '@/redux/store'; +import { overlaysFixture } from '@/models/fixtures/overlaysFixture'; +import { DEFAULT_ERROR } from '@/constants/errors'; +import { OVERLAYS_INITIAL_STATE_MOCK } from '@/redux/overlays/overlays.mock'; +import { OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK } from '@/redux/overlayBioEntity/overlayBioEntity.mock'; +import { hideDataOverlay } from './hideDataOverlay'; +import { PluginsEventBus } from '../../pluginsEventBus'; +import { ERROR_OVERLAY_ID_NOT_ACTIVE, ERROR_OVERLAY_ID_NOT_FOUND } from '../../errorMessages'; + +const OVERLAY_ID = overlaysFixture[0].idObject; + +describe('hideDataOverlay', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + const getStateSpy = jest.spyOn(store, 'getState'); + it('should throw error if overlay is not active', () => { + getStateSpy.mockImplementation( + () => + ({ + overlays: { + ...OVERLAYS_INITIAL_STATE_MOCK, + userOverlays: { + data: overlaysFixture, + loading: 'idle', + error: DEFAULT_ERROR, + }, + }, + overlayBioEntity: { ...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK, overlaysId: [123] }, + }) as RootState, + ); + expect(() => hideDataOverlay(OVERLAY_ID)).toThrow(ERROR_OVERLAY_ID_NOT_ACTIVE); + }); + + it('should throw error if matching overlay is not found', () => { + getStateSpy.mockImplementation( + () => + ({ + overlays: { + ...OVERLAYS_INITIAL_STATE_MOCK, + userOverlays: { + data: overlaysFixture, + loading: 'idle', + error: DEFAULT_ERROR, + }, + }, + overlayBioEntity: { ...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK, overlaysId: [OVERLAY_ID] }, + }) as RootState, + ); + + expect(() => hideDataOverlay(431)).toThrow(ERROR_OVERLAY_ID_NOT_FOUND); + }); + + it('should hide overlay with provided id', () => { + const dispatchSpy = jest.spyOn(store, 'dispatch'); + + getStateSpy.mockImplementation( + () => + ({ + overlays: { + ...OVERLAYS_INITIAL_STATE_MOCK, + userOverlays: { + data: overlaysFixture, + loading: 'idle', + error: DEFAULT_ERROR, + }, + }, + overlayBioEntity: { ...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK, overlaysId: [OVERLAY_ID] }, + }) as RootState, + ); + + hideDataOverlay(OVERLAY_ID); + + expect(dispatchSpy).toHaveBeenCalledWith({ + payload: { overlayId: OVERLAY_ID }, + type: 'overlayBioEntity/removeOverlayBioEntityForGivenOverlay', + }); + }); + it('should dispatch plugin event with hidden overlay', () => { + const pluginDispatchEvent = jest.spyOn(PluginsEventBus, 'dispatchEvent'); + + getStateSpy.mockImplementation( + () => + ({ + overlays: { + ...OVERLAYS_INITIAL_STATE_MOCK, + userOverlays: { + data: overlaysFixture, + loading: 'idle', + error: DEFAULT_ERROR, + }, + }, + overlayBioEntity: { ...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK, overlaysId: [OVERLAY_ID] }, + }) as RootState, + ); + + hideDataOverlay(OVERLAY_ID); + + expect(pluginDispatchEvent).toHaveBeenCalledWith('onHideOverlay', overlaysFixture[0]); + }); +}); diff --git a/src/services/pluginsManager/map/overlays/hideDataOverlay.ts b/src/services/pluginsManager/map/overlays/hideDataOverlay.ts new file mode 100644 index 00000000..17ca62b3 --- /dev/null +++ b/src/services/pluginsManager/map/overlays/hideDataOverlay.ts @@ -0,0 +1,26 @@ +import { isOverlayActiveSelector } from '@/redux/overlayBioEntity/overlayBioEntity.selector'; +import { removeOverlayBioEntityForGivenOverlay } from '@/redux/overlayBioEntity/overlayBioEntity.slice'; +import { overlaySelector, userOverlaySelector } from '@/redux/overlays/overlays.selectors'; +import { store } from '@/redux/store'; +import { PluginsEventBus } from '../../pluginsEventBus'; +import { ERROR_OVERLAY_ID_NOT_ACTIVE, ERROR_OVERLAY_ID_NOT_FOUND } from '../../errorMessages'; + +export const hideDataOverlay = (overlayId: number): void => { + const { dispatch, getState } = store; + const state = getState(); + const isOverlayActive = isOverlayActiveSelector(state, overlayId); + const overlay = overlaySelector(state, overlayId); + const userOverlay = userOverlaySelector(state, overlayId); + + const matchingOverlay = overlay || userOverlay; + + if (!matchingOverlay) throw new Error(ERROR_OVERLAY_ID_NOT_FOUND); + + if (!isOverlayActive) { + throw new Error(ERROR_OVERLAY_ID_NOT_ACTIVE); + } + + dispatch(removeOverlayBioEntityForGivenOverlay({ overlayId })); + + PluginsEventBus.dispatchEvent('onHideOverlay', matchingOverlay); +}; diff --git a/src/services/pluginsManager/map/overlays/removeDataOverlay.test.ts b/src/services/pluginsManager/map/overlays/removeDataOverlay.test.ts new file mode 100644 index 00000000..a178691f --- /dev/null +++ b/src/services/pluginsManager/map/overlays/removeDataOverlay.test.ts @@ -0,0 +1,44 @@ +/* eslint-disable no-magic-numbers */ +import { RootState, store } from '@/redux/store'; +import { OVERLAYS_INITIAL_STATE_MOCK } from '@/redux/overlays/overlays.mock'; +import { overlaysFixture } from '@/models/fixtures/overlaysFixture'; +import { DEFAULT_ERROR } from '@/constants/errors'; +import { removeOverlay } from '@/redux/overlays/overlays.thunks'; +import { removeDataOverlay } from './removeDataOverlay'; +import { ERROR_OVERLAY_ID_NOT_FOUND } from '../../errorMessages'; + +jest.mock('../../../../redux/store'); +jest.mock('../../../../redux/overlays/overlays.thunks'); + +const MOCK_STATE = { + overlays: { + ...OVERLAYS_INITIAL_STATE_MOCK, + userOverlays: { + data: overlaysFixture, + loading: 'idle', + error: DEFAULT_ERROR, + }, + }, +}; + +describe('removeDataOverlay', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + const getStateSpy = jest.spyOn(store, 'getState'); + it('should throw error if matching overlay not found', () => { + const overlayId = 991; + getStateSpy.mockImplementation(() => MOCK_STATE as RootState); + + expect(() => removeDataOverlay(overlayId)).toThrow(ERROR_OVERLAY_ID_NOT_FOUND); + }); + + it('should dispatch removeOverlay with correct overlayId if matching overlay is found', () => { + getStateSpy.mockImplementation(() => MOCK_STATE as RootState); + const overlayId = overlaysFixture[0].idObject; + + removeDataOverlay(overlayId); + + expect(removeOverlay).toHaveBeenCalledWith({ overlayId }); + }); +}); diff --git a/src/services/pluginsManager/map/overlays/removeDataOverlay.ts b/src/services/pluginsManager/map/overlays/removeDataOverlay.ts new file mode 100644 index 00000000..f730e17b --- /dev/null +++ b/src/services/pluginsManager/map/overlays/removeDataOverlay.ts @@ -0,0 +1,18 @@ +import { overlaySelector, userOverlaySelector } from '@/redux/overlays/overlays.selectors'; +import { removeOverlay } from '@/redux/overlays/overlays.thunks'; +import { store } from '@/redux/store'; +import { ERROR_OVERLAY_ID_NOT_FOUND } from '../../errorMessages'; + +export const removeDataOverlay = (overlayId: number): void => { + const { dispatch, getState } = store; + const state = getState(); + + const overlay = overlaySelector(state, overlayId); + const userOverlay = userOverlaySelector(state, overlayId); + + const matchingOverlayId = overlay || userOverlay; + + if (!matchingOverlayId) throw new Error(ERROR_OVERLAY_ID_NOT_FOUND); + + dispatch(removeOverlay({ overlayId })); +}; diff --git a/src/services/pluginsManager/map/overlays/showDataOverlay/index.ts b/src/services/pluginsManager/map/overlays/showDataOverlay/index.ts new file mode 100644 index 00000000..05d6fd3e --- /dev/null +++ b/src/services/pluginsManager/map/overlays/showDataOverlay/index.ts @@ -0,0 +1 @@ +export { showDataOverlay } from './showDataOverlay'; diff --git a/src/services/pluginsManager/map/overlays/showDataOverlay/setBackgroundtoEmptyIfAvailable.test.ts b/src/services/pluginsManager/map/overlays/showDataOverlay/setBackgroundtoEmptyIfAvailable.test.ts new file mode 100644 index 00000000..15b32c94 --- /dev/null +++ b/src/services/pluginsManager/map/overlays/showDataOverlay/setBackgroundtoEmptyIfAvailable.test.ts @@ -0,0 +1,52 @@ +import { + BACKGROUNDS_MOCK, + BACKGROUND_INITIAL_STATE_MOCK, +} from '@/redux/backgrounds/background.mock'; +import { initialMapStateFixture } from '@/redux/map/map.fixtures'; +import { RootState, store } from '@/redux/store'; +import { setBackgroundtoEmptyIfAvailable } from './setBackgroundtoEmptyIfAvailable'; + +const DEFAULT_BACKGROUND_ID = 0; +const EMPTY_BACKGROUND_ID = 15; + +describe('setEmptyBackground', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + const getStateSpy = jest.spyOn(store, 'getState'); + const dispatchSpy = jest.spyOn(store, 'dispatch'); + it('should not set background to empty if its not available', () => { + getStateSpy.mockImplementation( + () => + ({ + map: initialMapStateFixture, + backgrounds: BACKGROUND_INITIAL_STATE_MOCK, + }) as RootState, + ); + + expect(store.getState().map.data.backgroundId).toBe(DEFAULT_BACKGROUND_ID); + + setBackgroundtoEmptyIfAvailable(); + + expect(dispatchSpy).not.toHaveBeenCalled(); + }); + + it('should set background to empty if its available', () => { + getStateSpy.mockImplementation( + () => + ({ + map: initialMapStateFixture, + backgrounds: { ...BACKGROUND_INITIAL_STATE_MOCK, data: BACKGROUNDS_MOCK }, + }) as RootState, + ); + + expect(store.getState().map.data.backgroundId).toBe(DEFAULT_BACKGROUND_ID); + + setBackgroundtoEmptyIfAvailable(); + + expect(dispatchSpy).toHaveBeenCalledWith({ + payload: EMPTY_BACKGROUND_ID, + type: 'map/setMapBackground', + }); + }); +}); diff --git a/src/services/pluginsManager/map/overlays/showDataOverlay/setBackgroundtoEmptyIfAvailable.ts b/src/services/pluginsManager/map/overlays/showDataOverlay/setBackgroundtoEmptyIfAvailable.ts new file mode 100644 index 00000000..59c6c061 --- /dev/null +++ b/src/services/pluginsManager/map/overlays/showDataOverlay/setBackgroundtoEmptyIfAvailable.ts @@ -0,0 +1,12 @@ +import { emptyBackgroundIdSelector } from '@/redux/backgrounds/background.selectors'; +import { setMapBackground } from '@/redux/map/map.slice'; +import { store } from '@/redux/store'; + +export const setBackgroundtoEmptyIfAvailable = (): void => { + const { dispatch, getState } = store; + const emptyBackgroundId = emptyBackgroundIdSelector(getState()); + + if (emptyBackgroundId) { + dispatch(setMapBackground(emptyBackgroundId)); + } +}; diff --git a/src/services/pluginsManager/map/overlays/showDataOverlay/showDataOverlay.test.ts b/src/services/pluginsManager/map/overlays/showDataOverlay/showDataOverlay.test.ts new file mode 100644 index 00000000..3ccebefa --- /dev/null +++ b/src/services/pluginsManager/map/overlays/showDataOverlay/showDataOverlay.test.ts @@ -0,0 +1,151 @@ +/* eslint-disable no-magic-numbers */ +import { RootState, store } from '@/redux/store'; +import { overlaysFixture } from '@/models/fixtures/overlaysFixture'; +import { DEFAULT_ERROR } from '@/constants/errors'; +import { OVERLAYS_INITIAL_STATE_MOCK } from '@/redux/overlays/overlays.mock'; +import { OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK } from '@/redux/overlayBioEntity/overlayBioEntity.mock'; +import { + ERROR_OVERLAY_ID_ALREADY_ACTIVE, + ERROR_OVERLAY_ID_NOT_FOUND, +} from '@/services/pluginsManager/errorMessages'; +import { getOverlayBioEntityForAllModels } from '@/redux/overlayBioEntity/overlayBioEntity.thunk'; +import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus'; +import { showDataOverlay } from './showDataOverlay'; +import { setBackgroundtoEmptyIfAvailable } from './setBackgroundtoEmptyIfAvailable'; + +jest.mock('../../../../../redux/overlayBioEntity/overlayBioEntity.thunk'); +jest.mock('../../../../../redux/store'); +jest.mock('./setBackgroundtoEmptyIfAvailable'); + +const OVERLAY_ID = overlaysFixture[0].idObject; + +describe('showDataOverlay function', () => { + afterEach(() => { + jest.clearAllMocks(); // Clear mocks after each test + }); + + const getStateSpy = jest.spyOn(store, 'getState'); + + it('should throw error if overlay is already active', () => { + getStateSpy.mockImplementation( + () => + ({ + overlays: { + ...OVERLAYS_INITIAL_STATE_MOCK, + userOverlays: { + data: overlaysFixture, + loading: 'idle', + error: DEFAULT_ERROR, + }, + }, + overlayBioEntity: { ...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK, overlaysId: [OVERLAY_ID] }, + }) as RootState, + ); + + expect(() => showDataOverlay(OVERLAY_ID)).toThrow(ERROR_OVERLAY_ID_ALREADY_ACTIVE); + }); + + it('should throw error if matching overlay is not found', () => { + getStateSpy.mockImplementation( + () => + ({ + overlays: { + ...OVERLAYS_INITIAL_STATE_MOCK, + userOverlays: { + data: overlaysFixture, + loading: 'idle', + error: DEFAULT_ERROR, + }, + }, + overlayBioEntity: { ...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK, overlaysId: [OVERLAY_ID] }, + }) as RootState, + ); + + expect(() => showDataOverlay(991)).toThrow(ERROR_OVERLAY_ID_NOT_FOUND); + }); + + it('should dispatch overlay id to show', () => { + getStateSpy.mockImplementation( + () => + ({ + overlays: { + ...OVERLAYS_INITIAL_STATE_MOCK, + userOverlays: { + data: overlaysFixture, + loading: 'idle', + error: DEFAULT_ERROR, + }, + }, + overlayBioEntity: { ...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK }, + }) as RootState, + ); + + showDataOverlay(OVERLAY_ID); + + expect(getOverlayBioEntityForAllModels).toHaveBeenCalledWith({ overlayId: OVERLAY_ID }); + }); + + it('should dispatch plugin event with overlay to show', () => { + const pluginDispatchEvent = jest.spyOn(PluginsEventBus, 'dispatchEvent'); + + getStateSpy.mockImplementation( + () => + ({ + overlays: { + ...OVERLAYS_INITIAL_STATE_MOCK, + userOverlays: { + data: overlaysFixture, + loading: 'idle', + error: DEFAULT_ERROR, + }, + }, + overlayBioEntity: { ...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK }, + }) as RootState, + ); + + showDataOverlay(OVERLAY_ID); + + expect(pluginDispatchEvent).toHaveBeenCalledWith('onShowOverlay', overlaysFixture[0]); + }); + + it('should not set empty background when it show overlay', () => { + getStateSpy.mockImplementation( + () => + ({ + overlays: { + ...OVERLAYS_INITIAL_STATE_MOCK, + userOverlays: { + data: overlaysFixture, + loading: 'idle', + error: DEFAULT_ERROR, + }, + }, + overlayBioEntity: { ...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK }, + }) as RootState, + ); + + showDataOverlay(OVERLAY_ID); + + expect(setBackgroundtoEmptyIfAvailable).not.toHaveBeenCalled(); + }); + it('should set empty background when it show overlay if setBackgroundEmpty is true', () => { + getStateSpy.mockImplementation( + () => + ({ + overlays: { + ...OVERLAYS_INITIAL_STATE_MOCK, + userOverlays: { + data: overlaysFixture, + loading: 'idle', + error: DEFAULT_ERROR, + }, + }, + overlayBioEntity: { ...OVERLAY_BIO_ENTITY_INITIAL_STATE_MOCK }, + }) as RootState, + ); + + showDataOverlay(OVERLAY_ID, true); + + expect(setBackgroundtoEmptyIfAvailable).toHaveBeenCalled(); + }); +}); diff --git a/src/services/pluginsManager/map/overlays/showDataOverlay/showDataOverlay.ts b/src/services/pluginsManager/map/overlays/showDataOverlay/showDataOverlay.ts new file mode 100644 index 00000000..8304beed --- /dev/null +++ b/src/services/pluginsManager/map/overlays/showDataOverlay/showDataOverlay.ts @@ -0,0 +1,33 @@ +import { store } from '@/redux/store'; +import { getOverlayBioEntityForAllModels } from '@/redux/overlayBioEntity/overlayBioEntity.thunk'; +import { isOverlayActiveSelector } from '@/redux/overlayBioEntity/overlayBioEntity.selector'; +import { overlaySelector, userOverlaySelector } from '@/redux/overlays/overlays.selectors'; +import { PluginsEventBus } from '@/services/pluginsManager/pluginsEventBus'; +import { + ERROR_OVERLAY_ID_ALREADY_ACTIVE, + ERROR_OVERLAY_ID_NOT_FOUND, +} from '@/services/pluginsManager/errorMessages'; +import { setBackgroundtoEmptyIfAvailable } from './setBackgroundtoEmptyIfAvailable'; + +export const showDataOverlay = (overlayId: number, setBackgroundEmpty?: boolean): void => { + const { dispatch, getState } = store; + const state = getState(); + const isOverlayActive = isOverlayActiveSelector(state, overlayId); + const overlay = overlaySelector(state, overlayId); + const userOverlay = userOverlaySelector(state, overlayId); + + const matchingOverlay = overlay || userOverlay; + + if (!matchingOverlay) throw new Error(ERROR_OVERLAY_ID_NOT_FOUND); + + if (isOverlayActive) { + throw new Error(ERROR_OVERLAY_ID_ALREADY_ACTIVE); + } + + if (setBackgroundEmpty) { + setBackgroundtoEmptyIfAvailable(); + } + + dispatch(getOverlayBioEntityForAllModels({ overlayId })); + PluginsEventBus.dispatchEvent('onShowOverlay', matchingOverlay); +}; diff --git a/src/services/pluginsManager/pluginsManager.ts b/src/services/pluginsManager/pluginsManager.ts index f432527a..0b083ef9 100644 --- a/src/services/pluginsManager/pluginsManager.ts +++ b/src/services/pluginsManager/pluginsManager.ts @@ -18,12 +18,18 @@ import { showOverviewImageModal } from './overviewImage/showOverviewImageModal'; import { PluginsEventBus } from './pluginsEventBus'; import type { PluginsManagerType } from './pluginsManager.types'; import { configurationMapper } from './pluginsManager.utils'; +import { getDataOverlays } from './map/overlays/getDataOverlays'; +import { getVisibleDataOverlays } from './map/overlays/getVisibleDataOverlays'; +import { showDataOverlay } from './map/overlays/showDataOverlay'; +import { hideDataOverlay } from './map/overlays/hideDataOverlay'; +import { removeDataOverlay } from './map/overlays/removeDataOverlay'; +import { addDataOverlay } from './map/overlays/addDataOverlay'; +import { getApiUrls } from './project/data/getApiUrls'; import { getDisease } from './project/data/getDisease'; import { getName } from './project/data/getName'; import { getOrganism } from './project/data/getOrganism'; import { getProjectId } from './project/data/getProjectId'; import { getVersion } from './project/data/getVersion'; - import { getBounds } from './map/data/getBounds'; import { fitBounds } from './map/fitBounds'; import { getOpenMapId } from './map/getOpenMapId'; @@ -66,6 +72,16 @@ export const PluginsManager: PluginsManagerType = { selectOverviewImage, showOverviewImageModal, }, + overlays: { + data: { + getDataOverlays, + getVisibleDataOverlays, + }, + showDataOverlay, + hideDataOverlay, + removeDataOverlay, + addDataOverlay, + }, project: { data: { getProjectId, @@ -73,6 +89,7 @@ export const PluginsManager: PluginsManagerType = { getVersion, getDisease, getOrganism, + getApiUrls, }, }, }; diff --git a/src/services/pluginsManager/project/data/getApiUrls.ts b/src/services/pluginsManager/project/data/getApiUrls.ts new file mode 100644 index 00000000..cd5bd972 --- /dev/null +++ b/src/services/pluginsManager/project/data/getApiUrls.ts @@ -0,0 +1,11 @@ +import { BASE_API_URL, BASE_NEW_API_URL } from '@/constants'; + +type ApiUrls = { + baseApiUrl: string; + baseNewApiUrl: string; +}; + +export const getApiUrls = (): ApiUrls => ({ + baseApiUrl: BASE_API_URL, + baseNewApiUrl: BASE_NEW_API_URL, +}); -- GitLab