Skip to content

Commit b3ff8c1

Browse files
committed
front: type and path faster than light (trigrams to pathfinding)
1 parent d800049 commit b3ff8c1

File tree

15 files changed

+307
-17
lines changed

15 files changed

+307
-17
lines changed

front/public/locales/en/operationalStudies/manageTrainSchedule.json

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
"gare": "Station, post",
5353
"importTrainSchedule": "Import",
5454
"infraLoading": "Infrastructure is loading…",
55+
"inputOPTrigrams": "Enter the trigrams of operational points you want to the path to go through, separated by a space.",
56+
"inputOPTrigramsExample": "Ex: LSN CIG CCS",
5557
"inverseOD": "Reverse origin/destination",
5658
"launchSimulation": "Start",
5759
"manageVias": "Steps management",

front/public/locales/fr/operationalStudies/manageTrainSchedule.json

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
"gare": "Gare, poste",
5353
"importTrainSchedule": "Importation",
5454
"infraLoading": "Infra en cours de chargement…",
55+
"inputOPTrigrams": "Entrez les trigrammes des points remarquables par lesquels vous voulez que l'itinéraire se fasse, séparés par un espace.",
56+
"inputOPTrigramsExample": "Ex : LSN CIG CCS",
5557
"inverseOD": "Inverser origine/destination",
5658
"launchSimulation": "Démarrer",
5759
"manageVias": "Gestion des étapes",

front/src/applications/operationalStudies/views/ManageTrainSchedule.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import allowancesPic from 'assets/pictures/components/allowances.svg';
2424
import simulationSettings from 'assets/pictures/components/simulationSettings.svg';
2525
import RollingStock2Img from 'modules/rollingStock/components/RollingStock2Img';
2626
import { isElectric } from 'modules/rollingStock/helpers/utils';
27+
import { formatKmValue } from 'utils/strings';
2728

2829
export default function ManageTrainSchedule() {
2930
const dispatch = useDispatch();
@@ -91,8 +92,7 @@ export default function ManageTrainSchedule() {
9192
{t('tabs.pathFinding')}
9293
{pathFinding?.length && !Number.isNaN(pathFinding.length) && (
9394
<small className="ml-auto pl-1">
94-
{Math.round(pathFinding.length) / 1000}
95-
km
95+
{pathFinding.length && formatKmValue(pathFinding.length / 1000, 3)}
9696
</small>
9797
)}
9898
</span>

front/src/common/Pathfinding/Pathfinding.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,7 @@ function Pathfinding({ zoomToFeature }: PathfindingProps) {
468468
params: {
469469
origin,
470470
destination,
471+
vias,
471472
rollingStockID,
472473
},
473474
});
@@ -519,7 +520,7 @@ function Pathfinding({ zoomToFeature }: PathfindingProps) {
519520
useEffect(() => setIsPathfindingInitialized(true), []);
520521

521522
return (
522-
<div className="pathfinding-state-main-container">
523+
<div className="pathfinding-state-main-container flex-grow-1">
523524
{infra && infra.state !== 'CACHED' && (
524525
<div className="content infra-loading">
525526
<img src={infraLogo} alt="Infra logo" className="mr-2" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/* eslint-disable jsx-a11y/no-autofocus */
2+
import {
3+
Path,
4+
PostSearchApiArg,
5+
SearchResultItemOperationalPoint,
6+
osrdEditoastApi,
7+
} from 'common/api/osrdEditoastApi';
8+
import React, { useEffect, useState } from 'react';
9+
import { useTranslation } from 'react-i18next';
10+
import { useDispatch, useSelector } from 'react-redux';
11+
import { getInfraID, getRollingStockID } from 'reducers/osrdconf/selectors';
12+
import { useDebounce } from 'utils/helpers';
13+
import cx from 'classnames';
14+
import { GoAlert, GoTriangleRight } from 'react-icons/go';
15+
import { loadPathFinding } from 'modules/trainschedule/components/ManageTrainSchedule/helpers/adjustConfWithTrainToModify';
16+
import { setFailure } from 'reducers/main';
17+
import bbox from '@turf/bbox';
18+
import { Position } from 'geojson';
19+
20+
type SearchConstraintType = (string | number | string[])[];
21+
type PathfindingProps = {
22+
zoomToFeature: (lngLat: Position, id?: undefined, source?: undefined) => void;
23+
};
24+
25+
const monospaceOneCharREMWidth = 0.6225;
26+
27+
function OpTooltips({ opList }: { opList: SearchResultItemOperationalPoint[] }) {
28+
// Calculation of chars distance from left to put tooltip on center of op name
29+
const calcLeftMargin = (charsFromLeft: number, length: number) =>
30+
charsFromLeft * monospaceOneCharREMWidth + (length * monospaceOneCharREMWidth) / 2;
31+
let charsFromLeft = 0;
32+
return (
33+
<div className="op-tooltips">
34+
{opList.map((op, idx) => {
35+
const leftMargin = calcLeftMargin(charsFromLeft, op.trigram.length);
36+
charsFromLeft = charsFromLeft + op.trigram.length + 1;
37+
return (
38+
op.trigram !== '' && (
39+
<div
40+
className={cx('op', { wrong: !op.name })}
41+
key={`typeandpath-op-${idx}-${op.trigram}`}
42+
style={{ left: `${leftMargin}rem` }}
43+
>
44+
{op.name ? op.name : <GoAlert />}
45+
</div>
46+
)
47+
);
48+
})}
49+
</div>
50+
);
51+
}
52+
53+
export default function TypeAndPath({ zoomToFeature }: PathfindingProps) {
54+
const dispatch = useDispatch();
55+
const [inputText, setInputText] = useState<string>('');
56+
const [opList, setOpList] = useState<SearchResultItemOperationalPoint[]>([]);
57+
const infraId = useSelector(getInfraID);
58+
const rollingStockId = useSelector(getRollingStockID);
59+
const [postSearch] = osrdEditoastApi.usePostSearchMutation();
60+
const [postPathfinding] = osrdEditoastApi.usePostPathfindingMutation();
61+
const { t } = useTranslation('operationalStudies/manageTrainSchedule');
62+
63+
const debouncedInputText = useDebounce(inputText.trimEnd(), 500);
64+
65+
const handleInput = (text: string) => {
66+
// setInputText(text.trimStart().toUpperCase());
67+
setInputText(text.trimStart());
68+
};
69+
70+
function getOpNames() {
71+
if (infraId !== undefined) {
72+
const opTrigrams = inputText.toUpperCase().split(' ');
73+
const constraint = opTrigrams.reduce(
74+
(res, trigram) => [...res, ['=', ['trigram'], trigram]],
75+
['or'] as (string | SearchConstraintType)[]
76+
);
77+
const limitToMainStationConstraint = [
78+
'or',
79+
['=', ['ch'], ''],
80+
['=', ['ch'], 'BV'],
81+
['=', ['ch'], '00'],
82+
];
83+
const payload: PostSearchApiArg = {
84+
searchPayload: {
85+
object: 'operationalpoint',
86+
query: ['and', constraint, ['=', ['infra_id'], infraId], limitToMainStationConstraint],
87+
},
88+
pageSize: 1000,
89+
};
90+
postSearch(payload)
91+
.unwrap()
92+
.then((results) => {
93+
const operationalPoints = [...results] as SearchResultItemOperationalPoint[];
94+
setOpList(
95+
opTrigrams.map(
96+
(trigram) => operationalPoints.find((op) => op.trigram === trigram) || { trigram }
97+
) as SearchResultItemOperationalPoint[]
98+
);
99+
});
100+
}
101+
}
102+
103+
const isInvalid = !opList.every((op) => op.name || op.trigram === '');
104+
105+
function launchPathFinding() {
106+
if (infraId && rollingStockId && opList.length > 0) {
107+
const params = {
108+
infra: infraId,
109+
rolling_stocks: [rollingStockId],
110+
steps: opList
111+
.filter((op) => op.trigram !== '')
112+
.map((op) => ({
113+
duration: 0,
114+
waypoints: op.track_sections.map((position) => ({
115+
track_section: position.track,
116+
offset: position.position,
117+
})),
118+
})),
119+
};
120+
postPathfinding({ pathQuery: params })
121+
.unwrap()
122+
.then((itineraryCreated: Path) => {
123+
zoomToFeature(bbox(itineraryCreated.geographic));
124+
loadPathFinding(itineraryCreated, dispatch);
125+
})
126+
.catch((e) => {
127+
dispatch(
128+
setFailure({
129+
name: e.data.name,
130+
message: e.data.message,
131+
})
132+
);
133+
});
134+
}
135+
}
136+
137+
useEffect(() => {
138+
if (debouncedInputText !== '') {
139+
getOpNames();
140+
} else {
141+
setOpList([]);
142+
}
143+
}, [debouncedInputText]);
144+
145+
return (
146+
<div
147+
className="type-and-path"
148+
style={{ minWidth: `${monospaceOneCharREMWidth * inputText.length + 5.5}rem` }} // To grow input field & whole div along text size
149+
>
150+
<div className="help">{opList.length === 0 && t('inputOPTrigrams')}</div>
151+
<OpTooltips opList={opList} />
152+
<div className="d-flex align-items-center">
153+
<div
154+
className={cx('form-control-container', 'flex-grow-1', 'mr-2', {
155+
'is-invalid': isInvalid,
156+
})}
157+
>
158+
<input
159+
className="form-control form-control-sm text-zone"
160+
type="text"
161+
value={inputText}
162+
onChange={(e) => handleInput(e.target.value)}
163+
placeholder={t('inputOPTrigramsExample')}
164+
autoFocus
165+
/>
166+
<span className="form-control-state" />
167+
</div>
168+
<button
169+
className="btn btn-sm btn-success"
170+
type="button"
171+
onClick={launchPathFinding}
172+
disabled={isInvalid}
173+
>
174+
<GoTriangleRight />
175+
</button>
176+
</div>
177+
</div>
178+
);
179+
}

front/src/modules/trainschedule/components/ManageTrainSchedule/Itinerary/DisplayItinerary/Origin.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ function Origin(props: OriginProps) {
9595
<div className="mb-2 place" data-testid="itinerary-origin">
9696
{origin !== undefined ? (
9797
<>
98-
<div className="hover origin-name-and-time-container">
98+
<div className="pl-1 hover w-100 d-flex align-items-center">
9999
<span className="text-success mr-2">
100100
<RiMapPin2Fill />
101101
</span>

front/src/modules/trainschedule/components/ManageTrainSchedule/Itinerary/DisplayVias.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export default function DisplayVias({ zoomToFeaturePoint }: DisplayViasProps) {
7373
<div {...provided.droppableProps} ref={provided.innerRef}>
7474
{osrdconf.vias.map((place, index) => (
7575
<Draggable
76-
key={`drag-key-${place.id}-${place.position}`}
76+
key={`drag-key-${place.id}-${place.path_offset}`}
7777
draggableId={`drag-vias-${index}`}
7878
index={index}
7979
>

front/src/modules/trainschedule/components/ManageTrainSchedule/Itinerary/Itinerary.tsx

+17-2
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@ import ModalSugerredVias from 'modules/trainschedule/components/ManageTrainSched
1111
import { getOrigin, getDestination, getVias } from 'reducers/osrdconf/selectors';
1212
import { getMap } from 'reducers/map/selectors';
1313
import Pathfinding from 'common/Pathfinding/Pathfinding';
14+
import TypeAndPath from 'common/Pathfinding/TypeAndPath';
15+
import { GoRocket } from 'react-icons/go';
1416

1517
function Itinerary() {
1618
const origin = useSelector(getOrigin);
1719
const destination = useSelector(getDestination);
1820
const vias = useSelector(getVias);
1921
const [extViewport, setExtViewport] = useState<Viewport>();
22+
const [displayTypeAndPath, setDisplayTypeAndPath] = useState(false);
2023
const dispatch = useDispatch();
2124
const map = useSelector(getMap);
2225

@@ -88,10 +91,22 @@ function Itinerary() {
8891
}, [extViewport]);
8992

9093
return (
91-
<div className="itinerary mb-2">
92-
<div className="mb-2">
94+
<div className="osrd-config-item">
95+
<div className="mb-2 d-flex">
9396
<Pathfinding zoomToFeature={zoomToFeature} />
97+
<button
98+
type="button"
99+
className="btn btn-sm btn-only-icon btn-white px-3 ml-2"
100+
onClick={() => setDisplayTypeAndPath(!displayTypeAndPath)}
101+
>
102+
<GoRocket />
103+
</button>
94104
</div>
105+
{displayTypeAndPath && (
106+
<div className="mb-2">
107+
<TypeAndPath zoomToFeature={zoomToFeature} />
108+
</div>
109+
)}
95110
<div className="osrd-config-item-container pathfinding-details" data-testid="itinerary">
96111
<DisplayItinerary
97112
zoomToFeaturePoint={zoomToFeaturePoint}

front/src/modules/trainschedule/components/ManageTrainSchedule/ManageTrainScheduleMap/RenderItineraryMarkers.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ const RenderItineraryMarkers: FC = () => {
7272
via.duration === 0 ? '' : 'via-with-stop'
7373
}`}
7474
>
75-
{via.name || (via.track && via.track.split('-')[0])}
75+
{via.name ||
76+
(via?.path_offset && `KM ${Math.round(via.path_offset) / 1000}`) ||
77+
'N/A'}
7678
</div>
7779
</Marker>
7880
);

front/src/modules/trainschedule/components/ManageTrainSchedule/helpers/adjustConfWithTrainToModify.ts

+21
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,26 @@ function convertStepToPointOnMap(step?: ArrayElement<Path['steps']>): PointOnMap
3333
: undefined;
3434
}
3535

36+
export function loadPathFinding(path: Path, dispatch: Dispatch) {
37+
if (path.steps && path.steps.length > 1) {
38+
dispatch(updatePathfindingID(path.id));
39+
dispatch(updateItinerary(path));
40+
dispatch(updateOrigin(convertStepToPointOnMap(path.steps[0])));
41+
dispatch(updateDestination(convertStepToPointOnMap(path.steps.at(-1))));
42+
if (path.steps.length > 2) {
43+
const vias = path.steps
44+
.filter(
45+
(via, idx) => idx !== 0 && path.steps && idx < path.steps.length - 1 && !via.suggestion
46+
)
47+
.map((via) => convertStepToPointOnMap(via));
48+
dispatch(replaceVias(compact(vias)));
49+
dispatch(updateSuggeredVias(compact(path.steps.map((via) => convertStepToPointOnMap(via)))));
50+
} else {
51+
dispatch(replaceVias([]));
52+
}
53+
}
54+
}
55+
3656
export default function adjustConfWithTrainToModify(
3757
trainSchedule: TrainSchedule,
3858
path: Path,
@@ -89,4 +109,5 @@ export default function adjustConfWithTrainToModify(
89109
)
90110
);
91111
}
112+
loadPathFinding(path, dispatch);
92113
}

front/src/modules/trainschedule/styles/ManageTrainSchedule/_itinerary.scss

-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
11
.itinerary {
2-
.origin-name-and-time-container {
3-
display: flex;
4-
align-items: center;
5-
padding-left: 0.25rem;
6-
}
7-
82
.toggle-button-container {
93
position: relative;
104
align-self: stretch;

front/src/styles/scss/_variables.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ $colors: (
3535
);
3636

3737
:root {
38-
--border-radius: .4375rem;
38+
--border-radius: 4px; // 0.4375rem;
3939
--breakpoint-lg: 1024px;
4040
@each $color, $value in $colors {
4141
--#{$color}: #{$value};

front/src/styles/scss/common/_components.scss

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@import 'components/debouncedNumberInput';
22
@import 'components/pathfinding';
3+
@import 'components/typeAndPath';
34
@import 'components/scenarioExplorator';
45
@import 'components/stationCard';
56
@import 'components/releaseInformations';

front/src/styles/scss/common/components/_pathfinding.scss

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
align-items: center;
2929
margin-bottom: 0rem;
3030
border-radius: 4px;
31-
padding: 0 0.25rem;
31+
padding-left: 0.25rem;
3232
.ring {
3333
position: relative;
3434
display: block;
@@ -94,7 +94,7 @@
9494
.content {
9595
display: flex;
9696
align-items: center;
97-
gap: 1rem;
97+
gap: 0.5rem;
9898
justify-content: center;
9999
padding: 0.5rem;
100100
font-size: 0.9rem;

0 commit comments

Comments
 (0)