Skip to content
Snippets Groups Projects
Commit 8292e62c authored by Tadeusz Miesiąc's avatar Tadeusz Miesiąc
Browse files

feat(submaps): saving position on tab switch

parent 2ac4e50f
No related branches found
No related tags found
2 merge requests!223reset the pin numbers before search results are fetch (so the results will be...,!47feat(submaps tabs): open submap on click
Pipeline #81126 passed
......@@ -12,11 +12,13 @@ export const MAIN_MAP = 'Main map';
export const MODEL_ID_DEFAULT: number = 0;
export const BACKGROUND_ID_DEFAULT: number = 0;
export const MAP_DATA_INITIAL_STATE: MapData = {
projectId: PROJECT_ID,
meshId: '',
modelId: MODEL_ID_DEFAULT,
backgroundId: 0,
backgroundId: BACKGROUND_ID_DEFAULT,
overlaysIds: [],
position: {
last: DEFAULT_CENTER_POINT,
......
......@@ -35,22 +35,16 @@ export const setMapPositionReducer = (state: MapState, action: SetMapPositionDat
};
};
export const getMapReducers = (builder: ActionReducerMapBuilder<MapState>): void => {
builder.addCase(initMapData.pending, state => {
state.loading = 'pending';
});
builder.addCase(initMapData.fulfilled, (state, action) => {
const payload = action.payload || {};
state.data = { ...state.data, ...payload };
state.loading = 'succeeded';
});
builder.addCase(initMapData.rejected, state => {
state.loading = 'failed';
// TODO to discuss manage state of failure
});
const updateLastPositionOfCurrentlyActiveMap = (state: MapState): void => {
const currentMapId = state.data.modelId;
const currentOpenedMap = state.openedMaps.find(openedMap => openedMap.modelId === currentMapId);
if (currentOpenedMap) {
currentOpenedMap.lastPosition = state.data.position.last;
}
};
export const setActiveMapReducer = (state: MapState, action: SetActiveMapAction): void => {
updateLastPositionOfCurrentlyActiveMap(state);
state.data.modelId = action.payload.modelId;
};
......@@ -58,6 +52,8 @@ export const openMapAndSetActiveReducer = (
state: MapState,
action: OpenMapAndSetActiveAction,
): void => {
updateLastPositionOfCurrentlyActiveMap(state);
state.openedMaps.push({
modelId: action.payload.modelId,
modelName: action.payload.modelName,
......@@ -83,7 +79,23 @@ export const closeMapAndSetMainMapActiveReducer = (
state.openedMaps.find(openedMap => openedMap.modelName === MAIN_MAP)?.modelId || ZERO;
};
export const getMapPositionReducers = (builder: ActionReducerMapBuilder<MapState>): void => {
export const getMapReducers = (builder: ActionReducerMapBuilder<MapState>): void => {
builder.addCase(initMapData.pending, state => {
state.loading = 'pending';
});
builder.addCase(initMapData.fulfilled, (state, action) => {
const payload = action.payload || {};
state.data = { ...state.data, ...payload.data };
state.openedMaps = payload.openedMaps;
state.loading = 'succeeded';
});
builder.addCase(initMapData.rejected, state => {
state.loading = 'failed';
// TODO to discuss manage state of failure
});
};
export const initMapPositionReducers = (builder: ActionReducerMapBuilder<MapState>): void => {
builder.addCase(initMapPosition.pending, state => {
state.loading = 'pending';
});
......
......@@ -17,6 +17,12 @@ export const mapDataInitialPositionSelector = createSelector(
position => position.initial,
);
export const mapOpenedMapPositionByIdSelector = createSelector(
[mapOpenedMapsSelector, (_state, modelId: number): number => modelId],
(openedMaps, modelId) =>
openedMaps.find(openedMap => openedMap.modelId === modelId)?.lastPosition,
);
export const mapDataLastPositionSelector = createSelector(
mapDataPositionSelector,
position => position.last,
......
......@@ -7,7 +7,7 @@ import {
openMapAndSetActiveReducer,
setActiveMapReducer,
setMapDataReducer,
getMapPositionReducers,
initMapPositionReducers,
setMapPositionReducer,
} from './map.reducers';
import { MapState } from './map.types';
......@@ -32,7 +32,7 @@ const mapSlice = createSlice({
},
extraReducers: builder => {
getMapReducers(builder);
getMapPositionReducers(builder);
initMapPositionReducers(builder);
},
});
......
......@@ -61,10 +61,29 @@ describe('map thunks', () => {
it('should return valid payload', () => {
const FIRST = 0;
expect(payload).toMatchObject({
modelId: modelsFixture[FIRST].idObject,
backgroundId: backgroundsFixture[FIRST].id,
data: {
modelId: modelsFixture[FIRST].idObject,
backgroundId: backgroundsFixture[FIRST].id,
size: {
width: 66.1207745783031,
height: -54.25165700726211,
tileSize: 85.73858779855072,
minZoom: 19.16961562819779,
maxZoom: 78.78634324297309,
},
position: {
initial: { x: -47.612417908385396, y: -27.125828503631055, z: -97.42596028372645 },
last: { x: -47.612417908385396, y: -27.125828503631055, z: -97.42596028372645 },
},
},
openedMaps: [
{
modelId: 63.59699326567352,
modelName: 'Main map',
lastPosition: { x: 0, y: 0, z: 0 },
},
],
});
});
});
......@@ -88,8 +107,11 @@ describe('map thunks', () => {
.payload as InitMapDataActionPayload;
});
it('should return empty payload', () => {
expect(payload).toStrictEqual({});
it('should return empty values for data and openedMaps in payload', () => {
expect(payload).toStrictEqual({
data: {},
openedMaps: [{ modelId: 0, modelName: 'Main map', lastPosition: { x: 0, y: 0, z: 0 } }],
});
});
});
});
......
/* eslint-disable no-magic-numbers */
import { PROJECT_ID } from '@/constants';
import { QueryData } from '@/types/query';
import { GetUpdatedMapDataResult, getUpdatedMapData } from '@/utils/map/getUpdatedMapData';
import { ZERO } from '@/constants/common';
import { getUpdatedMapData } from '@/utils/map/getUpdatedMapData';
import { createAsyncThunk } from '@reduxjs/toolkit';
import { backgroundsDataSelector } from '../backgrounds/background.selectors';
import { getAllBackgroundsByProjectId } from '../backgrounds/backgrounds.thunks';
......@@ -10,10 +11,13 @@ import { getModels } from '../models/models.thunks';
import { getAllPublicOverlaysByProjectId } from '../overlays/overlays.thunks';
import type { AppDispatch, RootState } from '../store';
import {
GetUpdatedMapDataResult,
InitMapDataActionParams,
InitMapDataActionPayload,
OppenedMap,
SetMapPositionDataActionPayload,
} from './map.types';
import { DEFAULT_POSITION, MAIN_MAP } from './map.constants';
const getInitMapDataPayload = (
state: RootState,
......@@ -22,7 +26,7 @@ const getInitMapDataPayload = (
const FIRST = 0;
const models = modelsDataSelector(state);
const backgrounds = backgroundsDataSelector(state);
const modelId = queryData?.modelId || models?.[FIRST]?.idObject;
const modelId = queryData?.modelId || models?.[FIRST]?.idObject || ZERO; // TS does not get the type correctly. It might be undefined so fallback to 0 is needed
const backgroundId = queryData?.backgroundId || backgrounds?.[FIRST]?.id;
const model = models.find(({ idObject }) => idObject === modelId);
const background = backgrounds.find(({ id }) => id === backgroundId);
......@@ -42,6 +46,18 @@ const getInitMapDataPayload = (
});
};
const getUpdatedOpenedMapWithMainMap = (state: RootState): OppenedMap[] => {
const FIRST = 0;
const models = modelsDataSelector(state);
const mainMapId = models?.[FIRST]?.idObject || ZERO;
const openedMaps: OppenedMap[] = [
{ modelId: mainMapId, modelName: MAIN_MAP, lastPosition: DEFAULT_POSITION },
];
return openedMaps;
};
export const initMapData = createAsyncThunk<
InitMapDataActionPayload,
InitMapDataActionParams,
......@@ -56,18 +72,21 @@ export const initMapData = createAsyncThunk<
]);
const state = getState();
return getInitMapDataPayload(state, queryData);
const mapDataPayload = getInitMapDataPayload(state, queryData);
const openedMapsPayload = getUpdatedOpenedMapWithMainMap(state);
return { data: mapDataPayload, openedMaps: openedMapsPayload };
},
);
export const initMapPosition = createAsyncThunk<
InitMapDataActionPayload,
SetMapPositionDataActionPayload,
InitMapDataActionParams,
{ dispatch: AppDispatch; state: RootState }
>(
'map/initMapPosition',
async ({ queryData }, { getState }): Promise<SetMapPositionDataActionPayload> => {
async ({ queryData }, { getState }): Promise<GetUpdatedMapDataResult | object> => {
const state = getState();
return getInitMapDataPayload(state, queryData);
const mapDataPayload = getInitMapDataPayload(state, queryData);
return mapDataPayload;
},
);
......@@ -42,6 +42,10 @@ export type SetMapDataActionPayload =
})
| undefined;
export type UpdateOpenedMainMapActionPayload = Pick<OppenedMap, 'modelId' | 'lastPosition'>;
export type UpdateOpenedMainMapAction = PayloadAction<UpdateOpenedMainMapActionPayload>;
export type SetMapDataAction = PayloadAction<SetMapDataActionPayload>;
export type SetActiveMapActionPayload = Pick<OppenedMap, 'modelId'>;
......@@ -60,14 +64,8 @@ export type CloseMapAction = PayloadAction<CloseMapActionPayload>;
export type InitMapDataActionParams = { queryData: QueryData };
export type InitMapDataActionPayload = SetMapDataActionPayload | object;
export type InitMapDataAction = PayloadAction<SetMapDataAction>;
export type MiddlewareAllowedAction = PayloadAction<
SetMapDataActionPayload | InitMapDataActionPayload
>;
export type SetMapDataByQueryDataActionParams = { queryData: QueryData };
export type SetMapDataByQueryDataActionPayload = Pick<
......@@ -75,6 +73,19 @@ export type SetMapDataByQueryDataActionPayload = Pick<
'modelId' | 'backgroundId' | 'position'
>;
export type SetMapPositionDataActionPayload = Pick<MapData, 'position'> | object;
export type GetUpdatedMapDataResult = Pick<
MapData,
'modelId' | 'backgroundId' | 'size' | 'position'
>;
export type SetMapPositionDataActionPayload = GetUpdatedMapDataResult | object;
export type SetMapPositionDataAction = PayloadAction<SetMapPositionDataActionPayload>;
export type InitMapDataActionPayload = {
data: GetUpdatedMapDataResult | object;
openedMaps: OppenedMap[];
};
export type MiddlewareAllowedAction = PayloadAction<
SetMapDataActionPayload | InitMapDataActionPayload
>;
import { currentBackgroundSelector } from '@/redux/backgrounds/background.selectors';
import type { AppDispatch, AppListenerEffectAPI, AppStartListening } from '@/redux/store';
import { GetUpdatedMapDataResult, getUpdatedMapData } from '@/utils/map/getUpdatedMapData';
import type { AppListenerEffectAPI, AppStartListening } from '@/redux/store';
import { getUpdatedMapData } from '@/utils/map/getUpdatedMapData';
import { Action, createListenerMiddleware, isAnyOf } from '@reduxjs/toolkit';
import { setMapData, setMapPosition } from '../map.slice';
import {
openMapAndSetActive,
setActiveMap,
setMapData,
setMapPosition,
closeMapAndSetMainMapActive,
} from '../map.slice';
import { checkIfIsMapUpdateActionValid } from './checkIfIsMapUpdateActionValid';
import { getUpdatedModel } from './getUpdatedModel';
import { mapOpenedMapPositionByIdSelector } from '../map.selectors';
export const mapListenerMiddleware = createListenerMiddleware();
const startListening = mapListenerMiddleware.startListening as AppStartListening;
/* prettier-ignore */
export const dispatchMapDataWithPosition =
(updatedMapData: GetUpdatedMapDataResult) =>
(dispatch: AppDispatch): void => {
dispatch(setMapData(updatedMapData));
dispatch(setMapPosition(updatedMapData));
};
export const mapDataMiddlewareListener = async (
action: Action,
{ getOriginalState, dispatch }: AppListenerEffectAPI,
......@@ -31,12 +30,18 @@ export const mapDataMiddlewareListener = async (
}
const background = currentBackgroundSelector(state);
const updatedMapData = getUpdatedMapData({ model: updatedModel, background });
const modelId = updatedModel.idObject;
const lastPosition = mapOpenedMapPositionByIdSelector(state, modelId);
const updatedMapData = getUpdatedMapData({
model: updatedModel,
position: { initial: lastPosition, last: lastPosition },
background,
});
dispatch(setMapData(updatedMapData));
dispatch(setMapPosition(updatedMapData));
};
startListening({
matcher: isAnyOf(setMapData),
matcher: isAnyOf(setMapData, setActiveMap, openMapAndSetActive, closeMapAndSetMainMapActive),
effect: mapDataMiddlewareListener,
});
......@@ -19,6 +19,7 @@ export const getInitStoreData =
({ queryData }: GetInitStoreDataArgs) =>
(dispatch: AppDispatch): void => {
dispatch(getProjectById(PROJECT_ID));
// when app loads
dispatch(initMapData({ queryData }));
dispatch(initMapPosition({ queryData }));
};
......
import { DEFAULT_ZOOM } from '@/constants/map';
import { MAP_DATA_INITIAL_STATE } from '@/redux/map/map.constants';
import {
MapData,
SetMapDataActionPayload,
SetMapPositionDataActionPayload,
} from '@/redux/map/map.types';
import { GetUpdatedMapDataResult, MapData } from '@/redux/map/map.types';
import { MapBackground, MapModel } from '@/types/models';
import { DeepPartial } from '@reduxjs/toolkit';
import { getPointMerged } from '../object/getPointMerged';
......@@ -15,8 +11,6 @@ interface GetUpdatedMapDataArgs {
background?: MapBackground;
}
export type GetUpdatedMapDataResult = SetMapDataActionPayload & SetMapPositionDataActionPayload;
const HALF = 2;
export const getUpdatedMapData = ({
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment