Skip to content

Commit

Permalink
front: add new tracksection length, curves, slopes and loading gauges…
Browse files Browse the repository at this point in the history
… behavior
  • Loading branch information
Akctarus committed Dec 25, 2023
1 parent c45b7e4 commit 8a73067
Show file tree
Hide file tree
Showing 11 changed files with 172 additions and 34 deletions.
5 changes: 3 additions & 2 deletions front/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -450,8 +450,9 @@
"mode-add-point": "Add a point",
"mode-delete-point": "Delete points",
"mode-move-point": "Move points",
"toggle-anchoring": "Toggle anchoring on/off",
"save-line": "Save the line"
"reset-line": "Reset data",
"save-line": "Save the line",
"toggle-anchoring": "Toggle anchoring on/off"
},
"help": {
"add-anchor-point": "Click to add a point at the end of the track section. Click on the track to add an intermediate point.",
Expand Down
5 changes: 3 additions & 2 deletions front/public/locales/fr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -450,8 +450,9 @@
"mode-add-point": "Ajouter un point",
"mode-delete-point": "Supprimer des points",
"mode-move-point": "Déplacer les points",
"toggle-anchoring": "Activer / désactiver l'ancrage automatique",
"save-line": "Sauvegarder la ligne"
"reset-line": "Réinitialiser les données",
"save-line": "Sauvegarder la ligne",
"toggle-anchoring": "Activer / désactiver l'ancrage automatique"
},
"help": {
"add-anchor-point": "Cliquez pour ajouter un point au bout de la section de ligne. Cliquez sur la ligne pour ajouter un point intermédiaire.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,10 @@ import { LinearMetadataTooltip } from './tooltip';
import { FormBeginEndWidget } from './FormBeginEndWidget';
import 'common/IntervalsDataViz/style.scss';

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

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

if (!LINEAR_METADATA_FIELDS.includes(name))
return <Fields.ArrayField {...props} schema={jsonSchema} />;

return (
<div className="linear-metadata">
<div className="header">
Expand Down Expand Up @@ -427,4 +423,60 @@ export const FormComponent: React.FC<FieldProps> = (props) => {
);
};

export const FormComponent: React.FC<FieldProps> = (props) => {
const { name, formContext, schema, registry } = props;
const Fields = utils.getDefaultRegistry().fields;

// Get the distance of the geometry
const distance = useMemo(() => {
if (!isNil(formContext.length)) {
return formContext.length as number;
}
if (formContext.geometry?.type === 'LineString') {
return getLineStringDistance(formContext.geometry);
}
return 0;
}, [formContext]);

// Remove the 'valueField' required field because it is required by the backend. However,
// the segment with missing values is filtered in 'customOnChange' before being sent to the backend,
// and then re-added by 'fixLinearMetadataItems'.
const requiredFilter = (requireds: string[]) =>
requireds.filter((r) => ['end', 'begin'].includes(r));

// Compute the JSON schema of the linear metadata item
const jsonSchema = useMemo(
() =>
getFieldJsonSchema(
schema,
registry.rootSchema,
requiredFilter,
distance
? {
begin: {
minimum: 0,
maximum: distance,
},
end: {
minimum: 0,
maximum: distance,
},
}
: {}
),
[schema, registry.rootSchema, distance]
);

if (LINEAR_METADATA_FIELDS.includes(name))
return (
<IntervalEditorComponent
jsonSchema={jsonSchema}
distance={distance}
requiredFilter={requiredFilter}
{...props}
/>
);
return <Fields.ArrayField {...props} schema={jsonSchema} />;
};

export default FormComponent;
41 changes: 37 additions & 4 deletions front/src/applications/editor/tools/trackEdition/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ import { save } from 'reducers/editor';
import { getMap } from 'reducers/map/selectors';
import { getInfraID } from 'reducers/osrdconf/selectors';
import { CatenaryEntity, SpeedSectionEntity, TrackSectionEntity } from 'types';

import DebouncedNumberInputSNCF from 'common/BootstrapSNCF/FormSNCF/DebouncedNumberInputSNCF';
import { WidgetProps } from '@rjsf/core';
import { TrackEditionState } from './types';
import { injectGeometry } from './utils';
import { injectGeometry, removeInvalidRanges } from './utils';

export const TRACK_LAYER_ID = 'trackEditionTool/new-track-path';
export const POINTS_LAYER_ID = 'trackEditionTool/new-track-points';
Expand Down Expand Up @@ -346,6 +347,14 @@ export const TrackEditionLayers: FC = () => {
);
};

export const CustomLengthInput: React.FC<WidgetProps> = (props) => {
const { onChange, value } = props;

return (
<DebouncedNumberInputSNCF debouncedDelay={1500} input={value} setInput={onChange} label="" />
);
};

export const TrackEditionLeftPanel: FC = () => {
const dispatch = useDispatch();
const { t } = useTranslation();
Expand All @@ -354,7 +363,7 @@ export const TrackEditionLeftPanel: FC = () => {
EditorContext
) as ExtendedEditorContextType<TrackEditionState>;
const submitBtnRef = useRef<HTMLButtonElement>(null);
const { track } = state;
const { track, initialTrack } = state;
const isNew = track.properties.id === NEW_ENTITY_ID;

// Hack to be able to launch the submit event from the rjsf form by using
Expand All @@ -371,6 +380,11 @@ export const TrackEditionLeftPanel: FC = () => {
<>
<EditorForm
data={track}
overrideUiSchema={{
length: {
'ui:widget': CustomLengthInput,
},
}}
onSubmit={async (savedEntity) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const res: any = await dispatch(
Expand Down Expand Up @@ -403,7 +417,26 @@ export const TrackEditionLeftPanel: FC = () => {
});
}}
onChange={(newTrack) => {
setState({ ...state, track: newTrack as TrackSectionEntity });
let checkedTrack = { ...newTrack };
if (initialTrack.properties.length !== newTrack.properties.length) {
const { loading_gauge_limits, slopes, curves, length: newLength } = newTrack.properties;
const validLoadingGaugeLimits = removeInvalidRanges(loading_gauge_limits, newLength);
const validCurves = removeInvalidRanges(curves, newLength);
const validSlopes = removeInvalidRanges(slopes, newLength);
checkedTrack = {
...checkedTrack,
properties: {
...checkedTrack.properties,
loading_gauge_limits: validLoadingGaugeLimits,
slopes: validSlopes,
curves: validCurves,
},
};
}
setState({
...state,
track: checkedTrack as TrackSectionEntity,
});
}}
>
<div>
Expand Down
16 changes: 14 additions & 2 deletions front/src/applications/editor/tools/trackEdition/tool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { cloneDeep, isEmpty, isEqual } from 'lodash';
import { MdShowChart } from 'react-icons/md';
import { RiDragMoveLine } from 'react-icons/ri';
import { BiAnchor, BiArrowFromLeft, BiArrowToRight } from 'react-icons/bi';
import { BiAnchor, BiArrowFromLeft, BiArrowToRight, BiReset } from 'react-icons/bi';
import { Feature, LineString } from 'geojson';
import getNearestPoint from '@turf/nearest-point';
import { featureCollection } from '@turf/helpers';
Expand Down Expand Up @@ -65,6 +65,19 @@ const TrackEditionTool: Tool<TrackEditionState> = {
}
},
},
{
id: 'reset-entity',
icon: BiReset,
labelTranslationKey: `Editor.tools.track-edition.actions.reset-line`,
isDisabled({ state: { track, initialTrack } }) {
return isEqual(track, initialTrack);
},
onClick({ setState, state: { initialTrack } }) {
setState({
track: cloneDeep(initialTrack),
});
},
},
],
[
{
Expand Down Expand Up @@ -219,7 +232,6 @@ const TrackEditionTool: Tool<TrackEditionState> = {
const position = nearestPoint.geometry.coordinates as [number, number];
const index = nearestPoint.properties.sectionIndex;
const newState = cloneDeep(state);

newState.track.geometry.coordinates.splice(index + 1, 0, position);
newState.track = entityDoUpdate(newState.track, state.track.geometry);
newState.nearestPoint = null;
Expand Down
15 changes: 14 additions & 1 deletion front/src/applications/editor/tools/trackEdition/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { EditorEntity, TrackSectionEntity } from 'types';
import { LinearMetadataItem } from 'common/IntervalsDataViz/types';
import { NEW_ENTITY_ID } from '../../data/utils';

// eslint-disable-next-line import/prefer-default-export
export function getNewLine(points: [number, number][]): TrackSectionEntity {
return {
type: 'Feature',
Expand All @@ -15,6 +15,7 @@ export function getNewLine(points: [number, number][]): TrackSectionEntity {
length: 0,
slopes: [],
curves: [],
loading_gauge_limits: [],
},
};
}
Expand All @@ -29,3 +30,15 @@ export function injectGeometry(track: EditorEntity): EditorEntity {
},
};
}

/**
* Remove the invalid ranges when the length of the track section has been modified
* - keep ranges if begin is undefined in case we just added a new one or if we deleted the begin input value
* - remove ranges which start after the new end
* - cut the ranges which start before the new end but end after it
*/
export function removeInvalidRanges<T>(values: LinearMetadataItem<T>[], newLength: number) {
return values
.filter((item) => item.begin < newLength || item.begin === undefined)
.map((item) => (item.end >= newLength ? { ...item, end: newLength } : item));
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ type DebouncedNumberInputSNCFProps = {
id?: string;
max?: number;
min?: number;
sm?: boolean;
showFlex?: boolean;
};

const DebouncedNumberInputSNCF = ({
Expand All @@ -18,8 +20,10 @@ const DebouncedNumberInputSNCF = ({
setInput,
debouncedDelay = 800,
id = '',
max = 100,
max,
min = 0,
sm = false,
showFlex = false,
}: DebouncedNumberInputSNCFProps) => {
const [value, setValue] = useState<number | null>(input);

Expand All @@ -28,19 +32,27 @@ const DebouncedNumberInputSNCF = ({
}, [input]);

const checkChangedInput = (newValue: number | null) => {
if (newValue !== null && newValue !== input && min <= newValue && newValue <= max)
if (
newValue !== null &&
newValue !== input &&
min <= newValue &&
(max === undefined || newValue <= max)
) {
setInput(newValue);
else if (value === null && input !== 0) setInput(0);
} else if (value === null && input !== 0) {
const previousValue = input;
setInput(previousValue);
}
};

useDebouncedFunc(value, debouncedDelay, checkChangedInput);

return (
<div className="debounced-number-input">
<div className={`${showFlex && 'debounced-number-input'}`}>
<InputSNCF
type="number"
id={id}
isInvalid={value !== null && (value < min || max < value)}
isInvalid={value !== null && (value < min || (max !== undefined && max < value))}
label={label}
max={max}
min={min}
Expand All @@ -49,7 +61,7 @@ const DebouncedNumberInputSNCF = ({
setValue(e.target.value !== '' ? parseFloat(e.target.value) : null);
}}
value={value !== null ? value : ''}
sm
sm={sm}
/>
</div>
);
Expand Down
4 changes: 3 additions & 1 deletion front/src/common/IntervalsDataViz/data.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ function checkWrapperValidity<T>(
expect(result[0].begin).toEqual(0);
// we round due to some approximation that result to a diff (below millimeter)
if (newLine)
expect(Math.round(last(result)?.end || 0)).toEqual(Math.round(getLineStringDistance(newLine)));
expect(Math.round(last(result)?.end || 0)).not.toEqual(
Math.round(getLineStringDistance(newLine))
);
// Checking the continuity
tail(result).forEach((value, index) => {
expect(value.begin <= value.end).toEqual(true);
Expand Down
Loading

0 comments on commit 8a73067

Please sign in to comment.