diff --git a/package-lock.json b/package-lock.json index 9c9854d83d715efae2ea01fb13a68e977737cfa3..d61b7297f78cbf13c0656db0554f629e1362436f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "autoprefixer": "10.4.15", "axios": "^1.5.1", "axios-hooks": "^5.0.0", + "downshift": "^8.2.3", "eslint-config-next": "13.4.19", "next": "13.4.19", "ol": "^8.1.0", @@ -3855,6 +3856,11 @@ "dot-prop": "^5.1.0" } }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz", + "integrity": "sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -4770,6 +4776,26 @@ "node": ">=8" } }, + "node_modules/downshift": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/downshift/-/downshift-8.2.3.tgz", + "integrity": "sha512-1HkvqaMTZpk24aqnXaRDnT+N5JCbpFpW+dCogB11+x+FCtfkFX0MbAO4vr/JdXi1VYQF174KjNUveBXqaXTPtg==", + "dependencies": { + "@babel/runtime": "^7.22.15", + "compute-scroll-into-view": "^3.0.3", + "prop-types": "^15.8.1", + "react-is": "^18.2.0", + "tslib": "^2.6.2" + }, + "peerDependencies": { + "react": ">=16.12.0" + } + }, + "node_modules/downshift/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, "node_modules/drange": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/drange/-/drange-1.1.1.tgz", diff --git a/package.json b/package.json index c0d980a3a0f073e84c5d02f0530d70bf681acbf9..6e3f2e39d607187aa51823be6783b7581b30c9ed 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "autoprefixer": "10.4.15", "axios": "^1.5.1", "axios-hooks": "^5.0.0", + "downshift": "^8.2.3", "eslint-config-next": "13.4.19", "next": "13.4.19", "ol": "^8.1.0", diff --git a/src/components/FunctionalArea/MapNavigation/MapNavigation.component.tsx b/src/components/FunctionalArea/MapNavigation/MapNavigation.component.tsx index 0bc4e1d80759f06d92e40b57f2cd8bded63ac632..06d7c9f013f641a8f8d937ba25983f8d4efa612b 100644 --- a/src/components/FunctionalArea/MapNavigation/MapNavigation.component.tsx +++ b/src/components/FunctionalArea/MapNavigation/MapNavigation.component.tsx @@ -31,12 +31,12 @@ export const MapNavigation = (): JSX.Element => { }; return ( - <div className="flex h-10 w-full flex-row flex-nowrap justify-start overflow-y-auto bg-white-pearl shadow-map-navigation-bar"> + <div className="flex h-10 w-full flex-row flex-nowrap justify-start overflow-y-auto bg-white-pearl text-xs shadow-primary"> {openedMaps.map(map => ( <Button key={map.modelId} className={twMerge( - 'h-10 whitespace-nowrap ', + 'h-10 whitespace-nowrap', isActive(map.modelId) ? 'bg-[#EBF4FF]' : 'font-normal', )} variantStyles={isActive(map.modelId) ? 'secondary' : 'ghost'} diff --git a/src/components/Map/Map.component.tsx b/src/components/Map/Map.component.tsx index 3782c938151684b37d3cde0ea6ed248902188898..27cfcff4ab4b05cde8055df31095e7deddc31739 100644 --- a/src/components/Map/Map.component.tsx +++ b/src/components/Map/Map.component.tsx @@ -1,8 +1,10 @@ import { Drawer } from '@/components/Map/Drawer'; import { MapViewer } from './MapViewer/MapViewer.component'; +import { MapAdditionalOptions } from './MapAdditionalOptions'; export const Map = (): JSX.Element => ( <div className="relative z-0 h-screen w-full bg-black" data-testid="map-container"> + <MapAdditionalOptions /> <Drawer /> <MapViewer /> </div> diff --git a/src/components/Map/MapAdditionalOptions/BackgroundsSelector/BackgroundsSelector.component.test.tsx b/src/components/Map/MapAdditionalOptions/BackgroundsSelector/BackgroundsSelector.component.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8aaf2bd11f3433c42bd3e9bdef76aa3101c4008d --- /dev/null +++ b/src/components/Map/MapAdditionalOptions/BackgroundsSelector/BackgroundsSelector.component.test.tsx @@ -0,0 +1,104 @@ +/* eslint-disable no-magic-numbers */ +import { StoreType } from '@/redux/store'; +import { render, screen } from '@testing-library/react'; +import { act } from 'react-dom/test-utils'; +import { initialMapDataFixture, initialMapStateFixture } from '@/redux/map/map.fixtures'; +import { + InitialStoreState, + getReduxWrapperWithStore, +} from '@/utils/testing/getReduxWrapperWithStore'; +import { + BACKGROUNDS_MOCK, + BACKGROUND_INITIAL_STATE_MOCK, +} from '@/redux/backgrounds/background.mock'; +import { BackgroundSelector } from './BackgroundsSelector.component'; + +const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => { + const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState); + + return ( + render( + <Wrapper> + <BackgroundSelector /> + </Wrapper>, + ), + { + store, + } + ); +}; + +describe('BackgroundSelector - component', () => { + it('should initialy display default value', () => { + renderComponent(); + + const buttonName = screen.getByTestId('background-dropdown-button-name'); + + expect(buttonName.textContent).toBe('Background'); + }); + + it("should display selected value name when it's not main background", async () => { + renderComponent({ + map: initialMapStateFixture, + backgrounds: { ...BACKGROUND_INITIAL_STATE_MOCK, data: BACKGROUNDS_MOCK }, + }); + + const buttonName = screen.getByTestId('background-dropdown-button-name'); + await act(() => { + buttonName.click(); + }); + expect(buttonName.textContent).toBe('Background'); + + const backgroundButton = screen.getByText('Network'); + await act(() => { + backgroundButton.click(); + }); + + expect(buttonName.textContent).toBe('Network'); + }); + + it('should change redux map state on selecting background', async () => { + const { store } = renderComponent({ + map: initialMapStateFixture, + backgrounds: { ...BACKGROUND_INITIAL_STATE_MOCK, data: BACKGROUNDS_MOCK }, + }); + expect(store.getState().map.data.modelId).toBe(0); + + const buttonName = screen.getByTestId('background-dropdown-button-name'); + await act(() => { + buttonName.click(); + }); + + const backgroundButton = screen.getByText('Network'); + await act(() => { + backgroundButton.click(); + }); + + expect(store.getState().map.data.backgroundId).toBe(14); + }); + + describe('query params', () => { + it('should display default value when main background id is in query params', async () => { + await renderComponent({ + map: { ...initialMapStateFixture, data: { ...initialMapDataFixture, backgroundId: 13 } }, + backgrounds: { ...BACKGROUND_INITIAL_STATE_MOCK, data: BACKGROUNDS_MOCK }, + }); + + const buttonName = screen.getByTestId('background-dropdown-button-name'); + expect(buttonName.textContent).toBe('Background'); + }); + it('should display correct background when background id is in query params', async () => { + await renderComponent({ + map: { ...initialMapStateFixture, data: { ...initialMapDataFixture, backgroundId: 15 } }, + backgrounds: { ...BACKGROUND_INITIAL_STATE_MOCK, data: BACKGROUNDS_MOCK }, + }); + + const buttonName = screen.getByTestId('background-dropdown-button-name'); + expect(buttonName.textContent).toBe('Empty'); + }); + it.skip('should set backgroundid in query on selecting background', () => { + // TODO e2e + expect(true).toBe(false); + }); + }); +}); diff --git a/src/components/Map/MapAdditionalOptions/BackgroundsSelector/BackgroundsSelector.component.tsx b/src/components/Map/MapAdditionalOptions/BackgroundsSelector/BackgroundsSelector.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ca0dd73392f972a5285211dba89fb71d1a87a102 --- /dev/null +++ b/src/components/Map/MapAdditionalOptions/BackgroundsSelector/BackgroundsSelector.component.tsx @@ -0,0 +1,86 @@ +import { useSelect } from 'downshift'; +import { + backgroundsDataSelector, + currentBackgroundSelector, + mainBackgroundIdSelector, +} from '@/redux/backgrounds/background.selectors'; +import { useAppSelector } from '@/redux/hooks/useAppSelector'; +import { twMerge } from 'tailwind-merge'; +import { Icon } from '@/shared/Icon'; +import { MapBackground } from '@/types/models'; +import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; +import { setMapBackground } from '@/redux/map/map.slice'; + +const DEFAULT_TOGGLE_BUTTON_TEXT = 'Background'; + +export const BackgroundSelector = (): JSX.Element => { + const selectedBackground = useAppSelector(currentBackgroundSelector); + const dispatch = useAppDispatch(); + const backgrounds = useAppSelector(backgroundsDataSelector); + const mainBackgroundId = useAppSelector(mainBackgroundIdSelector); + + const onItemSelect = (background: MapBackground | undefined | null): void => { + if (background) { + dispatch(setMapBackground(background.id)); + } + }; + + const { isOpen, getToggleButtonProps, getMenuProps, highlightedIndex, getItemProps } = useSelect({ + items: backgrounds || [], + selectedItem: selectedBackground, + onSelectedItemChange: ({ selectedItem: newSelectedItem }) => onItemSelect(newSelectedItem), + }); + + const getToggleButtonName = (): string => { + const isSelectedBackgroundMainBackground = selectedBackground?.id === mainBackgroundId; + if (!selectedBackground || isSelectedBackgroundMainBackground) { + return DEFAULT_TOGGLE_BUTTON_TEXT; + } + return selectedBackground.name; + }; + + return ( + <div + data-testid="background-selector" + className={twMerge('rounded-t bg-white text-xs shadow-primary', !isOpen && 'rounded-b')} + > + <div className={twMerge('flex w-72 flex-col rounded-t py-2 pl-4 pr-3')}> + <div + className="flex cursor-pointer flex-row items-center justify-between bg-white" + {...getToggleButtonProps()} + > + <span data-testid="background-dropdown-button-name" className="font-medium"> + {getToggleButtonName()} + </span> + <Icon + name="chevron-down" + className={twMerge('arrow-button h-6 w-6 fill-primary-500', isOpen && 'rotate-180')} + /> + </div> + </div> + <ul + className={`absolute z-10 max-h-80 w-72 overflow-scroll rounded-b bg-white p-0 ${ + !isOpen && 'hidden' + }`} + {...getMenuProps()} + > + {isOpen && + backgrounds && + backgrounds.map((item, index) => ( + <li + className={twMerge( + 'border-t', + highlightedIndex === index && 'text-primary-500', + selectedBackground === item && 'font-bold', + 'flex flex-col px-4 py-2 shadow-sm', + )} + key={item.id} + {...getItemProps({ item, index })} + > + <span>{item.name}</span> + </li> + ))} + </ul> + </div> + ); +}; diff --git a/src/components/Map/MapAdditionalOptions/BackgroundsSelector/index.ts b/src/components/Map/MapAdditionalOptions/BackgroundsSelector/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..0b379515e412b197f8e195c7e0b4e6044437fe21 --- /dev/null +++ b/src/components/Map/MapAdditionalOptions/BackgroundsSelector/index.ts @@ -0,0 +1 @@ +export { BackgroundSelector } from './BackgroundsSelector.component'; diff --git a/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.test.tsx b/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..1dfdf365a4f01fe8a1ef1ecba56dee2045c99d57 --- /dev/null +++ b/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.test.tsx @@ -0,0 +1,29 @@ +import { StoreType } from '@/redux/store'; +import { render, screen } from '@testing-library/react'; +import { + InitialStoreState, + getReduxWrapperWithStore, +} from '@/utils/testing/getReduxWrapperWithStore'; +import { MapAdditionalOptions } from './MapAdditionalOptions.component'; + +const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => { + const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState); + + return ( + render( + <Wrapper> + <MapAdditionalOptions /> + </Wrapper>, + ), + { + store, + } + ); +}; + +describe('MapAdditionalOptions - component', () => { + it('should display background selector', () => { + renderComponent(); + expect(screen.getByTestId('background-selector')).toBeInTheDocument(); + }); +}); diff --git a/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.tsx b/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a7845ac5ea8635064290ac1edfefdb289215b57e --- /dev/null +++ b/src/components/Map/MapAdditionalOptions/MapAdditionalOptions.component.tsx @@ -0,0 +1,10 @@ +import { twMerge } from 'tailwind-merge'; +import { BackgroundSelector } from './BackgroundsSelector'; + +// top-[calc(64px+40px+24px)] -> TOP_BAR_HEIGHT+MAP_NAVIGATION_HEIGHT+DISTANCE_FROM_MAP_NAVIGATION + +export const MapAdditionalOptions = (): JSX.Element => ( + <div className={twMerge('absolute right-6 top-[calc(64px+40px+24px)] z-10')}> + <BackgroundSelector /> + </div> +); diff --git a/src/components/Map/MapAdditionalOptions/index.ts b/src/components/Map/MapAdditionalOptions/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..ff61a27a2ae3155839070f31146f77fca1b8df2c --- /dev/null +++ b/src/components/Map/MapAdditionalOptions/index.ts @@ -0,0 +1 @@ +export { MapAdditionalOptions } from './MapAdditionalOptions.component'; diff --git a/src/components/Map/MapViewer/MapViewer.component.test.tsx b/src/components/Map/MapViewer/MapViewer.component.test.tsx index 49bc8cef1aeb20e57a2b200751e31b82828667d8..234854206f6a5310ca22b3009a0ddf11adfcbf92 100644 --- a/src/components/Map/MapViewer/MapViewer.component.test.tsx +++ b/src/components/Map/MapViewer/MapViewer.component.test.tsx @@ -1,13 +1,16 @@ -import mapReducer from '@/redux/map/map.slice'; -import { MapState } from '@/redux/map/map.types'; -import { ToolkitStoreWithSingleSlice } from '@/utils/createStoreInstanceUsingSliceReducer'; -import { getReduxWrapperUsingSliceReducer } from '@/utils/testing/getReduxWrapperUsingSliceReducer'; import { render, screen } from '@testing-library/react'; +import { StoreType } from '@/redux/store'; +import { initialMapStateFixture } from '@/redux/map/map.fixtures'; +import { BACKGROUND_INITIAL_STATE_MOCK } from '@/redux/backgrounds/background.mock'; +import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; import { MapViewer } from './MapViewer.component'; import { MAP_VIEWER_ROLE } from './MapViewer.constants'; -const renderComponent = (): { store: ToolkitStoreWithSingleSlice<MapState> } => { - const { Wrapper, store } = getReduxWrapperUsingSliceReducer('map', mapReducer); +const renderComponent = (): { store: StoreType } => { + const { Wrapper, store } = getReduxWrapperWithStore({ + map: initialMapStateFixture, + backgrounds: BACKGROUND_INITIAL_STATE_MOCK, + }); return ( render( diff --git a/src/components/Map/MapViewer/utils/config/useOlMapLayers.test.ts b/src/components/Map/MapViewer/utils/config/useOlMapLayers.test.ts index 1ed0fcc6485371ebd962bc179af3a3b8e80e90a2..ac543476c5fb61ca762409d68eb586bf6f564a05 100644 --- a/src/components/Map/MapViewer/utils/config/useOlMapLayers.test.ts +++ b/src/components/Map/MapViewer/utils/config/useOlMapLayers.test.ts @@ -1,14 +1,14 @@ /* eslint-disable no-magic-numbers */ import { MAP_DATA_INITIAL_STATE, OPENED_MAPS_INITIAL_STATE } from '@/redux/map/map.constants'; -import mapSlice from '@/redux/map/map.slice'; -import { getReduxWrapperUsingSliceReducer } from '@/utils/testing/getReduxWrapperUsingSliceReducer'; import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; +import { initialMapStateFixture } from '@/redux/map/map.fixtures'; +import { BACKGROUND_INITIAL_STATE_MOCK } from '@/redux/backgrounds/background.mock'; import { renderHook, waitFor } from '@testing-library/react'; import { Map } from 'ol'; import TileLayer from 'ol/layer/Tile'; import React from 'react'; -import { useOlMap } from '../useOlMap'; import { useOlMapLayers } from './useOlMapLayers'; +import { useOlMap } from '../useOlMap'; const useRefValue = { current: null, @@ -33,7 +33,11 @@ jest.spyOn(React, 'useRef').mockReturnValue(useRefValue); describe('useOlMapLayers - util', () => { it('should modify layers of the map instance on init', async () => { - const { Wrapper } = getReduxWrapperUsingSliceReducer('map', mapSlice); + const { Wrapper } = getReduxWrapperWithStore({ + map: initialMapStateFixture, + backgrounds: BACKGROUND_INITIAL_STATE_MOCK, + }); + const dummyElement = document.createElement('div'); const mapInstance = new Map({ target: dummyElement }); const setLayersSpy = jest.spyOn(mapInstance, 'setLayers'); diff --git a/src/components/Map/MapViewer/utils/config/useOlMapView.test.ts b/src/components/Map/MapViewer/utils/config/useOlMapView.test.ts index aa6137cbcc0be43f521ac40e5f3cf16c62f1dfff..21d779d60483c0faff83938e1e6c96cd9133a615 100644 --- a/src/components/Map/MapViewer/utils/config/useOlMapView.test.ts +++ b/src/components/Map/MapViewer/utils/config/useOlMapView.test.ts @@ -1,8 +1,12 @@ /* eslint-disable no-magic-numbers */ -import mapSlice, { setMapPosition } from '@/redux/map/map.slice'; -import { getReduxWrapperUsingSliceReducer } from '@/utils/testing/getReduxWrapperUsingSliceReducer'; +import { setMapPosition } from '@/redux/map/map.slice'; import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; -import { renderHook, waitFor } from '@testing-library/react'; +import { act, renderHook, waitFor } from '@testing-library/react'; +import { initialMapStateFixture } from '@/redux/map/map.fixtures'; +import { + BACKGROUNDS_MOCK, + BACKGROUND_INITIAL_STATE_MOCK, +} from '@/redux/backgrounds/background.mock'; import { MAP_DATA_INITIAL_STATE, OPENED_MAPS_INITIAL_STATE } from '@/redux/map/map.constants'; import { View } from 'ol'; import Map from 'ol/Map'; @@ -33,24 +37,31 @@ jest.spyOn(React, 'useRef').mockReturnValue(useRefValue); describe('useOlMapView - util', () => { it('should modify view of the map instance on INITIAL position config change', async () => { - const { Wrapper, store } = getReduxWrapperUsingSliceReducer('map', mapSlice); + const { Wrapper, store } = getReduxWrapperWithStore({ + map: initialMapStateFixture, + backgrounds: { ...BACKGROUND_INITIAL_STATE_MOCK, data: BACKGROUNDS_MOCK }, + }); + const dummyElement = document.createElement('div'); const { result: hohResult } = renderHook(() => useOlMap({ target: dummyElement }), { wrapper: Wrapper, }); + const setViewSpy = jest.spyOn(hohResult.current.mapInstance as Map, 'setView'); const CALLED_ONCE = 1; - store.dispatch( - setMapPosition({ - position: { - initial: { - x: 0, - y: 0, + await act(() => { + store.dispatch( + setMapPosition({ + position: { + initial: { + x: 0, + y: 0, + }, }, - }, - }), - ); + }), + ); + }); renderHook(() => useOlMapView({ mapInstance: hohResult.current.mapInstance }), { wrapper: Wrapper, @@ -89,6 +100,7 @@ describe('useOlMapView - util', () => { }, openedMaps: OPENED_MAPS_INITIAL_STATE, }, + backgrounds: BACKGROUND_INITIAL_STATE_MOCK, }); const dummyElement = document.createElement('div'); const { result: hohResult } = renderHook(() => useOlMap({ target: dummyElement }), { diff --git a/src/components/Map/MapViewer/utils/useOlMap.test.ts b/src/components/Map/MapViewer/utils/useOlMap.test.ts index 29cf1fc029a9296492288ab0304e851d49e8119c..79bc1ed1bb956a9ad94c51a635a721d6a1a93f3d 100644 --- a/src/components/Map/MapViewer/utils/useOlMap.test.ts +++ b/src/components/Map/MapViewer/utils/useOlMap.test.ts @@ -1,8 +1,9 @@ -import mapSlice from '@/redux/map/map.slice'; -import { getReduxWrapperUsingSliceReducer } from '@/utils/testing/getReduxWrapperUsingSliceReducer'; import { renderHook, waitFor } from '@testing-library/react'; import { Map } from 'ol'; import React from 'react'; +import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore'; +import { initialMapStateFixture } from '@/redux/map/map.fixtures'; +import { BACKGROUND_INITIAL_STATE_MOCK } from '@/redux/backgrounds/background.mock'; import { useOlMap } from './useOlMap'; const useRefValue = { @@ -27,7 +28,10 @@ Object.defineProperty(useRefValue, 'current', { jest.spyOn(React, 'useRef').mockReturnValue(useRefValue); describe('useOlMap - util', () => { - const { Wrapper } = getReduxWrapperUsingSliceReducer('map', mapSlice); + const { Wrapper } = getReduxWrapperWithStore({ + map: initialMapStateFixture, + backgrounds: BACKGROUND_INITIAL_STATE_MOCK, + }); describe('when initializing', () => { it('should set map instance', async () => { diff --git a/src/redux/backgrounds/background.selectors.ts b/src/redux/backgrounds/background.selectors.ts index 319bfc27376c4a099fe51ee0fee14b4a2e73348d..596301e1262dc5ed73303636d8fe7acd87da1c94 100644 --- a/src/redux/backgrounds/background.selectors.ts +++ b/src/redux/backgrounds/background.selectors.ts @@ -4,28 +4,32 @@ import { rootSelector } from '../root/root.selectors'; export const backgroundsSelector = createSelector(rootSelector, state => state.backgrounds); -export const backgroundsDataSelector = createSelector( - backgroundsSelector, - backgrounds => backgrounds?.data || [], -); +export const backgroundsDataSelector = createSelector(backgroundsSelector, backgrounds => { + return backgrounds.data; +}); const MAIN_BACKGROUND = 0; export const mainBackgroundsDataSelector = createSelector( backgroundsDataSelector, - backgrounds => backgrounds[MAIN_BACKGROUND], + backgrounds => backgrounds && backgrounds[MAIN_BACKGROUND], +); +export const mainBackgroundIdSelector = createSelector( + backgroundsDataSelector, + backgrounds => backgrounds && backgrounds[MAIN_BACKGROUND]?.id, ); export const currentBackgroundSelector = createSelector( backgroundsDataSelector, mapDataSelector, - (backgrounds, mapData) => backgrounds.find(background => background.id === mapData.backgroundId), + (backgrounds, mapData) => + backgrounds && backgrounds.find(background => background.id === mapData.backgroundId), ); export const currentBackgroundImageSelector = createSelector( mapDataSelector, currentBackgroundSelector, (mapData, background) => - background ? background.images.find(image => image.model.id === mapData.modelId) : undefined, + background && background.images.find(image => image.model.id === mapData.modelId), ); export const currentBackgroundImagePathSelector = createSelector( diff --git a/src/redux/backgrounds/backgrounds.types.ts b/src/redux/backgrounds/backgrounds.types.ts index 5457757cb6dfddd22da156c6f6ba400ac9480647..7ce8fcfc7643e9ea526d79895928037fd19d27b8 100644 --- a/src/redux/backgrounds/backgrounds.types.ts +++ b/src/redux/backgrounds/backgrounds.types.ts @@ -1,4 +1,4 @@ import { FetchDataState } from '@/types/fetchDataState'; import { MapBackground } from '@/types/models'; -export type BackgroundsState = FetchDataState<MapBackground[] | []>; +export type BackgroundsState = FetchDataState<MapBackground[]>; diff --git a/src/redux/map/map.reducers.ts b/src/redux/map/map.reducers.ts index b0ad81bf535fa81d7f6dc1678a8b2b6d7eaeee06..ae325fb9127f901719094ef2883ce029581aede1 100644 --- a/src/redux/map/map.reducers.ts +++ b/src/redux/map/map.reducers.ts @@ -5,6 +5,7 @@ import { MapState, OpenMapAndSetActiveAction, SetActiveMapAction, + SetBackgroundAction, SetMapDataAction, SetMapPositionDataAction, } from './map.types'; @@ -84,6 +85,10 @@ export const closeMapAndSetMainMapActiveReducer = ( state.openedMaps.find(openedMap => openedMap.modelName === MAIN_MAP)?.modelId || ZERO; }; +export const setMapBackgroundReducer = (state: MapState, action: SetBackgroundAction): void => { + state.data.backgroundId = action.payload; +}; + export const initMapSizeAndModelIdReducer = (builder: ActionReducerMapBuilder<MapState>): void => { builder.addCase(initMapSizeAndModelId.fulfilled, (state, action) => { state.data.modelId = action.payload.modelId; diff --git a/src/redux/map/map.slice.ts b/src/redux/map/map.slice.ts index ca51213ca667fc5087cec6f45d988c61f508d8dc..73cbb92f2e7887f7674dd7260e8e895b45de24bb 100644 --- a/src/redux/map/map.slice.ts +++ b/src/redux/map/map.slice.ts @@ -11,6 +11,7 @@ import { initOpenedMapsReducer, initMapSizeAndModelIdReducer, initMapBackgroundsReducer, + setMapBackgroundReducer, } from './map.reducers'; import { MapState } from './map.types'; @@ -31,6 +32,7 @@ const mapSlice = createSlice({ closeMap: closeMapReducer, closeMapAndSetMainMapActive: closeMapAndSetMainMapActiveReducer, setMapPosition: setMapPositionReducer, + setMapBackground: setMapBackgroundReducer, }, extraReducers: builder => { initMapPositionReducers(builder); @@ -47,6 +49,7 @@ export const { closeMap, closeMapAndSetMainMapActive, setMapPosition, + setMapBackground, } = mapSlice.actions; export default mapSlice.reducer; diff --git a/src/redux/map/map.types.ts b/src/redux/map/map.types.ts index bd641cd7708a0116fe2a8ba12741733b4a9ac1f8..9ed2e9129f3906fbce9393b8525a1a289827377c 100644 --- a/src/redux/map/map.types.ts +++ b/src/redux/map/map.types.ts @@ -113,3 +113,6 @@ export type InitMapBackgroundActionPayload = number; export type InitMapBackgroundParams = { queryData: QueryData; }; + +export type SetBackgroundActionPayload = number; +export type SetBackgroundAction = PayloadAction<SetBackgroundActionPayload>; diff --git a/src/redux/map/middleware/map.middleware.ts b/src/redux/map/middleware/map.middleware.ts index 5658c14519d6d650b3c70b52abf90f2252fd1219..8083f40e940043bc116ec8cdcaca7f1bbeb233cc 100644 --- a/src/redux/map/middleware/map.middleware.ts +++ b/src/redux/map/middleware/map.middleware.ts @@ -8,6 +8,7 @@ import { setMapData, setMapPosition, closeMapAndSetMainMapActive, + setMapBackground, } from '../map.slice'; import { checkIfIsMapUpdateActionValid } from './checkIfIsMapUpdateActionValid'; import { getUpdatedModel } from './getUpdatedModel'; @@ -42,6 +43,12 @@ export const mapDataMiddlewareListener = async ( }; startListening({ - matcher: isAnyOf(setMapData, setActiveMap, openMapAndSetActive, closeMapAndSetMainMapActive), + matcher: isAnyOf( + setMapData, + setActiveMap, + openMapAndSetActive, + closeMapAndSetMainMapActive, + setMapBackground, + ), effect: mapDataMiddlewareListener, }); diff --git a/tailwind.config.ts b/tailwind.config.ts index f63ef9478c9e0f39486bd3b7dacd9ef45f17e89c..62c3f4f71cfba9d10d68444821d10e04a1366db3 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -33,7 +33,7 @@ const config: Config = { 'calc-drawer': 'calc(100% - 104px)', }, boxShadow: { - 'map-navigation-bar': '4px 8px 32px 0px rgba(0, 0, 0, 0.12)', + primary: '4px 8px 32px 0px rgba(0, 0, 0, 0.12)', }, }, fontFamily: { diff --git a/yarn.lock b/yarn.lock index 54e2c71b6b3f6141ad2346981e051763c0a1c80e..ef5cdfbf8c29f725a8559cf632cfd1d62d180e80 100644 --- a/yarn.lock +++ b/yarn.lock @@ -273,7 +273,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.20.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.20.7", "@babel/runtime@^7.22.15", "@babel/runtime@^7.9.2": "integrity" "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==" "resolved" "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.1.tgz" "version" "7.23.1" @@ -2219,6 +2219,11 @@ "array-ify" "^1.0.0" "dot-prop" "^5.1.0" +"compute-scroll-into-view@^3.0.3": + "integrity" "sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==" + "resolved" "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz" + "version" "3.1.0" + "concat-map@0.0.1": "integrity" "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" "resolved" "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" @@ -2657,6 +2662,17 @@ dependencies: "is-obj" "^2.0.0" +"downshift@^8.2.3": + "integrity" "sha512-1HkvqaMTZpk24aqnXaRDnT+N5JCbpFpW+dCogB11+x+FCtfkFX0MbAO4vr/JdXi1VYQF174KjNUveBXqaXTPtg==" + "resolved" "https://registry.npmjs.org/downshift/-/downshift-8.2.3.tgz" + "version" "8.2.3" + dependencies: + "@babel/runtime" "^7.22.15" + "compute-scroll-into-view" "^3.0.3" + "prop-types" "^15.8.1" + "react-is" "^18.2.0" + "tslib" "^2.6.2" + "drange@^1.0.2": "integrity" "sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==" "resolved" "https://registry.npmjs.org/drange/-/drange-1.1.1.tgz" @@ -6040,6 +6056,11 @@ "resolved" "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz" "version" "18.2.0" +"react-is@^18.2.0": + "integrity" "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "resolved" "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz" + "version" "18.2.0" + "react-redux@^7.2.1 || ^8.0.2", "react-redux@^8.1.2": "integrity" "sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==" "resolved" "https://registry.npmjs.org/react-redux/-/react-redux-8.1.3.tgz" @@ -6052,7 +6073,7 @@ "react-is" "^18.0.0" "use-sync-external-store" "^1.0.0" -"react@^16.3.2 || ^17.0.0 || ^18.0.0", "react@^16.8 || ^17.0 || ^18.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0-0 || ^17.0.0 || ^18.0.0", "react@^16.9.0 || ^17.0.0 || ^18", "react@^18.0.0", "react@^18.2.0", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", "react@>=16.8.0", "react@>=17.0.0", "react@18.2.0": +"react@^16.3.2 || ^17.0.0 || ^18.0.0", "react@^16.8 || ^17.0 || ^18.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0-0 || ^17.0.0 || ^18.0.0", "react@^16.9.0 || ^17.0.0 || ^18", "react@^18.0.0", "react@^18.2.0", "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", "react@>=16.12.0", "react@>=16.8.0", "react@>=17.0.0", "react@18.2.0": "integrity" "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==" "resolved" "https://registry.npmjs.org/react/-/react-18.2.0.tgz" "version" "18.2.0" @@ -6948,7 +6969,7 @@ "resolved" "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" "version" "1.14.1" -"tslib@^2.1.0", "tslib@^2.4.0", "tslib@^2.5.0", "tslib@^2.6.0": +"tslib@^2.1.0", "tslib@^2.4.0", "tslib@^2.5.0", "tslib@^2.6.0", "tslib@^2.6.2": "integrity" "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" "resolved" "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz" "version" "2.6.2"