Skip to content

Commit 1e46c62

Browse files
committed
front: fix curves slopes and loading gauges error
1 parent 746bfcc commit 1e46c62

File tree

7 files changed

+136
-21
lines changed

7 files changed

+136
-21
lines changed

front/src/applications/editor/components/LinearMetadata/FormComponent.tsx

+51-5
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,10 @@ import { LinearMetadataTooltip } from './tooltip';
3030
import { FormBeginEndWidget } from './FormBeginEndWidget';
3131
import 'common/IntervalsDataViz/style.scss';
3232

33-
export const FormComponent: React.FC<FieldProps> = (props) => {
33+
const IntervalEditorComponent: React.FC<FieldProps> = (props) => {
3434
const { name, formContext, formData, schema, onChange, registry } = props;
3535
const { openModal, closeModal } = useModal();
3636
const { t } = useTranslation();
37-
const Fields = utils.getDefaultRegistry().fields;
3837

3938
// Wich segment area is visible
4039
const [viewBox, setViewBox] = useState<[number, number] | null>(null);
@@ -145,9 +144,6 @@ export const FormComponent: React.FC<FieldProps> = (props) => {
145144
setSelectedData(selected !== null && data[selected] ? data[selected] : null);
146145
}, [selected, data]);
147146

148-
if (!LINEAR_METADATA_FIELDS.includes(name))
149-
return <Fields.ArrayField {...props} schema={jsonSchema} />;
150-
151147
return (
152148
<div className="linear-metadata">
153149
<div className="header">
@@ -427,4 +423,54 @@ export const FormComponent: React.FC<FieldProps> = (props) => {
427423
);
428424
};
429425

426+
export const FormComponent: React.FC<FieldProps> = (props) => {
427+
const { name, formContext, schema, registry } = props;
428+
const Fields = utils.getDefaultRegistry().fields;
429+
430+
// Get the distance of the geometry
431+
const distance = useMemo(() => {
432+
if (!isNil(formContext.length)) {
433+
return formContext.length;
434+
}
435+
if (formContext.geometry?.type === 'LineString') {
436+
return getLineStringDistance(formContext.geometry);
437+
}
438+
return 0;
439+
}, [formContext]);
440+
441+
// Remove the 'valueField' required field because it is required by the backend. However,
442+
// the segment with missing values is filtered in 'customOnChange' before being sent to the backend,
443+
// and then re-added by 'fixLinearMetadataItems'.
444+
const requiredFilter = (requireds: string[]) =>
445+
requireds.filter((r) => ['end', 'begin'].includes(r));
446+
447+
// Compute the JSON schema of the linear metadata item
448+
const jsonSchema = useMemo(
449+
() =>
450+
getFieldJsonSchema(
451+
schema,
452+
registry.rootSchema,
453+
requiredFilter,
454+
distance
455+
? {
456+
begin: {
457+
minimum: 0,
458+
maximum: distance,
459+
},
460+
end: {
461+
minimum: 0,
462+
maximum: distance,
463+
},
464+
}
465+
: {}
466+
),
467+
[schema, registry.rootSchema, distance]
468+
);
469+
470+
if (!LINEAR_METADATA_FIELDS.includes(name))
471+
return <Fields.ArrayField {...props} schema={jsonSchema} />;
472+
473+
return <IntervalEditorComponent {...props} />;
474+
};
475+
430476
export default FormComponent;

front/src/applications/editor/tools/trackEdition/components.tsx

+44-4
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@ import {
3131
SpeedSectionEntity,
3232
TrackSectionEntity,
3333
} from 'types';
34-
34+
import DebouncedNumberInputSNCF from 'common/BootstrapSNCF/FormSNCF/DebouncedNumberInputSNCF';
35+
import { WidgetProps } from '@rjsf/core';
3536
import { TrackEditionState } from './types';
36-
import { injectGeometry } from './utils';
37+
import { injectGeometry, removeInvalidRanges } from './utils';
3738

3839
export const TRACK_LAYER_ID = 'trackEditionTool/new-track-path';
3940
export const POINTS_LAYER_ID = 'trackEditionTool/new-track-points';
@@ -351,6 +352,21 @@ export const TrackEditionLayers: FC = () => {
351352
);
352353
};
353354

355+
export const CustomLengthInput: React.FC<WidgetProps> = (props) => {
356+
const { onChange, value } = props;
357+
358+
return (
359+
<DebouncedNumberInputSNCF
360+
debouncedDelay={2000}
361+
input={value}
362+
setInput={onChange}
363+
label=""
364+
showFlex={false}
365+
sm={false}
366+
/>
367+
);
368+
};
369+
354370
export const TrackEditionLeftPanel: FC = () => {
355371
const dispatch = useDispatch();
356372
const { t } = useTranslation();
@@ -359,7 +375,7 @@ export const TrackEditionLeftPanel: FC = () => {
359375
EditorContext
360376
) as ExtendedEditorContextType<TrackEditionState>;
361377
const submitBtnRef = useRef<HTMLButtonElement>(null);
362-
const { track } = state;
378+
const { track, initialTrack } = state;
363379
const isNew = track.properties.id === NEW_ENTITY_ID;
364380

365381
// Hack to be able to launch the submit event from the rjsf form by using
@@ -376,6 +392,11 @@ export const TrackEditionLeftPanel: FC = () => {
376392
<>
377393
<EditorForm
378394
data={track}
395+
overrideUiSchema={{
396+
length: {
397+
'ui:widget': CustomLengthInput,
398+
},
399+
}}
379400
onSubmit={async (savedEntity) => {
380401
// eslint-disable-next-line @typescript-eslint/no-explicit-any
381402
const res: any = await dispatch(
@@ -408,7 +429,26 @@ export const TrackEditionLeftPanel: FC = () => {
408429
});
409430
}}
410431
onChange={(newTrack) => {
411-
setState({ ...state, track: newTrack as TrackSectionEntity });
432+
let checkedTrack = { ...newTrack };
433+
if (initialTrack.properties.length !== newTrack.properties.length) {
434+
const { loading_gauge_limits, slopes, curves, length: newLength } = newTrack.properties;
435+
const validLoadingGaugeLimits = removeInvalidRanges(loading_gauge_limits, newLength);
436+
const validCurves = removeInvalidRanges(curves, newLength);
437+
const validSlopes = removeInvalidRanges(slopes, newLength);
438+
checkedTrack = {
439+
...checkedTrack,
440+
properties: {
441+
...checkedTrack.properties,
442+
loading_gauge_limits: validLoadingGaugeLimits,
443+
slopes: validSlopes,
444+
curves: validCurves,
445+
},
446+
};
447+
}
448+
setState({
449+
...state,
450+
track: { ...(checkedTrack as TrackSectionEntity) },
451+
});
412452
}}
413453
>
414454
<div>

front/src/applications/editor/tools/trackEdition/tool.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,6 @@ const TrackEditionTool: Tool<TrackEditionState> = {
219219
const position = nearestPoint.geometry.coordinates as [number, number];
220220
const index = nearestPoint.properties.sectionIndex;
221221
const newState = cloneDeep(state);
222-
223222
newState.track.geometry.coordinates.splice(index + 1, 0, position);
224223
newState.track = entityDoUpdate(newState.track, state.track.geometry);
225224
newState.nearestPoint = null;

front/src/applications/editor/tools/trackEdition/utils.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { EditorEntity, TrackSectionEntity } from 'types';
2+
import { LinearMetadataItem } from 'common/IntervalsDataViz/types';
23
import { NEW_ENTITY_ID } from '../../data/utils';
34

4-
// eslint-disable-next-line import/prefer-default-export
55
export function getNewLine(points: [number, number][]): TrackSectionEntity {
66
return {
77
type: 'Feature',
@@ -15,6 +15,7 @@ export function getNewLine(points: [number, number][]): TrackSectionEntity {
1515
length: 0,
1616
slopes: [],
1717
curves: [],
18+
loading_gauge_limits: [],
1819
},
1920
};
2021
}
@@ -29,3 +30,14 @@ export function injectGeometry(track: EditorEntity): EditorEntity {
2930
},
3031
};
3132
}
33+
34+
/**
35+
* Remove the invalid ranges when the length of the track section has been modified
36+
* - remove ranges which start after the new end
37+
* - cut the ranges which start before the new end but end after it
38+
*/
39+
export function removeInvalidRanges<T>(values: LinearMetadataItem<T>[], newLength: number) {
40+
return values
41+
.filter((item) => item.begin < newLength)
42+
.map((item) => (item.end >= newLength ? { ...item, end: newLength } : item));
43+
}

front/src/common/BootstrapSNCF/FormSNCF/DebouncedNumberInputSNCF.tsx

+14-5
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ type DebouncedNumberInputSNCFProps = {
1010
id?: string;
1111
max?: number;
1212
min?: number;
13+
sm?: boolean;
14+
showFlex?: boolean;
1315
};
1416

1517
const DebouncedNumberInputSNCF = ({
@@ -18,8 +20,10 @@ const DebouncedNumberInputSNCF = ({
1820
setInput,
1921
debouncedDelay = 800,
2022
id = '',
21-
max = 100,
23+
max,
2224
min = 0,
25+
sm = true,
26+
showFlex = true,
2327
}: DebouncedNumberInputSNCFProps) => {
2428
const [value, setValue] = useState<number | null>(input);
2529

@@ -28,19 +32,24 @@ const DebouncedNumberInputSNCF = ({
2832
}, [input]);
2933

3034
const checkChangedInput = (newValue: number | null) => {
31-
if (newValue !== null && newValue !== input && min <= newValue && newValue <= max)
35+
if (
36+
newValue !== null &&
37+
newValue !== input &&
38+
min <= newValue &&
39+
(max === undefined || newValue <= max)
40+
)
3241
setInput(newValue);
3342
else if (value === null && input !== 0) setInput(0);
3443
};
3544

3645
useDebouncedFunc(value, debouncedDelay, checkChangedInput);
3746

3847
return (
39-
<div className="debounced-number-input">
48+
<div className={`${showFlex ? 'debounced-number-input' : ''}`}>
4049
<InputSNCF
4150
type="number"
4251
id={id}
43-
isInvalid={value !== null && (value < min || max < value)}
52+
isInvalid={value !== null && (value < min || (max ? max < value : false))}
4453
label={label}
4554
max={max}
4655
min={min}
@@ -49,7 +58,7 @@ const DebouncedNumberInputSNCF = ({
4958
setValue(e.target.value !== '' ? parseFloat(e.target.value) : null);
5059
}}
5160
value={value !== null ? value : ''}
52-
sm
61+
sm={sm}
5362
/>
5463
</div>
5564
);

front/src/common/IntervalsDataViz/data.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -219,17 +219,17 @@ export function mergeIn<T>(
219219
* - if empty it generate one
220220
* - if there is a gap at begin/end or inside, it is created
221221
* - if there is an overlaps, remove it
222-
* @param value The linear metadata
222+
* @param items The linear metadata
223223
* @param lineLength The full length of the linearmetadata (should be computed from the LineString or given by the user)
224224
* @param opts If defined, it allows the function to fill gaps with default field value
225225
*/
226226
export function fixLinearMetadataItems<T>(
227-
value: Array<LinearMetadataItem<T>> | undefined,
227+
items: Array<LinearMetadataItem<T>> | undefined,
228228
lineLength: number,
229229
opts?: { fieldName: string; defaultValue: unknown }
230230
): Array<LinearMetadataItem<T>> {
231231
// simple scenario
232-
if (!value || value.length === 0) {
232+
if (!items || items.length === 0) {
233233
return [
234234
{
235235
begin: 0,
@@ -238,6 +238,9 @@ export function fixLinearMetadataItems<T>(
238238
} as LinearMetadataItem<T>,
239239
];
240240
}
241+
const filteredItems = items
242+
.filter((item) => item.begin < lineLength)
243+
.map((item) => (item.end >= lineLength ? { ...item, end: lineLength } : item));
241244

242245
function haveAdditionalKeys(item: LinearMetadataItem, itemToCompare: LinearMetadataItem) {
243246
const keys = Object.keys(item);
@@ -249,7 +252,7 @@ export function fixLinearMetadataItems<T>(
249252
}
250253

251254
// merge empty adjacent items
252-
let fixedLinearMetadata: Array<LinearMetadataItem<T>> = sortBy(value, ['begin']);
255+
let fixedLinearMetadata: Array<LinearMetadataItem<T>> = sortBy(filteredItems, ['begin']);
253256

254257
// Order the array and fix it by filling gaps if there are some
255258
fixedLinearMetadata = fixedLinearMetadata.flatMap((item, index, array) => {

front/src/types/editor.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { JSONSchema7 } from 'json-schema';
22
import { Feature, GeoJsonProperties, Geometry, Point, LineString, MultiLineString } from 'geojson';
3-
import { Direction, DirectionalTrackRange, ObjectType } from 'common/api/osrdEditoastApi';
3+
import {
4+
Direction,
5+
DirectionalTrackRange,
6+
LoadingGaugeType,
7+
ObjectType,
8+
} from 'common/api/osrdEditoastApi';
49
import { LinearMetadataItem } from 'common/IntervalsDataViz/types';
510
import { NullGeometry } from './geospatial';
611

@@ -24,6 +29,7 @@ export interface TrackSectionEntity
2429
{
2530
length: number;
2631
slopes: LinearMetadataItem<{ gradient: number }>[];
32+
loading_gauge_limits: LinearMetadataItem<{ category: LoadingGaugeType }>[];
2733
curves: LinearMetadataItem<{ radius: number }>[];
2834
extensions?: {
2935
sncf?: {

0 commit comments

Comments
 (0)