Skip to content

Commit 4b0a1e1

Browse files
jacomyalsim51
authored andcommitted
editor: flushes editor state on infra change
This commit is related to #1986. It fixes an issue raised by @sim51 in a comment to PR #2069: > When I have a selection and I switch to an other infrastructure, the selection is still there with the data of the previous infra Details: - Groups activeTool and toolState in a single useState call in Editor.tsx internal logic, to prevent some invalid app state - Adds a new `reset` editor action to reset editor state (except for schema, that never changed for now) - Flushes editor and tool state (and active tool, actually) when the infra changes
1 parent f7c77b2 commit 4b0a1e1

File tree

4 files changed

+96
-34
lines changed

4 files changed

+96
-34
lines changed

front/src/applications/editor/Editor.tsx

+65-31
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import './Editor.scss';
1111

1212
import { LoaderState } from '../../common/Loader';
1313
import { NotificationsState } from '../../common/Notifications';
14-
import { EditorState, loadDataModel } from '../../reducers/editor';
14+
import { EditorState, loadDataModel, reset } from '../../reducers/editor';
1515
import { MainState, setFailure } from '../../reducers/main';
1616
import { updateViewport } from '../../reducers/map';
1717
import { updateInfraID } from '../../reducers/osrdconf';
@@ -24,6 +24,7 @@ import {
2424
CommonToolState,
2525
EditorContextType,
2626
ExtendedEditorContextType,
27+
FullTool,
2728
ModalRequest,
2829
OSRDConf,
2930
Tool,
@@ -36,8 +37,10 @@ const EditorUnplugged: FC<{ t: TFunction }> = ({ t }) => {
3637
const editorState = useSelector((state: { editor: EditorState }) => state.editor);
3738
const { fullscreen } = useSelector((state: { main: MainState }) => state.main);
3839
/* eslint-disable @typescript-eslint/no-explicit-any */
39-
const [activeTool, activateTool] = useState<Tool<any>>(TOOLS[0]);
40-
const [toolState, setToolState] = useState<any>(activeTool.getInitialState({ osrdConf }));
40+
const [toolAndState, setToolAndState] = useState<FullTool<any>>({
41+
tool: TOOLS[0],
42+
state: TOOLS[0].getInitialState({ osrdConf }),
43+
});
4144
const [modal, setModal] = useState<ModalRequest<any, any> | null>(null);
4245
/* eslint-enable @typescript-eslint/no-explicit-any */
4346
const openModal = useCallback(
@@ -48,6 +51,29 @@ const EditorUnplugged: FC<{ t: TFunction }> = ({ t }) => {
4851
},
4952
[setModal]
5053
);
54+
const switchTool = useCallback(
55+
<S extends CommonToolState>(tool: Tool<S>, partialState?: Partial<S>) => {
56+
const state = { ...tool.getInitialState({ osrdConf }), ...(partialState || {}) };
57+
setToolAndState({
58+
tool,
59+
state,
60+
});
61+
},
62+
[osrdConf, setToolAndState]
63+
);
64+
const setToolState = useCallback(
65+
<S extends CommonToolState>(state?: S) => {
66+
setToolAndState((s) => ({
67+
...s,
68+
state,
69+
}));
70+
},
71+
[setToolAndState]
72+
);
73+
const resetState = useCallback(() => {
74+
switchTool(TOOLS[0]);
75+
dispatch(reset());
76+
}, []);
5177

5278
const { infra } = useParams<{ infra?: string }>();
5379
const { mapStyle, viewport } = useSelector(
@@ -68,16 +94,12 @@ const EditorUnplugged: FC<{ t: TFunction }> = ({ t }) => {
6894
closeModal: () => {
6995
setModal(null);
7096
},
71-
activeTool,
72-
state: toolState,
97+
activeTool: toolAndState.tool,
98+
state: toolAndState.state,
7399
setState: setToolState,
74-
switchTool<S extends CommonToolState>(tool: Tool<S>, state?: Partial<S>) {
75-
const fullState = { ...tool.getInitialState({ osrdConf }), ...(state || {}) };
76-
activateTool(tool);
77-
setToolState(fullState);
78-
},
100+
switchTool,
79101
}),
80-
[activeTool, modal, osrdConf, t, toolState]
102+
[toolAndState, modal, osrdConf, t]
81103
);
82104
const extendedContext = useMemo<ExtendedEditorContextType<CommonToolState>>(
83105
() => ({
@@ -93,9 +115,15 @@ const EditorUnplugged: FC<{ t: TFunction }> = ({ t }) => {
93115
[context, dispatch, editorState, mapStyle, osrdConf, viewport]
94116
);
95117

96-
const actionsGroups = activeTool.actions
97-
.map((group) => group.filter((action) => !action.isHidden || !action.isHidden(extendedContext)))
98-
.filter((group) => group.length);
118+
const actionsGroups = useMemo(
119+
() =>
120+
toolAndState.tool.actions
121+
.map((group) =>
122+
group.filter((action) => !action.isHidden || !action.isHidden(extendedContext))
123+
)
124+
.filter((group) => group.length),
125+
[toolAndState.tool]
126+
);
99127

100128
// Initial viewport:
101129
useEffect(() => {
@@ -109,20 +137,23 @@ const EditorUnplugged: FC<{ t: TFunction }> = ({ t }) => {
109137
useEffect(() => {
110138
if (infra && parseInt(infra, 10) > 0) {
111139
getInfrastructure(parseInt(infra, 10))
112-
.then((infrastructure) => dispatch(updateInfraID(infrastructure.id)))
140+
.then((infrastructure) => {
141+
dispatch(updateInfraID(infrastructure.id));
142+
resetState();
143+
})
113144
.catch(() => {
114145
dispatch(setFailure(new Error(t('Editor.errors.infra-not-found', { id: infra }))));
115-
dispatch(updateViewport({}, `/editor/`));
146+
dispatch(updateViewport({}, `/editor/`, false));
116147
});
117148
} else if (osrdConf.infraID) {
118-
dispatch(updateViewport({}, `/editor/${osrdConf.infraID}`));
149+
dispatch(updateViewport({}, `/editor/${osrdConf.infraID}`, false));
119150
} else {
120151
getInfrastructures()
121152
.then((infras) => {
122153
if (infras && infras.length > 0) {
123154
const infrastructure = infras[0];
124155
dispatch(updateInfraID(infrastructure.id));
125-
dispatch(updateViewport({}, `/editor/${infrastructure.id}`));
156+
dispatch(updateViewport({}, `/editor/${infrastructure.id}`, false));
126157
} else {
127158
dispatch(setFailure(new Error(t('Editor.errors.no-infra-available'))));
128159
}
@@ -135,13 +166,13 @@ const EditorUnplugged: FC<{ t: TFunction }> = ({ t }) => {
135166

136167
// Lifecycle events on tools:
137168
useEffect(() => {
138-
if (activeTool.onMount) activeTool.onMount(extendedContext);
169+
if (toolAndState.tool.onMount) toolAndState.tool.onMount(extendedContext);
139170

140171
return () => {
141-
if (activeTool.onUnmount) activeTool.onUnmount(extendedContext);
172+
if (toolAndState.tool.onUnmount) toolAndState.tool.onUnmount(extendedContext);
142173
};
143174
// eslint-disable-next-line react-hooks/exhaustive-deps
144-
}, [activeTool]);
175+
}, [toolAndState.tool]);
145176

146177
return (
147178
<EditorContext.Provider value={extendedContext as EditorContextType<unknown>}>
@@ -158,10 +189,13 @@ const EditorUnplugged: FC<{ t: TFunction }> = ({ t }) => {
158189
<Tipped key={id} mode="right">
159190
<button
160191
type="button"
161-
className={cx('btn-rounded', id === activeTool.id && 'active', 'editor-btn')}
192+
className={cx(
193+
'btn-rounded',
194+
id === toolAndState.tool.id && 'active',
195+
'editor-btn'
196+
)}
162197
onClick={() => {
163-
activateTool(tool);
164-
setToolState(tool.getInitialState({ osrdConf }));
198+
switchTool(tool);
165199
}}
166200
disabled={isDisabled && isDisabled(extendedContext)}
167201
>
@@ -216,21 +250,21 @@ const EditorUnplugged: FC<{ t: TFunction }> = ({ t }) => {
216250
: actions;
217251
})}
218252
</div>
219-
{activeTool.leftPanelComponent && (
253+
{toolAndState.tool.leftPanelComponent && (
220254
<div className="panel-box">
221-
<activeTool.leftPanelComponent />
255+
<toolAndState.tool.leftPanelComponent />
222256
</div>
223257
)}
224258
<div className="map-wrapper">
225259
<div className="map">
226260
<Map
227261
{...{
228-
toolState,
229-
setToolState,
262+
mapStyle,
230263
viewport,
231264
setViewport,
232-
activeTool,
233-
mapStyle,
265+
toolState: toolAndState.state,
266+
activeTool: toolAndState.tool,
267+
setToolState,
234268
}}
235269
/>
236270

@@ -279,7 +313,7 @@ const EditorUnplugged: FC<{ t: TFunction }> = ({ t }) => {
279313
</div>
280314
</div>
281315
<div className="messages-bar">
282-
{activeTool.messagesComponent && <activeTool.messagesComponent />}
316+
{toolAndState.tool.messagesComponent && <toolAndState.tool.messagesComponent />}
283317
</div>
284318
</div>
285319
</div>

front/src/applications/editor/components/InfraSelectionModal.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@ import React, { FC, useEffect, useState } from 'react';
22
import { useTranslation } from 'react-i18next';
33
import { useDispatch } from 'react-redux';
44
import { datetime2string } from 'utils/timeManipulation';
5+
import { useNavigate } from 'react-router';
56

67
import { ModalProps } from '../tools/types';
78
import Modal from './Modal';
89
import { get } from '../../../common/requests';
910
import { addNotification, setFailure } from '../../../reducers/main';
10-
import history from '../../../main/history';
11-
import { useNavigate } from 'react-router';
1211

1312
const infraURL = '/infra/';
1413
type InfrasList = {

front/src/applications/editor/tools/types.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,8 @@ export interface Tool<S> {
122122
leftPanelComponent?: ComponentType;
123123
messagesComponent?: ComponentType;
124124
}
125+
126+
export type FullTool<S> = {
127+
tool: Tool<S>;
128+
state: S;
129+
};

front/src/reducers/editor.ts

+25-1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,18 @@ export function selectZone(zone: ActionSelectZone['zone']): ThunkAction<ActionSe
5858
};
5959
}
6060

61+
const RESET = 'editor/RESET';
62+
type ActionReset = {
63+
type: typeof RESET;
64+
};
65+
export function reset(): ThunkAction<ActionReset> {
66+
return (dispatch) => {
67+
dispatch({
68+
type: RESET,
69+
});
70+
};
71+
}
72+
6173
//
6274
// Verify if the data model definition is already loaded.
6375
// If not we do it and store it in the state
@@ -127,7 +139,12 @@ export function save(operations: {
127139
};
128140
}
129141

130-
export type EditorActions = ActionSelectZone | ActionLoadDataModel | ActionSave | ActionSetData;
142+
export type EditorActions =
143+
| ActionSelectZone
144+
| ActionLoadDataModel
145+
| ActionSave
146+
| ActionSetData
147+
| ActionReset;
131148

132149
//
133150
// State definition
@@ -170,6 +187,13 @@ export default function reducer(inputState: EditorState | undefined, action: Edi
170187
draft.editorData = action.data;
171188
draft.editorDataIndex = keyBy(action.data, 'id');
172189
break;
190+
case RESET:
191+
draft.editorLayers = initialState.editorLayers;
192+
draft.editorZone = initialState.editorZone;
193+
draft.editorData = initialState.editorData;
194+
draft.editorDataIndex = initialState.editorDataIndex;
195+
// The schema is preserved, because it never changes at the moment.
196+
break;
173197
default:
174198
// Nothing to do here
175199
}

0 commit comments

Comments
 (0)