diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.component.test.tsx b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.component.test.tsx index 9fb75c283445be61f5ea351185806fb7ca790eb1..474934d8f7bf9805432330ca0361a9ee2a7572e3 100644 --- a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.component.test.tsx +++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/UserOverlayForm.component.test.tsx @@ -1,6 +1,6 @@ /* eslint-disable no-magic-numbers */ import React from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { projectFixture } from '@/models/fixtures/projectFixture'; import { InitialStoreState, @@ -16,9 +16,11 @@ import { apiPath } from '@/redux/apiPath'; import { createdOverlayFileFixture, createdOverlayFixture, + overlaysFixture, uploadedOverlayFileContentFixture, } from '@/models/fixtures/overlaysFixture'; import { OVERLAYS_INITIAL_STATE_MOCK } from '@/redux/overlays/overlays.mock'; +import { DEFAULT_ERROR } from '@/constants/errors'; import { UserOverlayForm } from './UserOverlayForm.component'; const mockedAxiosClient = mockNetworkResponse(); @@ -68,11 +70,11 @@ describe('UserOverlayForm - Component', () => { .reply(HttpStatusCode.Ok, createdOverlayFileFixture); mockedAxiosClient - .onPost(apiPath.uploadOverlayFileContent(123)) + .onPost(apiPath.uploadOverlayFileContent(createdOverlayFileFixture.id)) .reply(HttpStatusCode.Ok, uploadedOverlayFileContentFixture); mockedAxiosClient - .onPost(apiPath.createOverlay('pd')) + .onPost(apiPath.createOverlay(projectFixture.projectId)) .reply(HttpStatusCode.Ok, createdOverlayFixture); renderComponent({ @@ -189,4 +191,62 @@ describe('UserOverlayForm - Component', () => { expect(currentStep).toBe(1); }); + it('should refetch user overlays after submit the form', 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); + + mockedAxiosClient + .onGet(apiPath.getAllUserOverlaysByCreatorQuery({ creator: 'test', publicOverlay: false })) + .reply(HttpStatusCode.Ok, overlaysFixture); + + const { store } = renderComponent({ + user: { + authenticated: true, + error: DEFAULT_ERROR, + login: 'test', + loading: 'succeeded', + }, + project: { + data: projectFixture, + loading: 'succeeded', + error: DEFAULT_ERROR, + }, + overlays: OVERLAYS_INITIAL_STATE_MOCK, + }); + + const userOverlays = store.getState().overlays.userOverlays.data; + + expect(userOverlays).toEqual([]); + + fireEvent.change(screen.getByTestId('overlay-name'), { target: { value: 'Test Overlay' } }); + fireEvent.change(screen.getByTestId('overlay-description'), { + target: { value: 'Description Overlay' }, + }); + fireEvent.change(screen.getByTestId('overlay-elements-list'), { + target: { value: 'Elements List Overlay' }, + }); + + fireEvent.click(screen.getByLabelText('upload overlay')); + + expect(screen.getByLabelText('upload overlay')).toBeDisabled(); + + await waitFor(() => { + expect(store.getState().overlays.userOverlays.loading).toBe('succeeded'); + }); + + const refetchedUserOverlays = store.getState().overlays.userOverlays.data; + + await waitFor(() => { + expect(refetchedUserOverlays).toEqual(overlaysFixture); + }); + }); }); diff --git a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/hooks/useUserOverlayForm.ts b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/hooks/useUserOverlayForm.ts index dda281bf7dc92145f1902d283f40a7521a34ee25..5229c9fa0cb374030ed3d5ae294d07d0062f5331 100644 --- a/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/hooks/useUserOverlayForm.ts +++ b/src/components/Map/Drawer/OverlaysDrawer/UserOverlayForm/hooks/useUserOverlayForm.ts @@ -100,12 +100,12 @@ export const useUserOverlayForm = (): ReturnType => { filename = 'unknown.txt'; // Elements list is sent to the backend as a file, so we need to create a filename for the elements list. } - if (!overlayContent || !projectId || !description || !name) return; + if (!overlayContent || !projectId || !name) return; dispatch( addOverlay({ content: overlayContent, - description, + description: description || '', filename, name, projectId, diff --git a/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.test.ts b/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.test.ts index 3c4dbcfdeec212a14ebc661e6cdfb9723bd02199..74d8d6bf04ca2dafef844575915682af5819ed97 100644 --- a/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.test.ts +++ b/src/components/Map/MapViewer/utils/config/overlaysLayer/useOverlayFeatures.test.ts @@ -4,10 +4,14 @@ import { mapStateWithCurrentlySelectedMainMapFixture } from '@/redux/map/map.fix import { SURFACE_MARKER } from '@/redux/models/marker.mock'; import { MODELS_DATA_MOCK_WITH_MAIN_MAP } from '@/redux/models/models.mock'; import { MOCKED_OVERLAY_BIO_ENTITY_RENDER } from '@/redux/overlayBioEntity/overlayBioEntity.mock'; -import { OVERLAYS_PUBLIC_FETCHED_STATE_MOCK } from '@/redux/overlays/overlays.mock'; +import { + OVERLAYS_PUBLIC_FETCHED_STATE_MOCK, + USER_OVERLAYS_MOCK, +} from '@/redux/overlays/overlays.mock'; import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; import { renderHook } from '@testing-library/react'; import { useOverlayFeatures } from './useOverlayFeatures'; +import * as getPolygonLatitudeCoordinates from './getPolygonLatitudeCoordinates'; /** * mocks for useOverlayFeatures @@ -15,7 +19,20 @@ import { useOverlayFeatures } from './useOverlayFeatures'; * point of the test is to test if all helper functions work correctly when combined together */ +jest.mock('./getPolygonLatitudeCoordinates', () => ({ + __esModule: true, + ...jest.requireActual('./getPolygonLatitudeCoordinates'), +})); + +const getPolygonLatitudeCoordinatesSpy = jest.spyOn( + getPolygonLatitudeCoordinates, + 'getPolygonLatitudeCoordinates', +); + describe('useOverlayFeatures', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); const { Wrapper } = getReduxWrapperWithStore({ configuration: { ...CONFIGURATION_INITIAL_STORE_MOCKS, @@ -100,4 +117,91 @@ describe('useOverlayFeatures', () => { // @ts-ignore expect(features[10].getStyle().getFill().getColor()).toBe('#0000001a'); }); + + it('should get coordinates only for active general overlays and user overlays if exist', () => { + const { Wrapper: OverlayFeaturesWrapper } = getReduxWrapperWithStore({ + configuration: { + ...CONFIGURATION_INITIAL_STORE_MOCKS, + }, + overlayBioEntity: { + overlaysId: [11, 12, 99, 123], + data: { + // overlayId + 11: { + // modelId + 52: MOCKED_OVERLAY_BIO_ENTITY_RENDER, + 53: MOCKED_OVERLAY_BIO_ENTITY_RENDER, + }, + 12: { + 52: MOCKED_OVERLAY_BIO_ENTITY_RENDER, + 53: MOCKED_OVERLAY_BIO_ENTITY_RENDER, + }, + 99: { + 52: MOCKED_OVERLAY_BIO_ENTITY_RENDER, + 53: MOCKED_OVERLAY_BIO_ENTITY_RENDER, + }, + 123: { + 52: MOCKED_OVERLAY_BIO_ENTITY_RENDER, + 53: MOCKED_OVERLAY_BIO_ENTITY_RENDER, + }, + }, + }, + overlays: { + ...OVERLAYS_PUBLIC_FETCHED_STATE_MOCK, + userOverlays: { + data: USER_OVERLAYS_MOCK, + error: { message: '', name: '' }, + loading: 'succeeded', + }, + }, + map: { + ...mapStateWithCurrentlySelectedMainMapFixture, + }, + models: { + ...MODELS_DATA_MOCK_WITH_MAIN_MAP, + }, + }); + + renderHook(() => useOverlayFeatures(), { + wrapper: OverlayFeaturesWrapper, + }); + + expect(getPolygonLatitudeCoordinatesSpy).toHaveBeenLastCalledWith({ + nOverlays: 4, + overlayIndexBasedOnOrder: 0, + width: 8.923194537814197, + xMin: 4454.850442288663, + }); + }); + it('should not get coordinates for active overlays if no overlay is active', () => { + const { Wrapper: OverlayFeaturesWrapper } = getReduxWrapperWithStore({ + configuration: { + ...CONFIGURATION_INITIAL_STORE_MOCKS, + }, + overlayBioEntity: { + overlaysId: [], + data: {}, + }, + overlays: { + ...OVERLAYS_PUBLIC_FETCHED_STATE_MOCK, + userOverlays: { + data: USER_OVERLAYS_MOCK, + error: { message: '', name: '' }, + loading: 'succeeded', + }, + }, + map: { + ...mapStateWithCurrentlySelectedMainMapFixture, + }, + models: { + ...MODELS_DATA_MOCK_WITH_MAIN_MAP, + }, + }); + + renderHook(() => useOverlayFeatures(), { + wrapper: OverlayFeaturesWrapper, + }); + + expect(getPolygonLatitudeCoordinatesSpy).not.toHaveBeenCalled(); + }); }); diff --git a/src/redux/overlayBioEntity/overlayBioEntity.selector.ts b/src/redux/overlayBioEntity/overlayBioEntity.selector.ts index e42ee1cba731c1c9fe3edc05dcf225e3ceeeeb9f..f6ad669d69dad1723460c82f8764112db4ee87cd 100644 --- a/src/redux/overlayBioEntity/overlayBioEntity.selector.ts +++ b/src/redux/overlayBioEntity/overlayBioEntity.selector.ts @@ -2,9 +2,18 @@ import { OverlayBioEntityRender } from '@/types/OLrendering'; import { createSelector } from '@reduxjs/toolkit'; import { currentSearchedBioEntityId } from '../drawer/drawer.selectors'; import { currentModelIdSelector } from '../models/models.selectors'; -import { overlaysDataSelector, overlaysIdsAndOrderSelector } from '../overlays/overlays.selectors'; +import { + overlaysDataSelector, + overlaysIdsAndOrderSelector, + userOverlaysDataSelector, + userOverlaysIdsAndOrderSelector, +} from '../overlays/overlays.selectors'; import { rootSelector } from '../root/root.selectors'; -import { calculateOvarlaysOrder } from './overlayBioEntity.utils'; +import { + calculateOvarlaysOrder, + getActiveOverlaysIdsAndOrder, + getActiveUserOverlaysIdsAndOrder, +} from './overlayBioEntity.utils'; export const overlayBioEntitySelector = createSelector( rootSelector, @@ -52,19 +61,35 @@ export const isOverlayLoadingSelector = createSelector( export const activeOverlaysSelector = createSelector( rootSelector, overlaysDataSelector, - (state, overlaysData) => - overlaysData.filter(overlay => isOverlayActiveSelector(state, overlay.idObject)), + userOverlaysDataSelector, + (state, overlaysData, userOverlaysData) => { + const activeOverlays = overlaysData.filter(overlay => + isOverlayActiveSelector(state, overlay.idObject), + ); + const activeUserOverlays = + userOverlaysData?.filter(overlay => isOverlayActiveSelector(state, overlay.idObject)) || []; + + return [...activeOverlays, ...activeUserOverlays]; + }, ); export const getOverlayOrderSelector = createSelector( overlaysIdsAndOrderSelector, + userOverlaysIdsAndOrderSelector, activeOverlaysIdSelector, - (overlaysIdsAndOrder, activeOverlaysIds) => { - const activeOverlaysIdsAndOrder = overlaysIdsAndOrder.filter(({ idObject }) => - activeOverlaysIds.includes(idObject), + (overlaysIdsAndOrder, userOverlaysIdsAndOrder, activeOverlaysIds) => { + const { activeOverlaysIdsAndOrder, maxOrderValue } = getActiveOverlaysIdsAndOrder( + overlaysIdsAndOrder, + activeOverlaysIds, + ); + + const activeUserOverlaysIdsAndOrder = getActiveUserOverlaysIdsAndOrder( + userOverlaysIdsAndOrder, + activeOverlaysIds, + maxOrderValue, ); - return calculateOvarlaysOrder(activeOverlaysIdsAndOrder); + return calculateOvarlaysOrder([...activeOverlaysIdsAndOrder, ...activeUserOverlaysIdsAndOrder]); }, ); diff --git a/src/redux/overlayBioEntity/overlayBioEntity.types.ts b/src/redux/overlayBioEntity/overlayBioEntity.types.ts index 4074058bae71559b4c40de5a3adedaa0be381049..2733514a48df8541ebabfb5d2d764e041fbe869c 100644 --- a/src/redux/overlayBioEntity/overlayBioEntity.types.ts +++ b/src/redux/overlayBioEntity/overlayBioEntity.types.ts @@ -13,3 +13,8 @@ export type OverlaysBioEntityState = { export type RemoveOverlayBioEntityForGivenOverlayPayload = { overlayId: number }; export type RemoveOverlayBioEntityForGivenOverlayAction = PayloadAction<RemoveOverlayBioEntityForGivenOverlayPayload>; + +export type OverlaysIdsAndOrder = { + idObject: number; + order: number; +}[]; diff --git a/src/redux/overlayBioEntity/overlayBioEntity.utils.test.ts b/src/redux/overlayBioEntity/overlayBioEntity.utils.test.ts index abb5b8fb8b1a2e841f19a268a3c49a4e2350de02..d4248b65ad9b72049d7ff222881c3f90f043c72c 100644 --- a/src/redux/overlayBioEntity/overlayBioEntity.utils.test.ts +++ b/src/redux/overlayBioEntity/overlayBioEntity.utils.test.ts @@ -1,6 +1,12 @@ +/* eslint-disable no-magic-numbers */ import { overlayBioEntityFixture } from '@/models/fixtures/overlayBioEntityFixture'; import { OverlayBioEntity } from '@/types/models'; -import { calculateOvarlaysOrder, getValidOverlayBioEntities } from './overlayBioEntity.utils'; +import { + calculateOvarlaysOrder, + getActiveOverlaysIdsAndOrder, + getActiveUserOverlaysIdsAndOrder, + getValidOverlayBioEntities, +} from './overlayBioEntity.utils'; describe('calculateOverlaysOrder', () => { const cases = [ @@ -98,3 +104,113 @@ describe('getValidOverlayBioEntities', () => { expect(result).toEqual([]); }); }); + +describe('getActiveUserOverlaysIdsAndOrder', () => { + const userOverlaysIdsAndOrder = [ + { idObject: 1, order: 1 }, + { idObject: 2, order: 2 }, + { idObject: 3, order: 3 }, + { idObject: 4, order: 4 }, + ]; + + const activeOverlaysIds = [2, 4]; + const maxOrderValue = 10; + + it('should return active user overlays with updated order values', () => { + const expectedOutput = [ + { idObject: 2, order: 12 }, + { idObject: 4, order: 14 }, + ]; + expect( + getActiveUserOverlaysIdsAndOrder(userOverlaysIdsAndOrder, activeOverlaysIds, maxOrderValue), + ).toEqual(expectedOutput); + }); + + it('should return an empty array when there are no active overlays', () => { + expect(getActiveUserOverlaysIdsAndOrder(userOverlaysIdsAndOrder, [], maxOrderValue)).toEqual( + [], + ); + }); + + it('should return all user overlays with updated order values when all overlays are active', () => { + const allOverlaysActive = [1, 2, 3, 4]; + const expectedOutput = [ + { idObject: 1, order: 11 }, + { idObject: 2, order: 12 }, + { idObject: 3, order: 13 }, + { idObject: 4, order: 14 }, + ]; + expect( + getActiveUserOverlaysIdsAndOrder(userOverlaysIdsAndOrder, allOverlaysActive, maxOrderValue), + ).toEqual(expectedOutput); + }); + + it('should return active user overlays with order values starting from 1 when maxOrderValue is 0', () => { + const maxOrderZero = 0; + const expectedOutput = [ + { idObject: 2, order: 2 }, + { idObject: 4, order: 4 }, + ]; + expect( + getActiveUserOverlaysIdsAndOrder(userOverlaysIdsAndOrder, activeOverlaysIds, maxOrderZero), + ).toEqual(expectedOutput); + }); +}); + +describe('getActiveOverlaysIdsAndOrder', () => { + const overlaysIdsAndOrder = [ + { idObject: 1, order: 1 }, + { idObject: 2, order: 2 }, + { idObject: 3, order: 3 }, + { idObject: 4, order: 4 }, + ]; + + const activeOverlaysIds = [2, 4]; + + it('should return maxOrderValue and active overlays', () => { + const expectedOutput = { + maxOrderValue: 4, + activeOverlaysIdsAndOrder: [ + { idObject: 2, order: 2 }, + { idObject: 4, order: 4 }, + ], + }; + expect(getActiveOverlaysIdsAndOrder(overlaysIdsAndOrder, activeOverlaysIds)).toEqual( + expectedOutput, + ); + }); + + it('should return maxOrderValue as 0 when there are no active overlays', () => { + const expectedOutput = { + maxOrderValue: 0, + activeOverlaysIdsAndOrder: [], + }; + expect(getActiveOverlaysIdsAndOrder(overlaysIdsAndOrder, [])).toEqual(expectedOutput); + }); + + it('should return maxOrderValue and all overlays when all overlays are active', () => { + const allOverlaysActive = [1, 2, 3, 4]; + const expectedOutput = { + maxOrderValue: 4, + activeOverlaysIdsAndOrder: overlaysIdsAndOrder, + }; + expect(getActiveOverlaysIdsAndOrder(overlaysIdsAndOrder, allOverlaysActive)).toEqual( + expectedOutput, + ); + }); + + it('should return maxOrderValue as 0 and no active overlays when none of the overlays are active', () => { + const expectedOutput = { + maxOrderValue: 0, + activeOverlaysIdsAndOrder: [], + }; + expect(getActiveOverlaysIdsAndOrder(overlaysIdsAndOrder, [])).toEqual(expectedOutput); + }); + it('should return maxOrderValue as 0 and no active overlays when there are no overlays', () => { + const expectedOutput = { + maxOrderValue: 0, + activeOverlaysIdsAndOrder: [], + }; + expect(getActiveOverlaysIdsAndOrder([], [])).toEqual(expectedOutput); + }); +}); diff --git a/src/redux/overlayBioEntity/overlayBioEntity.utils.ts b/src/redux/overlayBioEntity/overlayBioEntity.utils.ts index becf2d2ab94699294879668994a6b5c88cf73f62..420ec1d08e39f19f7bc6c2acf79753aaa5cd0d6e 100644 --- a/src/redux/overlayBioEntity/overlayBioEntity.utils.ts +++ b/src/redux/overlayBioEntity/overlayBioEntity.utils.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-magic-numbers */ import { ONE } from '@/constants/common'; import { overlayBioEntitySchema } from '@/models/overlayBioEntitySchema'; import { OverlayBioEntityRender } from '@/types/OLrendering'; @@ -5,6 +6,7 @@ import { OverlayBioEntity } from '@/types/models'; import { getOverlayReactionCoordsFromLine } from '@/utils/overlays/getOverlayReactionCoords'; import { isBioEntity, isReaction, isSubmapLink } from '@/utils/overlays/overlaysElementsTypeGuards'; import { z } from 'zod'; +import { OverlaysIdsAndOrder } from './overlayBioEntity.types'; export const parseOverlayBioEntityToOlRenderingFormat = ( data: OverlayBioEntity[], @@ -142,3 +144,53 @@ export const getValidOverlayBioEntities = ( return parsedOverlayBioEntities.success ? parsedOverlayBioEntities.data : undefined; }; + +type GetActiveOverlaysIdsAndOrderReturnType = { + maxOrderValue: number; + activeOverlaysIdsAndOrder: OverlaysIdsAndOrder; +}; + +export const getActiveOverlaysIdsAndOrder = ( + overlaysIdsAndOrder: OverlaysIdsAndOrder, + activeOverlaysIds: number[], +): GetActiveOverlaysIdsAndOrderReturnType => { + let maxOrderValue = -Infinity; + const activeOverlaysIdsAndOrder = overlaysIdsAndOrder.filter(({ idObject }) => + activeOverlaysIds.includes(idObject), + ); + + overlaysIdsAndOrder.forEach(({ idObject, order }) => { + const isActive = activeOverlaysIds.includes(idObject); + if (isActive && order > maxOrderValue) maxOrderValue = order; + }); + + if (maxOrderValue === -Infinity) { + maxOrderValue = 0; + } + + return { + maxOrderValue, + activeOverlaysIdsAndOrder, + }; +}; + +export const getActiveUserOverlaysIdsAndOrder = ( + userOverlaysIdsAndOrder: OverlaysIdsAndOrder, + activeOverlaysIds: number[], + maxOrderValue: number, +): OverlaysIdsAndOrder => { + const clonedUserOverlaysIdsAndOrder: OverlaysIdsAndOrder = JSON.parse( + JSON.stringify(userOverlaysIdsAndOrder), + ); + + const activeUserOverlaysIdsAndOrder = clonedUserOverlaysIdsAndOrder.filter(userOverlay => { + const isActive = activeOverlaysIds.includes(userOverlay.idObject); + if (isActive) { + /* eslint-disable-next-line no-param-reassign */ + userOverlay.order = maxOrderValue + userOverlay.order; // user overlays appear after general overlays, we need to get max order value of general overlays and add to it user overlay order to be ensured that user overlay will appear after general overlays + } + return isActive; + }); + + return activeUserOverlaysIdsAndOrder; +}; diff --git a/src/redux/overlays/overlays.mock.ts b/src/redux/overlays/overlays.mock.ts index 7942bb040427bcc40b158893a6abab60cbdd27e8..4f7d8687ba2205a06193fb67dec32cc049a280d2 100644 --- a/src/redux/overlays/overlays.mock.ts +++ b/src/redux/overlays/overlays.mock.ts @@ -121,3 +121,32 @@ export const ADD_OVERLAY_MOCK = { projectId: 'pd', type: 'GENERIC', }; + +export const USER_OVERLAYS_MOCK: MapOverlay[] = [ + { + name: 'PD substantia nigra', + googleLicenseConsent: false, + creator: 'appu-admin', + description: + 'Differential transcriptome expression from post mortem tissue. Meta-analysis from 8 published datasets, FDR = 0.05, see PMIDs 23832570 and 25447234.', + genomeType: null, + genomeVersion: null, + idObject: 99, + publicOverlay: true, + type: 'GENERIC', + order: 1, + }, + { + name: 'Ageing brain', + googleLicenseConsent: false, + creator: 'appu-admin', + description: + 'Differential transcriptome expression from post mortem tissue. Source: Allen Brain Atlas datasets, see PMID 25447234.', + genomeType: null, + genomeVersion: null, + idObject: 123, + publicOverlay: true, + type: 'GENERIC', + order: 2, + }, +]; diff --git a/src/redux/overlays/overlays.selectors.ts b/src/redux/overlays/overlays.selectors.ts index 87409b8e72d3566869b9c0525ee2c3bf89b4fd08..fc988894cadde34d37479adec4edc9e56356f069 100644 --- a/src/redux/overlays/overlays.selectors.ts +++ b/src/redux/overlays/overlays.selectors.ts @@ -34,6 +34,11 @@ export const userOverlaysDataSelector = createSelector( overlays => overlays.data, ); +export const userOverlaysIdsAndOrderSelector = createSelector( + userOverlaysDataSelector, + userOverlays => userOverlays?.map(({ idObject, order }) => ({ idObject, order })) || [], +); + export const userOverlaySelector = createSelector( [userOverlaysDataSelector, (_, userOverlayId: number): number => userOverlayId], (userOverlays, userOverlayId) => diff --git a/src/redux/overlays/overlays.thunks.ts b/src/redux/overlays/overlays.thunks.ts index 6e290d9f5577f134b68f7d99ef21d5b2d7b4a3cf..6331642162ed8cfcc3b37f756bcbafbabf74a1a2 100644 --- a/src/redux/overlays/overlays.thunks.ts +++ b/src/redux/overlays/overlays.thunks.ts @@ -29,6 +29,36 @@ export const getAllPublicOverlaysByProjectId = createAsyncThunk( }, ); +export const getAllUserOverlaysByCreator = createAsyncThunk( + 'overlays/getAllUserOverlaysByCreator', + async (_, { getState }): Promise<MapOverlay[]> => { + const state = getState() as RootState; + const creator = state.user.login; + if (!creator) return []; + + const response = await axiosInstance<MapOverlay[]>( + apiPath.getAllUserOverlaysByCreatorQuery({ + creator, + publicOverlay: false, + }), + { + withCredentials: true, + }, + ); + + const isDataValid = validateDataUsingZodSchema(response.data, z.array(mapOverlay)); + + const sortByOrder = (userOverlayA: MapOverlay, userOverlayB: MapOverlay): number => { + if (userOverlayA.order > userOverlayB.order) return 1; + return -1; + }; + + const sortedUserOverlays = response.data.sort(sortByOrder); + + return isDataValid ? sortedUserOverlays : []; + }, +); + /** UTILS */ type CreateFileArgs = { @@ -140,14 +170,10 @@ type AddOverlayArgs = { export const addOverlay = createAsyncThunk( 'overlays/addOverlay', - async ({ - filename, - content, - description, - name, - type, - projectId, - }: AddOverlayArgs): Promise<void> => { + async ( + { filename, content, description, name, type, projectId }: AddOverlayArgs, + { dispatch }, + ): Promise<void> => { const createdFile = await createFile({ filename, content, @@ -165,36 +191,8 @@ export const addOverlay = createAsyncThunk( type, projectId, }); - }, -); - -export const getAllUserOverlaysByCreator = createAsyncThunk( - 'overlays/getAllUserOverlaysByCreator', - async (_, { getState }): Promise<MapOverlay[]> => { - const state = getState() as RootState; - const creator = state.user.login; - if (!creator) return []; - const response = await axiosInstance<MapOverlay[]>( - apiPath.getAllUserOverlaysByCreatorQuery({ - creator, - publicOverlay: false, - }), - { - withCredentials: true, - }, - ); - - const isDataValid = validateDataUsingZodSchema(response.data, z.array(mapOverlay)); - - const sortByOrder = (userOverlayA: MapOverlay, userOverlayB: MapOverlay): number => { - if (userOverlayA.order > userOverlayB.order) return 1; - return -1; - }; - - const sortedUserOverlays = response.data.sort(sortByOrder); - - return isDataValid ? sortedUserOverlays : []; + dispatch(getAllUserOverlaysByCreator()); }, );