diff --git a/front/public/index.html b/front/public/index.html index 587ca1a8ee4..d1efbe8339d 100644 --- a/front/public/index.html +++ b/front/public/index.html @@ -23,7 +23,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - OSRD - DGEX Solutions + OSRD diff --git a/front/public/locales/fr/home.json b/front/public/locales/fr/home.json new file mode 100644 index 00000000000..5a963588a78 --- /dev/null +++ b/front/public/locales/fr/home.json @@ -0,0 +1,8 @@ +{ + "customget": "Visualisation JSON GET", + "editor": "Éditeur d'infrastructure", + "map": "Cartographie", + "opendataimport": "Importation horaires", + "stdcm": "Sillons de dernière minute", + "timetable": "Grilles horaires" +} \ No newline at end of file diff --git a/front/public/locales/fr/opendata.json b/front/public/locales/fr/opendata.json new file mode 100644 index 00000000000..e645efc7378 --- /dev/null +++ b/front/public/locales/fr/opendata.json @@ -0,0 +1,44 @@ +{ + "closeOSRDConfig": "Fermer", + "completeTrackSectionID": "Compléter les coordonnées", + "date": "DATE", + "datetime": "Date & horaires", + "endTime": "FIN", + "errorMessages": { + "error": "Une erreur est survenue", + "errorNoDate": "Vous devez renseigner une date.", + "errorNoFrom": "Vous devez renseigner une origine.", + "errorNoTo": "Vous devez renseigner une destination.", + "errorSameFromTo": "L'origine et la destination doivent être différentes.", + "unableToRetrievePathfinding": "Impossible de créer un chemin" + }, + "from": "Origine", + "generatePaths": "Générer les pathfinding", + "generatePathsAuto": "Compléter et générer les pathfinding automatiquement", + "generateTrainSchedules": "Générer les calculs de marche", + "goToSimulation": "Résultats de simulation", + "goToSTDCM": "Sillon de dernière minute", + "import": "Importation", + "inputPlaceholder": "Nom, trigramme", + "launchImport": "Démarrer l'importation", + "noResults": "Aucun résultat", + "openOSRDConfig": "Infra / Table horaire / Matériel", + "startTime": "DÉBUT", + "status": { + "calculatingTrainSchedule": "Calcul de marches en cours...", + "calculatingTrainScheduleComplete": "Calcul de marches terminé", + "calculatingTrainScheduleCompleteAll": "Tous les calculs de marches sont terminés.", + "calculatingTrainScheduleError": "Le calcul de marche a échoué", + "complete": "positionnez", + "missingInfra": "Vous devez renseigner une infrastructure", + "missingRollingStock": "Vous devez choisir un matériel roulant par défaut", + "missingTimetable": "Vous devez choisir une grille horaire", + "noImportationPossible": "L'importation n'est pas possible pour l'instant :", + "pathComplete": "Tous les pathfinding ont été effectués", + "ready": "Prêt.", + "searchingPath": "Génération pathfinding", + "uicComplete": "Tous les lieux ont été renseignés." + }, + "to": "Destination", + "trainsFound": "trains trouvé(s)" +} \ No newline at end of file diff --git a/front/public/locales/fr/translation.json b/front/public/locales/fr/translation.json index 88c65febc67..ea2ce351ddc 100644 --- a/front/public/locales/fr/translation.json +++ b/front/public/locales/fr/translation.json @@ -194,13 +194,6 @@ "track_section-length": "Taille" } }, - "Home": { - "customget": "Visualisation JSON GET", - "map": "Cartographie", - "editor": "Éditeur d'infrastructure", - "timetable": "Grilles horaires", - "stdcm": "Sillons de dernière minute (en travaux)" - }, "Login": { "connect": "Se connecter", "englishFlag": "drapeau anglais", diff --git a/front/src/applications/carto/Map.tsx b/front/src/applications/carto/Map.tsx index 66ab6a31b10..a51f755bcd1 100644 --- a/front/src/applications/carto/Map.tsx +++ b/front/src/applications/carto/Map.tsx @@ -125,7 +125,7 @@ function Map() { touchZoomRotate > - + { dispatch(updateMustRedraw(true)); }, 0); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { // ADN, entire fonction operation is subject to one condition, so aopply this condition before OR write clear and first condition to return (do nothing) offsetTimeByDragging(dragOffset); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [dragOffset]); useEffect(() => { @@ -196,6 +198,7 @@ export default function SpaceTimeChart(props) { drawAllTrains(resetChart); handleWindowResize(CHART_ID, dispatch, drawAllTrains, isResizeActive, setResizeActive); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [mustRedraw, rotate, selectedTrain, consolidatedSimulation]); // ADN: trigger a redraw on every simulation change. This is the right pattern. @@ -208,6 +211,7 @@ export default function SpaceTimeChart(props) { drawAllTrains(resetChart, true, newDataSimulation); handleWindowResize(CHART_ID, dispatch, drawAllTrains, isResizeActive, setResizeActive); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [simulation.trains]); useEffect(() => { @@ -221,6 +225,7 @@ export default function SpaceTimeChart(props) { ); dispatch(updatePositionValues(newPositionValues)); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [chart, mustRedraw]); useEffect(() => { @@ -236,6 +241,7 @@ export default function SpaceTimeChart(props) { timePosition ); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [positionValues]); useEffect(() => { diff --git a/front/src/applications/customget/views/TrainList.js b/front/src/applications/customget/views/TrainList.js index 4f4b97fc6bf..9ec3f8bfa3b 100644 --- a/front/src/applications/customget/views/TrainList.js +++ b/front/src/applications/customget/views/TrainList.js @@ -163,6 +163,7 @@ export default function TrainsList() { setOnInput(false); dispatch(updateMustRedraw(true)); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [debouncedInputName]); useEffect(() => { @@ -170,12 +171,14 @@ export default function TrainsList() { setOnInput(false); dispatch(updateMustRedraw(true)); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [debouncedInputTime]); useEffect(() => { if (!onInput) { setFormattedList(formatTrainsList()); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedTrain, departureArrivalTimes, trainNameClickedIDX, typeOfInputFocused]); return ( diff --git a/front/src/applications/editor/Map.tsx b/front/src/applications/editor/Map.tsx index d42c3811a6d..d4dbe48d944 100644 --- a/front/src/applications/editor/Map.tsx +++ b/front/src/applications/editor/Map.tsx @@ -172,7 +172,7 @@ const MapUnplugged: FC> = ({ }} > - + + } /> + } + /> + + + } /> + } /> + + + + ); +} diff --git a/front/src/applications/opendata/OpenDataImport.js b/front/src/applications/opendata/OpenDataImport.js new file mode 100644 index 00000000000..09355d5a77f --- /dev/null +++ b/front/src/applications/opendata/OpenDataImport.js @@ -0,0 +1,49 @@ +import React, { useEffect, useState } from 'react'; +import OpenDataImportConfig from 'applications/opendata/views/OpenDataImportConfig'; +import OpenDataTrainsList from 'applications/opendata/views/OpenDataTrainsList'; +import OpenDataGlobalSettings from 'applications/opendata/views/OpenDataGlobalSettings'; +import { get } from 'common/requests'; +import Loader from 'common/Loader'; + +const ROLLING_STOCK_URL = '/light_rolling_stock/'; + +export default function OpenDataImport() { + const [config, setConfig] = useState(); + const [rollingStockDB, setRollingStockDB] = useState(); + const [mustUpdateTimetable, setMustUpdateTimetable] = useState(true); + + async function getRollingStockDB() { + try { + const data = await get(ROLLING_STOCK_URL, { page_size: 1000 }); + setRollingStockDB(data.results); + } catch (error) { + console.log(error); + } + } + + useEffect(() => { + if (!rollingStockDB) { + getRollingStockDB(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return rollingStockDB ? ( +
+
+ + + +
+
+ ) : ( + + ); +} diff --git a/front/src/applications/opendata/components/Map.js b/front/src/applications/opendata/components/Map.js new file mode 100644 index 00000000000..d23cd4ca8a3 --- /dev/null +++ b/front/src/applications/opendata/components/Map.js @@ -0,0 +1,122 @@ +import React, { useEffect, useRef, useState } from 'react'; +import PropTypes from 'prop-types'; +import maplibregl from 'maplibre-gl'; +import ReactMapGL, { AttributionControl, ScaleControl } from 'react-map-gl'; +import { point as turfPoint } from '@turf/helpers'; +import { useSelector } from 'react-redux'; +import turfNearestPointOnLine from '@turf/nearest-point-on-line'; + +/* Main data & layers */ +import Background from 'common/Map/Layers/Background'; +import VirtualLayers from 'applications/osrd/views/OSRDSimulation/VirtualLayers'; +import SnappedMarker from 'common/Map/Layers/SnappedMarker'; +/* Objects & various */ +import TracksGeographic from 'common/Map/Layers/TracksGeographic'; +import colors from 'common/Map/Consts/colors'; +import osmBlankStyle from 'common/Map/Layers/osmBlankStyle'; +import { LAYER_GROUPS_ORDER, LAYERS } from 'config/layerOrder'; + +import 'common/Map/Map.scss'; +import OperationalPoints from 'common/Map/Layers/OperationalPoints'; +import Platforms from 'common/Map/Layers/Platforms'; +import { getMapMouseEventNearestFeature } from 'utils/mapboxHelper'; + +export default function Map(props) { + const { viewport, setViewport, setClickedFeature } = props; + const mapStyle = useSelector((state) => state.map.mapStyle); + const [lngLatHover, setLngLatHover] = useState(); + const [trackSectionGeoJSON, setTrackSectionGeoJSON] = useState(); + const [snappedPoint, setSnappedPoint] = useState(); + const mapRef = useRef(null); + const scaleControlStyle = { + left: 20, + bottom: 20, + }; + + const onFeatureClick = (e) => { + const result = getMapMouseEventNearestFeature(e); + if ( + result && + result.feature.properties && + result.feature.properties.id && + result.feature.geometry.type === 'LineString' + ) { + setClickedFeature(result.feature); + } + }; + + const onMoveGetFeature = (e) => { + const result = getMapMouseEventNearestFeature(e); + if ( + result && + result.feature.properties && + result.feature.properties.id && + result.feature.geometry.type === 'LineString' + ) { + setTrackSectionGeoJSON(result.feature.geometry); + setLngLatHover(result.nearest); + } else { + setSnappedPoint(undefined); + } + }; + + useEffect(() => { + if (trackSectionGeoJSON !== undefined && lngLatHover !== undefined) { + const point = turfPoint(lngLatHover); + try { + setSnappedPoint(turfNearestPointOnLine(trackSectionGeoJSON, point)); + } catch (error) { + console.warn(`Ìmpossible to snapPoint - error ${error}`); + } + } + }, [trackSectionGeoJSON, lngLatHover]); + + return ( + setViewport(e.viewState)} + onMouseMove={(e) => onMoveGetFeature(e)} + attributionControl={false} // Defined below + onClick={onFeatureClick} + interactiveLayerIds={['chartis/tracks-geo/main']} + touchZoomRotate + > + + + + + + + + + + + {snappedPoint !== undefined ? : null} + + ); +} + +Map.propTypes = { + viewport: PropTypes.object.isRequired, + setViewport: PropTypes.func.isRequired, + setClickedFeature: PropTypes.func.isRequired, +}; diff --git a/front/src/applications/opendata/components/OpenDataHelpers.js b/front/src/applications/opendata/components/OpenDataHelpers.js new file mode 100644 index 00000000000..e5255fed4a1 --- /dev/null +++ b/front/src/applications/opendata/components/OpenDataHelpers.js @@ -0,0 +1,45 @@ +import rollingstockOpenData2OSRD from 'applications/opendata/components/rollingstock_opendata2osrd.json'; + +export function seconds2hhmmss(seconds) { + const dateTime = new Date(seconds * 1000); + return dateTime.toJSON().substring(11, 19); +} + +// Look for unique pathways by concatenation of duration & coordinates +// Compare them & create dictionnary, associate reference of unique path to each train +export function refactorUniquePaths( + trains, + setTrainsWithPathRef, + setPathsDictionnary, + setPointsDictionnary +) { + const pathsDictionnary = []; + const trainsWithPathRef = []; + const pointsList = {}; + trains.forEach((train) => { + const pathString = + train.etapes + .map((step, idx) => + idx === 0 || idx === step.length - 1 + ? `${step.duree}${step.lat}${step.lon}` + : `0${step.lat}${step.lon}` + ) + .join() + rollingstockOpenData2OSRD[train.type_em]; + const pathRef = pathsDictionnary.find((entry) => entry.pathString === pathString); + if (pathRef === undefined) { + pathsDictionnary.push({ + num: train.num, + pathString, + }); + trainsWithPathRef.push({ ...train, pathRef: train.num }); + } else { + trainsWithPathRef.push({ ...train, pathRef: pathRef.num }); + } + train.etapes.forEach((step) => { + pointsList[step.uic] = { lng: step.lon, lat: step.lat, name: step.gare }; + }); + }); + setTrainsWithPathRef(trainsWithPathRef); + setPathsDictionnary(pathsDictionnary); + setPointsDictionnary(pointsList); +} diff --git a/front/src/applications/opendata/components/StationSelector.js b/front/src/applications/opendata/components/StationSelector.js new file mode 100644 index 00000000000..7ff1974dd28 --- /dev/null +++ b/front/src/applications/opendata/components/StationSelector.js @@ -0,0 +1,107 @@ +import React, { useEffect, useState, memo } from 'react'; +import PropTypes from 'prop-types'; +import { get } from 'axios'; +import InputSNCF from 'common/BootstrapSNCF/InputSNCF'; +import { useDebounce } from 'utils/helpers'; +import { useTranslation } from 'react-i18next'; +import Loader from 'common/Loader'; +import nextId from 'react-id-generator'; +import { GRAOU_URL } from '../consts'; + +export function formatStation(stationData) { + return ( +
+
+ {stationData.code} + {stationData.localite} + {stationData.chantier} +
+
+ {stationData.commune} + {stationData.departement} / + {stationData.region} + {stationData.uic} +
+ {stationData.ligne ? ( +
+ {stationData.libelle} + {stationData.pk ? PK {stationData.pk} : null} + {stationData.ligne} +
+ ) : null} +
+ ); +} + +function StationSelector(props) { + const { id, onSelect, term, setTerm } = props; + const { t } = useTranslation(['opendata']); + const [stationsList, setStationsList] = useState(); + const [isSearching, setIsSearching] = useState(false); + const debouncedTerm = useDebounce(term, 500); + + async function getStation() { + setIsSearching(true); + try { + const params = { + q: 'stations', + term, + }; + const result = await get(`${GRAOU_URL}/api/stations.php`, { params }); + setStationsList(result.data); + setIsSearching(false); + } catch (error) { + console.log(error); + setIsSearching(false); + } + } + useEffect(() => { + if (debouncedTerm) { + getStation(); + } else { + setStationsList(undefined); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [debouncedTerm]); + + return ( + <> + setTerm(e.target.value)} + placeholder={t('inputPlaceholder')} + sm + noMargin + isInvalid={stationsList && stationsList.length === 0} + unit={stationsList && stationsList.length > 0 ? stationsList.length.toString() : ''} + focus + selectAllOnFocus + /> + {stationsList && stationsList.length > 0 ? ( +
+ {stationsList.map((station) => ( +
onSelect(station)} key={nextId()}> + {formatStation(station)} +
+ ))} +
+ ) : null} + {isSearching ? : null} + + ); +} + +StationSelector.defaultProps = { + term: '', +}; + +StationSelector.propTypes = { + onSelect: PropTypes.func.isRequired, + term: PropTypes.string, + setTerm: PropTypes.func.isRequired, + id: PropTypes.string.isRequired, +}; + +export const MemoStationSelector = memo(StationSelector); diff --git a/front/src/applications/opendata/components/TrainDetail.js b/front/src/applications/opendata/components/TrainDetail.js new file mode 100644 index 00000000000..10dc1e4bc96 --- /dev/null +++ b/front/src/applications/opendata/components/TrainDetail.js @@ -0,0 +1,64 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import nextId from 'react-id-generator'; +import { seconds2hhmmss } from 'applications/opendata/components/OpenDataHelpers'; +import RollingStock2Img from 'applications/osrd/components/RollingStock/RollingStock2Img'; +import rollingstockOpenData2OSRD from './rollingstock_opendata2osrd.json'; + +export default function TrainDetail(props) { + const { trainData, idx } = props; + const [isOpened, setIsOpened] = useState(false); + return ( +
setIsOpened(!isOpened)} + > +
+ {idx + 1} + + {trainData.num} + {trainData.origine} + + + {trainData.debut} - {trainData.fin} + + {seconds2hhmmss(trainData.duree)} + + + + + + {trainData.num_transilien} + + {trainData.etapes.length - 2} + +
+
+ {trainData.etapes.map((step, stepIdx) => + // Remove origin & destination + stepIdx !== 0 && stepIdx !== trainData.etapes.length - 1 ? ( +
+ {stepIdx} + + {`${step.debut} - ${step.fin}`} + + {step.duree}s + {step.gare} +
+ ) : null + )} +
+
+ ); +} + +TrainDetail.propTypes = { + trainData: PropTypes.object.isRequired, + idx: PropTypes.number.isRequired, +}; diff --git a/front/src/applications/opendata/components/generatePathfindingPayload.js b/front/src/applications/opendata/components/generatePathfindingPayload.js new file mode 100644 index 00000000000..fba0b7952bb --- /dev/null +++ b/front/src/applications/opendata/components/generatePathfindingPayload.js @@ -0,0 +1,43 @@ +import rollingstockOpenData2OSRD from 'applications/opendata/components/rollingstock_opendata2osrd.json'; + +function formatSteps(pointsDictionnary, trainFromPathRef, autoComplete) { + if (autoComplete) { + return trainFromPathRef.etapes.map((step, idx) => ({ + duration: idx === 0 || idx === trainFromPathRef.etapes.length - 1 ? 0 : step.duree, + op_trigram: step.code, + })); + } + return trainFromPathRef.etapes.map((step, idx) => ({ + duration: idx === 0 || idx === trainFromPathRef.etapes.length - 1 ? 0 : step.duree, + waypoints: [ + { + track_section: pointsDictionnary[step.uic].trackSectionId, + geo_coordinate: [Number(step.lon), Number(step.lat)], + }, + ], + })); +} + +export default function generatePathfindingPayload( + infraID, + rollingStockID, + trainsWithPathRef, + pathsDictionnary, + pointsDictionnary, + rollingStockDB, + autoComplete +) { + const pathsToGenerate = {}; + pathsDictionnary.forEach((pathRef) => { + const trainFromPathRef = trainsWithPathRef.find((train) => train.num === pathRef.num); + const rollingStockFound = rollingStockDB.find( + (rollingstock) => rollingstock.name === rollingstockOpenData2OSRD[trainFromPathRef.type_em] + ); + pathsToGenerate[pathRef.num] = { + infra: infraID, + rolling_stocks: [rollingStockFound ? rollingStockFound.id : rollingStockID], + steps: formatSteps(pointsDictionnary, trainFromPathRef, autoComplete), + }; + }); + return pathsToGenerate; +} diff --git a/front/src/applications/opendata/components/generateTrainSchedulesPayload.js b/front/src/applications/opendata/components/generateTrainSchedulesPayload.js new file mode 100644 index 00000000000..869b27971ed --- /dev/null +++ b/front/src/applications/opendata/components/generateTrainSchedulesPayload.js @@ -0,0 +1,21 @@ +import { time2sec } from 'utils/timeManipulation'; + +export default function generateTrainSchedulesPayload(trainsWithPathRef, infraID, timetableID) { + const trainSchedulesByPathID = {}; + trainsWithPathRef.forEach((train) => { + if (!trainSchedulesByPathID[train.pathId]) { + trainSchedulesByPathID[train.pathId] = { + timetable: timetableID, + path: train.pathId, + schedules: [], + }; + } + trainSchedulesByPathID[train.pathId].schedules.push({ + train_name: train.num, + rolling_stock: train.rollingStockId, + departure_time: time2sec(train.debut), + initial_speed: 0, + }); + }); + return trainSchedulesByPathID; +} diff --git a/front/src/applications/opendata/components/rollingstock_opendata2osrd.json b/front/src/applications/opendata/components/rollingstock_opendata2osrd.json new file mode 100644 index 00000000000..60d29df8ecc --- /dev/null +++ b/front/src/applications/opendata/components/rollingstock_opendata2osrd.json @@ -0,0 +1,128 @@ +{ + "AGC3C": "1X76503", + "AGC4C": "1X76504", + "AGC4CLOR": "1X76504", + "AGCUM3C": "2X76503", + "AGCUM3C/4C": "2X76503", + "AGCUM4C": "2X76504", + "AM96": "SNCB1Z96", + "B5": "26000US", + "BB15000": "15000", + "BB15000B6": "15000", + "BB15000V2N": "15000", + "BB17000": "17000G", + "BB17000~RIB": "17000G", + "BB22000B5": "22200G", + "BB22200": "22200BM", + "BB22200~RRR": "22200G", + "BB22200~VR2N": "22200G", + "BB25500": "25500G", + "BB25500RRR3C": "25500G", + "BB25500RRR6C": "25500G", + "BB26000": "26000US", + "BB26000B5": "26000US", + "BB27000": "27000UM", + "BB27300": "CIM192L", + "BB27300~VB2N": "CIM192L", + "BB36000": "36000US", + "BB37000": "37000US", + "BB60000": "60000US", + "BB63000": "63001A", + "BB63500": "63500A", + "BB64600": "BB64600", + "BB66000": "66000A", + "BB66400": "66400A", + "BB66700": "66700", + "BB67200": "67200US", + "BB67300": "67300AG", + "BB67300RRR3C": "67300AG", + "BB67400": "67400A", + "BB67400CORAIL": "67400A", + "BB67400RRR3C": "67400A", + "BB67400RRR6C": "67400A", + "BB69200": "69400US", + "BB69400_Infra": "69400US", + "BB7200": "7200GH", + "BB7200B5": "7200GH", + "BB75000": "75000US", + "BB7600": "7600REV", + "BB8500": "8500G", + "BB8500~VO2N": "8500G", + "BB9300": "BB9300", + "BGC4C": "1Z81504", + "BGCUM4C": "2Z81504", + "CC72000": "72001BG", + "CC72100": "72001BG", + "CORAIL": "CORAIL", + "EUROSTAR": "TMSTEURO", + "ICE": "1ICE3", + "PPG": "1X83506", + "PPM": "1X83504", + "PPMUM": "2X83504", + "REGIO2N": "1Z56500", + "RIB": "RIB", + "RIO": "RIO", + "RRR": "RRR", + "S100": "S100", + "TGVA": "1TGVA", + "TGVD": "1TGVDUPL", + "TGVPOS": "1TGVPOS", + "TGVR": "1TGVR", + "TGVSE": "1TGVOSE", + "THALYS": "1TGVPBKA", + "TYPE13": "TYPE13", + "U25500": "1U52504", + "U52500": "1U52504", + "U53500": "1U53504", + "UM3ZGCALSLO": "3Z81504", + "UMBGC/ZGC": "2Z81504", + "V2N": "1TGV2N2", + "X2100": "X2100M10", + "X2100UM": "X2100M20", + "X2100UM3": "X2100M31", + "X2200": "X2200", + "X4750": "11X4547", + "X4900": "X4900S21", + "X72500": "1X725002", + "X73500": "1X73500", + "X73500/900UM": "2X73500", + "X73500/900UM3": "3X73500", + "X73500UM": "2X73500", + "X73500UM3": "3X73500", + "X73900": "1X73500", + "X73900UM": "2X73500", + "X73900UM3": "3X73500", + "Y8000": "Y8000AG", + "Z100": "Z100TJ", + "Z11500": "Z1150011", + "Z11500UM": "Z1150022", + "Z11500UM3": "Z1150033", + "Z150": "Z150", + "Z20500": "Z2050022", + "Z20900": "1Z20900", + "Z21500": "Z215001", + "Z21500UM": "Z215002", + "Z21500UM3": "Z215003", + "Z22500": "Z22500", + "Z23500": "TER2N", + "Z24500": "1Z24502", + "Z26500": "1Z26504", + "Z275LOR4C": "1Z27504", + "Z50000": "1Z50007", + "Z5300": "Z5300B12", + "Z5600": "1Z56500", + "Z6300": "Z630012", + "Z6400": "Z6400JUS", + "Z7300": "Z7300PP1", + "Z7500": "Z7500", + "Z800": "Z800MM", + "Z8100": "MI79US", + "Z850": "Z800MM", + "Z9500": "Z950011", + "Z9600": "Z950011", + "Z9600UM": "Z950022", + "ZGC3C": "1Z27503", + "ZGC4C": "1Z27504", + "ZGC4CALSUM": "2Z27504", + "ZGC4CUM": "2Z27504" +} \ No newline at end of file diff --git a/front/src/applications/opendata/consts.js b/front/src/applications/opendata/consts.js new file mode 100644 index 00000000000..b5cd2094072 --- /dev/null +++ b/front/src/applications/opendata/consts.js @@ -0,0 +1,26 @@ +export const GRAOU_URL = 'https://graou.info'; +// export const GRAOU_URL = 'http://localhost/graou'; + +export const initialViewport = { + latitude: 48.86521728735368, + longitude: 2.341549498045856, + zoom: 10.257506947953921, + bearing: 0, + pitch: 0, + width: 420, + height: 320, + altitude: 1.5, + maxZoom: 24, + minZoom: 0, + maxPitch: 60, + minPitch: 0, + transitionDuration: 100, +}; + +export const initialStatus = { + uicComplete: false, + pathFindingDone: false, + trainSchedulesDone: false, +}; + +export const itineraryURI = '/pathfinding/'; diff --git a/front/src/applications/opendata/opendata.scss b/front/src/applications/opendata/opendata.scss new file mode 100644 index 00000000000..85dcd88f327 --- /dev/null +++ b/front/src/applications/opendata/opendata.scss @@ -0,0 +1,264 @@ +.opendata-import { + --opendatagreen: #329623; + + .opendata-import-global-settings { + background-color: var(--coolgray9); + border-radius: 4px; + padding: .25rem .5rem 0; + margin-bottom: .5rem; + transition: .2s; + &:hover { + background-color: var(--coolgray7); + } + .opendata-import-global-settings-bar { + display: flex; + align-items: center; + justify-content: flex-end; + cursor: pointer; + color: var(--light); + padding: 0 0 .25rem; + } + .opendata-import-global-settings-items { + display: none; + &.active { + display: block; + } + } + } + + .osrd-config-item-from { + background-color: var(--green); + } + .osrd-config-item-to { + background-color: var(--yellow); + } + + .results-stations { + height: 15rem; + overflow: auto; + } + .results-stations, .result-station-selected { + border-radius: 4px; + position: relative; + margin-top: .5rem; + .results-station-data { + background-color: var(--white); + flex-grow: 1; + cursor: pointer; + border: 2px solid var(--coolgray1); + padding-top: .15rem; + margin-bottom: .25rem; + border-radius: 4px; + transition: .2s; + &:hover { + border-color: var(--coolgray9); + } + .station-data-head { + display: flex; + align-items: baseline; + padding: 0 .25rem; + height: 1.5rem; + .station-data-code { + font-size: .8rem; + width: 2.25rem; + } + .station-data-name { + overflow: hidden; + white-space: nowrap; + font-size: 1.1rem; + } + .station-data-ch { + margin-left: auto; + font-weight: 500; + font-size: .8rem; + } + } + .station-data-localization { + display: flex; + align-items: baseline; + position: relative; + padding: 0 .25rem 0 2.5rem; + height: 1rem; + line-height: .8rem; + color: var(--coolgray9); + gap: .25rem; + .station-data-city { + font-size: .7rem; + } + .station-data-department { + text-transform: uppercase; + font-size: .6rem; + } + .station-data-region { + text-transform: uppercase; + font-size: .6rem; + } + .station-data-uic { + margin-left: auto; + font-size: .6rem; + color: var(--primary); + } + } + .station-data-footer { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 .25rem; + font-size: .7rem; + background-color: var(--coolgray1); + border-radius: 0 0 4px 4px; + height: 1rem; + .station-data-line { + overflow: hidden; + white-space: nowrap; + color: var(--coolgray9); + } + .station-data-pk { + overflow: hidden; + white-space: nowrap; + margin-left: auto; + margin-right: .25rem; + font-size: .6rem; + } + .station-data-line-number { + font-size: .6rem; + color: var(--purple); + } + } + } + } + + .opendata-trainlist { + position: relative; + .opendata-trainlist-launchbar { + display: flex; + align-items: center; + padding: .5rem; + border-radius: 4px 4px 0 0; + height: 2.5rem; + color: var(--light); + background-color: var(--secondary); + } + .opendata-trainlist-results { + position: relative; + overflow: auto; + max-height: calc(100vh - 17.5rem); + .opendata-traindetail { + position: relative; + margin-bottom: .5rem; + border: 2px solid var(--coolgray3); + border-radius: 4px; + .opendata-traindetail-main { + display: flex; + align-items: baseline; + // justify-content: space-between; + cursor: pointer; + background-color: var(--coolgray1); + padding: .5rem; + transition: .2s; + &:hover { + background-color: var(--coolgray3); + } + .opendata-traindetail-idx { + width: 1.5rem; + font-size: .7rem; + } + .opendata-traindetail-num { + width: 4.5rem; + font-size: 1.2rem; + font-weight: bold; + color: var(--coolgray9); + height: 2rem; + .opendata-traindetail-activity { + display: block; + font-weight: normal; + text-transform: uppercase; + font-size: .6rem; + line-height: .5rem; + } + } + .opendata-traindetail-startend { + width: 9rem; + font-size: .9rem; + text-align: center; + } + .opendata-traindetail-duration { + width: 3.5rem; + font-size: .8rem; + } + .opendata-traindetail-rollingstock { + align-self: flex-start; + overflow: hidden; + height: 2rem; + width: 50%; + .opendata-traindetail-rollingstock-img { + display: flex; + align-items: baseline; + transform: scale(.4); + transform-origin: top; + } + } + .opendata-traindetail-transilien { + color: var(--blue); + font-weight: 500; + margin-left: auto; + text-transform: uppercase; + margin-right: .5rem; + font-size: .8rem; + } + .opendata-traindetail-stepnb { + color: var(--light); + font-weight: bold; + font-size: .8rem; + border-radius: 1rem; + padding: .5rem; + line-height: 1rem; + width: 2rem; + height: 2rem; + text-align: center; + } + } + .opendata-traindetail-steps { + overflow: auto; + max-height: 0rem; + transition: .2s; + &.opened { + max-height: 10rem; + } + .opendata-traindetail-step { + display: flex; + align-items: baseline; + margin-left: 5rem; + &:hover { + background-color: var(--coolgray1); + } + .opendata-traindetail-step-nb { + width: 2rem; + text-align: right; + padding: 0 .5rem; + font-size: .6rem; + } + .opendata-traindetail-step-startend { + width: 8.5rem; + font-size: .9rem; + } + .opendata-traindetail-step-duration { + width: 2rem; + margin-right: .5rem; + text-align: right; + font-size: .8rem; + } + .opendata-traindetail-step-name { + font-size: .9rem; + } + } + } + } + } + } + .automated-map { + position: relative; + width: 100%; + height: 20rem; + } +} \ No newline at end of file diff --git a/front/src/applications/opendata/views/OpenDataGlobalSettings.js b/front/src/applications/opendata/views/OpenDataGlobalSettings.js new file mode 100644 index 00000000000..557414a3cb7 --- /dev/null +++ b/front/src/applications/opendata/views/OpenDataGlobalSettings.js @@ -0,0 +1,64 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import InfraSelector from 'common/InfraSelector/InfraSelector'; +import RollingStockSelector from 'applications/osrd/views/OSRDConfig/RollingStockSelector'; +import TimetableSelector from 'applications/osrd/views/OSRDConfig/TimetableSelector'; +import { useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; +import { getInfraID, getRollingStockID, getTimetableID } from 'reducers/osrdconf/selectors'; + +function OpenDataGlobalSettings(props) { + const { mustUpdateTimetable, setMustUpdateTimetable } = props; + const infraID = useSelector(getInfraID); + const rollingStockID = useSelector(getRollingStockID); + const timetableID = useSelector(getTimetableID); + const [showGlobalSettings, setShowGlobalSettings] = useState( + !infraID || !timetableID || !rollingStockID + ); + const { t } = useTranslation(['opendata']); + + return ( +
+
setShowGlobalSettings(!showGlobalSettings)} + > + {showGlobalSettings ? ( + <> + {t('closeOSRDConfig')} + + + ) : ( + <> + {t('openOSRDConfig')} + + + )} +
+
+
+
+ + +
+
+ +
+
+
+
+ ); +} +export default React.memo(OpenDataGlobalSettings); + +OpenDataGlobalSettings.propTypes = { + mustUpdateTimetable: PropTypes.bool.isRequired, + setMustUpdateTimetable: PropTypes.func.isRequired, +}; diff --git a/front/src/applications/opendata/views/OpenDataImportConfig.js b/front/src/applications/opendata/views/OpenDataImportConfig.js new file mode 100644 index 00000000000..176250014aa --- /dev/null +++ b/front/src/applications/opendata/views/OpenDataImportConfig.js @@ -0,0 +1,178 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import PropTypes from 'prop-types'; +import InputSNCF from 'common/BootstrapSNCF/InputSNCF'; +import { MemoStationSelector, formatStation } from 'applications/opendata/components/StationSelector'; +import { setFailure } from 'reducers/main'; +import { useDispatch } from 'react-redux'; + +function dateOfToday() { + const date = new Date(); + return date.toJSON().substring(0, 10); +} + +export default function OpenDataImportConfig(props) { + const { setConfig } = props; + const { t } = useTranslation(['opendata']); + const [from, setFrom] = useState(); + const [fromSearchString, setFromSearchString] = useState(''); + const [to, setTo] = useState(); + const [toSearchString, setToSearchString] = useState(''); + const [date, setDate] = useState(dateOfToday()); + const [startTime, setStartTime] = useState('00:00'); + const [endTime, setEndTime] = useState('23:59'); + const dispatch = useDispatch(); + + function defineConfig() { + let error = false; + if (!from) { + dispatch( + setFailure({ name: t('errorMessages.error'), message: t('errorMessages.errorNoFrom') }) + ); + error = true; + } + if (!to) { + dispatch( + setFailure({ name: t('errorMessages.error'), message: t('errorMessages.errorNoTo') }) + ); + error = true; + } + if (!date) { + dispatch( + setFailure({ name: t('errorMessages.error'), message: t('errorMessages.errorNoDate') }) + ); + error = true; + } + if (JSON.stringify(from) === JSON.stringify(to)) { + dispatch( + setFailure({ name: t('errorMessages.error'), message: t('errorMessages.errorSameFromTo') }) + ); + error = true; + } + + if (!error) { + setConfig({ + from, + to, + date, + startTime, + endTime, + }); + } + } + + return ( +
+
+
+
+

{t('from')}

+ {from ? ( +
setFrom(undefined)} + role="button" + tabIndex={0} + > + {formatStation(from)} +
+ ) : ( + + )} +
+
+
+
+
+
+

{t('to')}

+ {to ? ( +
setTo(undefined)} + role="button" + tabIndex={0} + > + {formatStation(to)} +
+ ) : ( + + )} +
+
+
+
+
+
+

{t('datetime')}

+
+
+
+ setDate(e.target.value)} + sm + noMargin + step={0} + unit={t('date')} + /> +
+
+ + setStartTime(e.target.value)} + sm + noMargin + step={0} + unit={t('startTime')} + /> + + + setEndTime(e.target.value)} + sm + noMargin + step={0} + unit={t('endTime')} + /> + +
+
+
+ +
+
+
+
+
+
+ ); +} + +OpenDataImportConfig.propTypes = { + setConfig: PropTypes.func.isRequired, +}; diff --git a/front/src/applications/opendata/views/OpenDataImportModal.js b/front/src/applications/opendata/views/OpenDataImportModal.js new file mode 100644 index 00000000000..b7825efe465 --- /dev/null +++ b/front/src/applications/opendata/views/OpenDataImportModal.js @@ -0,0 +1,327 @@ +import React, { useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; +import ModalSNCF from 'common/BootstrapSNCF/ModalSNCF/ModalSNCF'; +import ModalBodySNCF from 'common/BootstrapSNCF/ModalSNCF/ModalBodySNCF'; +import Map from 'applications/opendata/components/Map'; +import { useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; +import { getRollingStockID, getInfraID, getTimetableID } from 'reducers/osrdconf/selectors'; +import generatePathfindingPayload from 'applications/opendata/components/generatePathfindingPayload'; +import generateTrainSchedulesPayload from 'applications/opendata/components/generateTrainSchedulesPayload'; +import { post } from 'common/requests'; +import { scheduleURL } from 'applications/osrd/components/Simulation/consts'; +import { initialViewport, initialStatus, itineraryURI } from 'applications/opendata/consts'; +import OpenDataImportModalFooter from './OpenDataImportModalFooter'; +import { refactorUniquePaths } from '../components/OpenDataHelpers'; + +/* METHOD + * + * 1: complete data in interactive way, proposing user to select position of each point/OP + * OR automatically use pathfindin/op endpoint to choose a path + * 2: generate missing paths with new datas (or skipped part if automatically generated in phase 1) + * 3: calculate trainSchedules and add them to the selected timetable + * 4: two links come visible: stdcm or simulation + * + */ + +export default function OpenDataImportModal(props) { + const { rollingStockDB, setMustUpdateTimetable, trains } = props; + const { t } = useTranslation('translation', 'opendata'); + const infraID = useSelector(getInfraID); + const rollingStockID = useSelector(getRollingStockID); + const timetableID = useSelector(getTimetableID); + + const [trainsWithPathRef, setTrainsWithPathRef] = useState([]); + + // Places, points, OPs to add track section id + const [pointsDictionnary, setPointsDictionnary] = useState(); + const [clickedFeature, setClickedFeature] = useState(); + const [uicNumberToComplete, setUicNumberToComplete] = useState(); + + // Path to compute + const [pathsDictionnary, setPathsDictionnary] = useState(); + + const [whatIAmDoingNow, setWhatIAmDoingNow] = useState(t('opendata:status.ready')); + + const [viewport, setViewport] = useState(initialViewport); + const [status, setStatus] = useState(initialStatus); + + function testMissingInfos() { + const messages = []; + if (!infraID) messages.push(t('opendata:status.missingInfra')); + if (!rollingStockID) messages.push(t('opendata:status.missingRollingStock')); + if (!timetableID) messages.push(t('opendata:status.missingTimetable')); + if (messages.length > 0) { + setWhatIAmDoingNow( + + {[t('opendata:status.noImportationPossible'), ''].concat(messages).join('\n')} + + ); + } else { + setWhatIAmDoingNow(t('opendata:status.ready')); + } + } + + function getTrackSectionID(lat, lng) { + setViewport({ + ...viewport, + latitude: Number(lat), + longitude: Number(lng), + pitch: 0, + bearing: 0, + zoom: 18, + }); + } + + // + // 1 COMPLETE DATA INTERACTIVE WAY + // + + function completePaths(init = false) { + if (init) { + setStatus(initialStatus); + setUicNumberToComplete(undefined); + } + const uic2complete = Object.keys(pointsDictionnary); + const uicNumberToCompleteLocal = + uicNumberToComplete === undefined || init ? 0 : uicNumberToComplete + 1; + if (uicNumberToCompleteLocal < uic2complete.length) { + setUicNumberToComplete(uicNumberToCompleteLocal); + getTrackSectionID( + pointsDictionnary[uic2complete[uicNumberToCompleteLocal]].lat, + pointsDictionnary[uic2complete[uicNumberToCompleteLocal]].lng + ); + setWhatIAmDoingNow( + `${uicNumberToCompleteLocal}/${uic2complete.length} ${t('opendata:status.complete')} ${ + pointsDictionnary[uic2complete[uicNumberToCompleteLocal]].name + }` + ); + } else { + setWhatIAmDoingNow(t('opendata:status.uicComplete')); + setUicNumberToComplete(undefined); + setStatus({ ...status, uicComplete: true }); + } + } + + // + // 2 GENERATE PATHS (autocomplete to automatically look for OPs) + // + + async function launchPathfinding( + params, + pathRefNum, + pathNumberToComplete, + pathsIDs, + continuePath, + autoComplete + ) { + try { + const itineraryCreated = autoComplete + ? await post(`${itineraryURI}op/`, params, {}, true) + : await post(itineraryURI, params, {}, true); + continuePath( + pathNumberToComplete + 1, + { + ...pathsIDs, + [pathRefNum]: { pathId: itineraryCreated.id, rollingStockId: params.rolling_stocks[0] }, + }, + autoComplete + ); + } catch (e) { + setWhatIAmDoingNow( + + {t('opendata:errorMessages.unableToRetrievePathfinding')} + + ); + setStatus(initialStatus); + console.log('ERROR', e); + } + } + + function generatePaths(pathNumberToComplete = 0, pathsIDs = {}, autoComplete = false) { + if (autoComplete && pathNumberToComplete === 0) { + setStatus({ ...initialStatus, uicComplete: true }); + setUicNumberToComplete(undefined); + } + + const pathfindingPayloads = generatePathfindingPayload( + infraID, + rollingStockID, + trainsWithPathRef, + pathsDictionnary, + pointsDictionnary, + rollingStockDB, + autoComplete + ); + + const path2complete = Object.keys(pathfindingPayloads); + if (pathNumberToComplete < path2complete.length) { + setWhatIAmDoingNow( + `${pathNumberToComplete}/${path2complete.length} ${t('opendata:status.searchingPath')} ${ + path2complete[pathNumberToComplete] + }` + ); + launchPathfinding( + pathfindingPayloads[path2complete[pathNumberToComplete]], + path2complete[pathNumberToComplete], + pathNumberToComplete, + pathsIDs, + generatePaths, + autoComplete + ); + } else { + setWhatIAmDoingNow(t('opendata:status.pathComplete')); + setTrainsWithPathRef( + trainsWithPathRef.map((train) => ({ + ...train, + pathId: pathsIDs[train.pathRef].pathId, + rollingStockId: pathsIDs[train.pathRef].rollingStockId, + })) + ); + setStatus({ ...status, uicComplete: true, pathFindingDone: true }); + } + } + + // + // 3 CALCULATE TRAINS SCHEDULES + // + + async function launchTrainSchedules(params) { + try { + await post(scheduleURL, params, {}); + return `${t('opendata:status.calculatingTrainScheduleComplete')} (${params.path})`; + } catch (error) { + console.log(error); + } + return `${t('opendata:status.calculatingTrainScheduleError')} (${params.path})`; + } + async function generateTrainSchedules() { + const payload = generateTrainSchedulesPayload(trainsWithPathRef, infraID, timetableID); + setWhatIAmDoingNow(`${t('opendata:status.calculatingTrainSchedule')}`); + const messages = []; + const promisesList = []; + // eslint-disable-next-line no-restricted-syntax + for (const [idx, params] of Object.values(payload).entries()) { + // eslint-disable-next-line no-await-in-loop + const message = await launchTrainSchedules(params); + promisesList.push(message); + messages.push(`${message} ${idx + 1}/${Object.values(payload).length}`); + setWhatIAmDoingNow(messages.join('\n')); + } + Promise.all(promisesList).then(() => { + setStatus({ ...status, trainSchedulesDone: true }); + setMustUpdateTimetable(true); + setWhatIAmDoingNow(t('opendata:status.calculatingTrainScheduleCompleteAll')); + }); + } + + useEffect(() => { + if (clickedFeature) { + const actualUic = Object.keys(pointsDictionnary)[uicNumberToComplete]; + setPointsDictionnary({ + ...pointsDictionnary, + [actualUic]: { + ...pointsDictionnary[actualUic], + trackSectionId: clickedFeature.properties.id, + }, + }); + + completePaths(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [clickedFeature]); + + useEffect(() => { + if (rollingStockDB && trains && trains.length > 0) { + refactorUniquePaths(trains, setTrainsWithPathRef, setPathsDictionnary, setPointsDictionnary); + } + }, [trains, rollingStockDB]); + + useEffect(() => { + testMissingInfos(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [infraID, rollingStockID, timetableID]); + + return ( + + {pathsDictionnary && trainsWithPathRef.length > 0 ? ( + + {!infraID || !timetableID || !rollingStockID ? null : ( + <> + +
{t('opendata:or')}
+ + + + +
+ + )} + +
{whatIAmDoingNow}
+ + {uicNumberToComplete !== undefined ? ( +
+ +
+ ) : null} +
+ ) : ( + '' + )} + +
+ ); +} + +OpenDataImportModal.defaultProps = { + rollingStockDB: [], +}; + +OpenDataImportModal.propTypes = { + trains: PropTypes.array.isRequired, + rollingStockDB: PropTypes.array, + setMustUpdateTimetable: PropTypes.func.isRequired, +}; diff --git a/front/src/applications/opendata/views/OpenDataImportModalFooter.js b/front/src/applications/opendata/views/OpenDataImportModalFooter.js new file mode 100644 index 00000000000..5d4977b2cad --- /dev/null +++ b/front/src/applications/opendata/views/OpenDataImportModalFooter.js @@ -0,0 +1,44 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { useTranslation } from 'react-i18next'; +import ModalFooterSNCF from 'common/BootstrapSNCF/ModalSNCF/ModalFooterSNCF'; +import { useNavigate } from 'react-router-dom'; + +export default function OpenDataImportModalFooter(props) { + const { status } = props; + const { t } = useTranslation('translation', 'opendata'); + const navigate = useNavigate(); + return ( + +
+ {Object.values(status).every((el) => el) ? ( +
+ + +
+ ) : null} + +
+
+ ); +} + +OpenDataImportModalFooter.propTypes = { + status: PropTypes.object.isRequired, +}; diff --git a/front/src/applications/opendata/views/OpenDataTrainsList.js b/front/src/applications/opendata/views/OpenDataTrainsList.js new file mode 100644 index 00000000000..7682b5b9599 --- /dev/null +++ b/front/src/applications/opendata/views/OpenDataTrainsList.js @@ -0,0 +1,104 @@ +import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import PropTypes from 'prop-types'; +import Loader from 'common/Loader'; +import { get } from 'axios'; +import nextId from 'react-id-generator'; +import TrainDetail from 'applications/opendata/components/TrainDetail'; +import OpenDataImportModal from 'applications/opendata/views/OpenDataImportModal'; +import { GoRocket } from 'react-icons/go'; +import { GRAOU_URL } from '../consts'; + +function LoadingIfSearching(props) { + const { t } = useTranslation(['opendata']); + const { isSearching } = props; + return ( +

+ {isSearching ? : t('noResults')} +

+ ); +} + +export default function OpenDataTrainsList(props) { + const { t } = useTranslation(['opendata']); + const { config, rollingStockDB, setMustUpdateTimetable } = props; + const [trainsList, setTrainList] = useState(); + const [isSearching, setIsSearching] = useState(false); + + async function getTrains() { + setTrainList(undefined); + setIsSearching(true); + try { + const params = { + q: 'trains', + config, + }; + const result = await get(`${GRAOU_URL}/api/trainschedules.php`, { params }); + setTrainList(result.data); + setIsSearching(false); + } catch (error) { + console.log(error); + setIsSearching(false); + } + } + + useEffect(() => { + if (config) { + getTrains(); + } else { + setTrainList(undefined); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [config]); + + return trainsList && trainsList.length > 0 ? ( +
+
+
+ + {trainsList.length} {t('trainsFound')} + + +
+
+ {trainsList.map((train, idx) => ( + + ))} +
+
+ +
+ ) : ( +
+
+ +
+
+ ); +} + +OpenDataTrainsList.defaultProps = { + config: undefined, +}; + +LoadingIfSearching.propTypes = { + isSearching: PropTypes.bool.isRequired, +}; + +OpenDataTrainsList.propTypes = { + config: PropTypes.object, + rollingStockDB: PropTypes.array.isRequired, + setMustUpdateTimetable: PropTypes.func.isRequired, +}; diff --git a/front/src/applications/osrd/components/Itinerary/ModalSuggeredVias.js b/front/src/applications/osrd/components/Itinerary/ModalSuggeredVias.js index 8030f43e381..9e372621213 100644 --- a/front/src/applications/osrd/components/Itinerary/ModalSuggeredVias.js +++ b/front/src/applications/osrd/components/Itinerary/ModalSuggeredVias.js @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import nextId from 'react-id-generator'; import { useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; import ModalSNCF from 'common/BootstrapSNCF/ModalSNCF/ModalSNCF'; @@ -33,7 +34,7 @@ export default function ModalSugerredVias(props) { const formatVia = (via, idx, idxTrueVia) => (
{!via.suggestion && {idxTrueVia}} diff --git a/front/src/applications/osrd/components/RollingStock/RollingStock.js b/front/src/applications/osrd/components/RollingStock/RollingStock.js index b20d2a301b0..f580c074526 100644 --- a/front/src/applications/osrd/components/RollingStock/RollingStock.js +++ b/front/src/applications/osrd/components/RollingStock/RollingStock.js @@ -18,7 +18,7 @@ import { enhanceData } from './RollingStockHelpers'; const ROLLING_STOCK_URL = '/light_rolling_stock/'; -export default function RollingStock(props) { +function RollingStock(props) { const { ref2scroll } = props; const dispatch = useDispatch(); const { darkmode } = useSelector((state) => state.main); @@ -211,3 +211,6 @@ export default function RollingStock(props) { RollingStock.propTypes = { ref2scroll: PropTypes.object.isRequired, }; + +const MemoizedRollingStock = React.memo(RollingStock); +export default MemoizedRollingStock; diff --git a/front/src/applications/osrd/components/RollingStock/RollingStock2Img.js b/front/src/applications/osrd/components/RollingStock/RollingStock2Img.js index 3c433cbb0ae..ecc749b45ef 100644 --- a/front/src/applications/osrd/components/RollingStock/RollingStock2Img.js +++ b/front/src/applications/osrd/components/RollingStock/RollingStock2Img.js @@ -12,13 +12,17 @@ function cleanGifName(gifName) { export default function RollingStock2Img(props) { const { name } = props; - return mlgTraffic[name] + return name && mlgTraffic[name] ? mlgTraffic[name].map((gif) => ( )) : null; } +RollingStock2Img.defaultProps = { + name: undefined, +}; + RollingStock2Img.propTypes = { - name: PropTypes.string.isRequired, + name: PropTypes.string, }; diff --git a/front/src/applications/osrd/components/RollingStock/RollingStockEmpty.js b/front/src/applications/osrd/components/RollingStock/RollingStockEmpty.js index 45b50e6d848..4e99c468812 100644 --- a/front/src/applications/osrd/components/RollingStock/RollingStockEmpty.js +++ b/front/src/applications/osrd/components/RollingStock/RollingStockEmpty.js @@ -1,6 +1,6 @@ import React from 'react'; import { FaQuestion } from 'react-icons/fa'; -import icon from 'assets/pictures/train.svg'; +import icon from 'assets/pictures/components/train.svg'; export default function RollingStockEmpty() { return ( diff --git a/front/src/applications/osrd/components/TimetableSelector/TimetableSelectorModal.js b/front/src/applications/osrd/components/TimetableSelector/TimetableSelectorModal.js index 256ec2dc70a..135107ad255 100644 --- a/front/src/applications/osrd/components/TimetableSelector/TimetableSelectorModal.js +++ b/front/src/applications/osrd/components/TimetableSelector/TimetableSelectorModal.js @@ -9,7 +9,7 @@ import ModalHeaderSNCF from 'common/BootstrapSNCF/ModalSNCF/ModalHeaderSNCF'; import ModalBodySNCF from 'common/BootstrapSNCF/ModalSNCF/ModalBodySNCF'; import InputSNCF from 'common/BootstrapSNCF/InputSNCF'; import { setSuccess, setFailure } from 'reducers/main'; -import icon from 'assets/pictures/trains_timetable.png'; +import icon from 'assets/pictures/components/trains_timetable.svg'; const timetableURL = '/timetable/'; @@ -65,7 +65,8 @@ export default function TimetableSelectorModal() { }; try { - await post(timetableURL, params, {}); + const result = await post(timetableURL, params, {}); + dispatch(updateTimetableID(result.id)); } catch (e) { console.log('ERROR', e); } diff --git a/front/src/applications/osrd/views/OSRDConfig/AddTrainSchedule.js b/front/src/applications/osrd/views/OSRDConfig/AddTrainSchedule.js index b40410c7106..de25c1b0721 100644 --- a/front/src/applications/osrd/views/OSRDConfig/AddTrainSchedule.js +++ b/front/src/applications/osrd/views/OSRDConfig/AddTrainSchedule.js @@ -15,7 +15,7 @@ import trainNameWithNum from 'applications/osrd/components/AddTrainSchedule/trai import { scheduleURL } from 'applications/osrd/components/Simulation/consts'; export default function AddTrainSchedule(props) { - const { mustUpdateTimetable, setMustUpdateTimetable } = props; + const { setMustUpdateTimetable } = props; const [name, setName] = useState(undefined); const [isWorking, setIsWorking] = useState(false); const [trainCount, setTrainCount] = useState(1); @@ -68,7 +68,7 @@ export default function AddTrainSchedule(props) { }) ); } - setMustUpdateTimetable(!mustUpdateTimetable); + setMustUpdateTimetable(true); } }; @@ -79,6 +79,7 @@ export default function AddTrainSchedule(props) { const handleNameChange = useCallback((newName) => { setName(newName); debouncedUpdateName(newName); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { @@ -148,6 +149,5 @@ export default function AddTrainSchedule(props) { } AddTrainSchedule.propTypes = { - mustUpdateTimetable: PropTypes.bool.isRequired, setMustUpdateTimetable: PropTypes.func.isRequired, }; diff --git a/front/src/applications/osrd/views/OSRDConfig/Itinerary.js b/front/src/applications/osrd/views/OSRDConfig/Itinerary.js index 6455403f779..28aa4cc4b1a 100644 --- a/front/src/applications/osrd/views/OSRDConfig/Itinerary.js +++ b/front/src/applications/osrd/views/OSRDConfig/Itinerary.js @@ -226,18 +226,21 @@ function Itinerary(props) { zoomToFeature(bbox(osrdconf.geojson[map.mapTrackSources])); } setLaunchPathfinding(true); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { if (launchPathfinding) { mapItinerary(); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [osrdconf.origin, osrdconf.destination, map.mapTrackSources, osrdconf.rollingStockID]); useEffect(() => { if (launchPathfinding) { mapItinerary(false); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [osrdconf.vias]); return ( diff --git a/front/src/applications/osrd/views/OSRDConfig/Map.tsx b/front/src/applications/osrd/views/OSRDConfig/Map.tsx index f02ce9f058f..9c867fe50e5 100644 --- a/front/src/applications/osrd/views/OSRDConfig/Map.tsx +++ b/front/src/applications/osrd/views/OSRDConfig/Map.tsx @@ -187,7 +187,7 @@ function Map() { touchZoomRotate > - + state.osrdconf); + const timetableID = useSelector((state) => state.osrdconf.timetableID); const dispatch = useDispatch(); const { t } = useTranslation(); const getTimetable = async (id) => { + setIsWorking(true); try { const timetableQuery = await get(`${timetableURL}${id}/`, {}); timetableQuery.train_schedules.sort((a, b) => a.departure_time > b.departure_time); - setselectedTimetable(timetableQuery); + setSelectedTimetable(timetableQuery); setTrainList(timetableQuery.train_schedules); + setIsWorking(false); } catch (e) { dispatch(updateTimetableID(undefined)); dispatch( @@ -37,6 +40,7 @@ export default function TimetableSelector(props) { }) ); console.log('ERROR', e); + setIsWorking(false); } }; @@ -65,33 +69,48 @@ export default function TimetableSelector(props) {
)); + useEffect(() => { + if (timetableID !== undefined && mustUpdateTimetable) { + getTimetable(timetableID); + setMustUpdateTimetable(false); + } else { + setSelectedTimetable(undefined); + setMustUpdateTimetable(false); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [mustUpdateTimetable]); + useEffect(() => { if (timetableID !== undefined) { getTimetable(timetableID); } else { - setselectedTimetable(undefined); + setSelectedTimetable(undefined); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [timetableID, mustUpdateTimetable]); - let timeTable = {t('osrdconf:noTimetable')}; - if (timetableID !== undefined && selectedTimetable === undefined) { - timeTable = ( - - - - ); - } else if (selectedTimetable !== undefined) { - timeTable = ( - <> - {selectedTimetable.name} - {selectedTimetable.id} - - {`${selectedTimetable.train_schedules.length} ${t('translation:common.train(s)')}`} + const timeTable = () => { + if (isWorking) { + return ( + + - - ); - } + ); + } + if (selectedTimetable !== undefined) { + return ( + <> + {selectedTimetable.name} + {selectedTimetable.id} + + {`${selectedTimetable.train_schedules.length} ${t('translation:common.train(s)')}`} + + + ); + } + return {t('osrdconf:noTimetable')}; + }; + return ( <>
@@ -104,7 +123,7 @@ export default function TimetableSelector(props) { >
timetableIcon - {timeTable} + {timeTable()}
{timetableID !== undefined && trainList !== undefined && trainList.length > 0 ? ( @@ -120,4 +139,5 @@ export default function TimetableSelector(props) { TimetableSelector.propTypes = { mustUpdateTimetable: PropTypes.bool.isRequired, + setMustUpdateTimetable: PropTypes.func.isRequired, }; diff --git a/front/src/applications/osrd/views/OSRDSimulation/Map.tsx b/front/src/applications/osrd/views/OSRDSimulation/Map.tsx index 915f8fa02dd..1cd7d5c2951 100644 --- a/front/src/applications/osrd/views/OSRDSimulation/Map.tsx +++ b/front/src/applications/osrd/views/OSRDSimulation/Map.tsx @@ -344,7 +344,7 @@ const Map: FC = ({ setExtViewport }) => { onLoad={handleLoadFinished} > - + { @@ -226,12 +227,14 @@ export default function TrainsList(props) { ); dispatch(updateMustRedraw(true)); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [debouncedInputTime]); useEffect(() => { if (!onInput) { setFormattedList(formatTrainsList()); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedTrain, departureArrivalTimes, filter, trainNameClickedIDX, typeOfInputFocused]); return ( diff --git a/front/src/assets/pictures/carto.png b/front/src/assets/pictures/carto.png deleted file mode 100644 index 9eea247498e..00000000000 Binary files a/front/src/assets/pictures/carto.png and /dev/null differ diff --git a/front/src/assets/pictures/components/speedometer.svg b/front/src/assets/pictures/components/speedometer.svg new file mode 100644 index 00000000000..dd36e6d1581 --- /dev/null +++ b/front/src/assets/pictures/components/speedometer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/assets/pictures/components/tracks.svg b/front/src/assets/pictures/components/tracks.svg new file mode 100644 index 00000000000..e5147f8a78f --- /dev/null +++ b/front/src/assets/pictures/components/tracks.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/assets/pictures/components/tracks_edit.svg b/front/src/assets/pictures/components/tracks_edit.svg new file mode 100644 index 00000000000..34c312c77ff --- /dev/null +++ b/front/src/assets/pictures/components/tracks_edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/assets/pictures/components/train.svg b/front/src/assets/pictures/components/train.svg new file mode 100644 index 00000000000..75b03eb55ae --- /dev/null +++ b/front/src/assets/pictures/components/train.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/assets/pictures/components/trains_timetable.svg b/front/src/assets/pictures/components/trains_timetable.svg new file mode 100644 index 00000000000..fb73afe75c6 --- /dev/null +++ b/front/src/assets/pictures/components/trains_timetable.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/assets/pictures/customget.png b/front/src/assets/pictures/customget.png deleted file mode 100644 index c82af1f92d5..00000000000 Binary files a/front/src/assets/pictures/customget.png and /dev/null differ diff --git a/front/src/assets/pictures/dgexsollogo.png b/front/src/assets/pictures/dgexsollogo.png deleted file mode 100644 index d11f96739de..00000000000 Binary files a/front/src/assets/pictures/dgexsollogo.png and /dev/null differ diff --git a/front/src/assets/pictures/dgexsollogo_only.png b/front/src/assets/pictures/dgexsollogo_only.png deleted file mode 100644 index 7587e86ff6a..00000000000 Binary files a/front/src/assets/pictures/dgexsollogo_only.png and /dev/null differ diff --git a/front/src/assets/pictures/editor.png b/front/src/assets/pictures/editor.png deleted file mode 100644 index b2f79c081ba..00000000000 Binary files a/front/src/assets/pictures/editor.png and /dev/null differ diff --git a/front/src/assets/pictures/home/customget.svg b/front/src/assets/pictures/home/customget.svg new file mode 100644 index 00000000000..d46dc796cad --- /dev/null +++ b/front/src/assets/pictures/home/customget.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/assets/pictures/home/editor.svg b/front/src/assets/pictures/home/editor.svg new file mode 100644 index 00000000000..dd24a7f1793 --- /dev/null +++ b/front/src/assets/pictures/home/editor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/assets/pictures/home/map.svg b/front/src/assets/pictures/home/map.svg new file mode 100644 index 00000000000..d510f67571c --- /dev/null +++ b/front/src/assets/pictures/home/map.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/assets/pictures/home/opendata.svg b/front/src/assets/pictures/home/opendata.svg new file mode 100644 index 00000000000..d9b4344fcc9 --- /dev/null +++ b/front/src/assets/pictures/home/opendata.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/assets/pictures/home/stdcm.svg b/front/src/assets/pictures/home/stdcm.svg new file mode 100644 index 00000000000..691f62fa186 --- /dev/null +++ b/front/src/assets/pictures/home/stdcm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/assets/pictures/home/timetable.svg b/front/src/assets/pictures/home/timetable.svg new file mode 100644 index 00000000000..b5512ae88eb --- /dev/null +++ b/front/src/assets/pictures/home/timetable.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/assets/pictures/layersicons/bufferstop.svg b/front/src/assets/pictures/layersicons/bufferstop.svg index ec9af2854ff..c187df7a298 100644 --- a/front/src/assets/pictures/layersicons/bufferstop.svg +++ b/front/src/assets/pictures/layersicons/bufferstop.svg @@ -1,78 +1 @@ - - - - - - - - - image/svg+xml - - - - - - - - - - - - + \ No newline at end of file diff --git a/front/src/assets/pictures/layersicons/detectors.svg b/front/src/assets/pictures/layersicons/detectors.svg index 96c97822ff3..50481d00e35 100644 --- a/front/src/assets/pictures/layersicons/detectors.svg +++ b/front/src/assets/pictures/layersicons/detectors.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/front/src/assets/pictures/layersicons/layer_adv.svg b/front/src/assets/pictures/layersicons/layer_adv.svg index c57e78cd410..47e2b70022e 100644 --- a/front/src/assets/pictures/layersicons/layer_adv.svg +++ b/front/src/assets/pictures/layersicons/layer_adv.svg @@ -1,82 +1 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/front/src/assets/pictures/layersicons/layer_itineraire.svg b/front/src/assets/pictures/layersicons/layer_itineraire.svg index 5730434fbca..5b79ff3de95 100644 --- a/front/src/assets/pictures/layersicons/layer_itineraire.svg +++ b/front/src/assets/pictures/layersicons/layer_itineraire.svg @@ -1,103 +1 @@ - - - - - - - - - - image/svg+xml - - - - - - - - A - B - - - - - - +AB \ No newline at end of file diff --git a/front/src/assets/pictures/layersicons/layer_jdz.svg b/front/src/assets/pictures/layersicons/layer_jdz.svg index fe952bd5f37..5768c26f5c1 100644 --- a/front/src/assets/pictures/layersicons/layer_jdz.svg +++ b/front/src/assets/pictures/layersicons/layer_jdz.svg @@ -1,80 +1 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/front/src/assets/pictures/layersicons/layer_pn.svg b/front/src/assets/pictures/layersicons/layer_pn.svg index 9363459b331..71440111a88 100644 --- a/front/src/assets/pictures/layersicons/layer_pn.svg +++ b/front/src/assets/pictures/layersicons/layer_pn.svg @@ -1,116 +1 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/front/src/assets/pictures/layersicons/layer_sel.svg b/front/src/assets/pictures/layersicons/layer_sel.svg deleted file mode 100644 index 2c3be2ebb70..00000000000 --- a/front/src/assets/pictures/layersicons/layer_sel.svg +++ /dev/null @@ -1,250 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/front/src/assets/pictures/layersicons/layer_signal.svg b/front/src/assets/pictures/layersicons/layer_signal.svg index cff0050a39d..6204124e930 100644 --- a/front/src/assets/pictures/layersicons/layer_signal.svg +++ b/front/src/assets/pictures/layersicons/layer_signal.svg @@ -1,106 +1 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/front/src/assets/pictures/layersicons/layer_signal_open.svg b/front/src/assets/pictures/layersicons/layer_signal_open.svg index 782e9b5fc5d..4fca56d5d74 100644 --- a/front/src/assets/pictures/layersicons/layer_signal_open.svg +++ b/front/src/assets/pictures/layersicons/layer_signal_open.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/front/src/assets/pictures/layersicons/layer_stops.svg b/front/src/assets/pictures/layersicons/layer_stops.svg index 53876553eff..0f741de9960 100644 --- a/front/src/assets/pictures/layersicons/layer_stops.svg +++ b/front/src/assets/pictures/layersicons/layer_stops.svg @@ -1,62 +1 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/front/src/assets/pictures/layersicons/layer_tiv.svg b/front/src/assets/pictures/layersicons/layer_tiv.svg deleted file mode 100644 index f967e4829d9..00000000000 --- a/front/src/assets/pictures/layersicons/layer_tiv.svg +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - V2 - - - diff --git a/front/src/assets/pictures/layersicons/layer_tivs.svg b/front/src/assets/pictures/layersicons/layer_tivs.svg index f789cd2a313..1c05aeb2ff9 100644 --- a/front/src/assets/pictures/layersicons/layer_tivs.svg +++ b/front/src/assets/pictures/layersicons/layer_tivs.svg @@ -1,66 +1 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/front/src/assets/pictures/layersicons/layer_zep.svg b/front/src/assets/pictures/layersicons/layer_zep.svg index 719bd2c2284..0db8a3abd45 100644 --- a/front/src/assets/pictures/layersicons/layer_zep.svg +++ b/front/src/assets/pictures/layersicons/layer_zep.svg @@ -1,173 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/front/src/assets/pictures/layersicons/ops.svg b/front/src/assets/pictures/layersicons/ops.svg index c4e2e1ad5e4..aad62ce94b9 100644 --- a/front/src/assets/pictures/layersicons/ops.svg +++ b/front/src/assets/pictures/layersicons/ops.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/front/src/assets/pictures/layersicons/switches.svg b/front/src/assets/pictures/layersicons/switches.svg index 23f7d10de4b..35da0134971 100644 --- a/front/src/assets/pictures/layersicons/switches.svg +++ b/front/src/assets/pictures/layersicons/switches.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/front/src/assets/pictures/layersicons/timetable.svg b/front/src/assets/pictures/layersicons/timetable.svg deleted file mode 100644 index 64a4b47ad02..00000000000 --- a/front/src/assets/pictures/layersicons/timetable.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/front/src/assets/pictures/map.svg b/front/src/assets/pictures/map.svg deleted file mode 100644 index 89b26fd4aa1..00000000000 --- a/front/src/assets/pictures/map.svg +++ /dev/null @@ -1 +0,0 @@ -map \ No newline at end of file diff --git a/front/src/assets/pictures/mapstyle-blueprint.jpg b/front/src/assets/pictures/mapbuttons/mapstyle-blueprint.jpg similarity index 100% rename from front/src/assets/pictures/mapstyle-blueprint.jpg rename to front/src/assets/pictures/mapbuttons/mapstyle-blueprint.jpg diff --git a/front/src/assets/pictures/mapstyle-cadastre.jpg b/front/src/assets/pictures/mapbuttons/mapstyle-cadastre.jpg similarity index 100% rename from front/src/assets/pictures/mapstyle-cadastre.jpg rename to front/src/assets/pictures/mapbuttons/mapstyle-cadastre.jpg diff --git a/front/src/assets/pictures/mapstyle-dark.jpg b/front/src/assets/pictures/mapbuttons/mapstyle-dark.jpg similarity index 100% rename from front/src/assets/pictures/mapstyle-dark.jpg rename to front/src/assets/pictures/mapbuttons/mapstyle-dark.jpg diff --git a/front/src/assets/pictures/mapstyle-normal.jpg b/front/src/assets/pictures/mapbuttons/mapstyle-normal.jpg similarity index 100% rename from front/src/assets/pictures/mapstyle-normal.jpg rename to front/src/assets/pictures/mapbuttons/mapstyle-normal.jpg diff --git a/front/src/assets/pictures/mapstyle-ortho.jpg b/front/src/assets/pictures/mapbuttons/mapstyle-ortho.jpg similarity index 100% rename from front/src/assets/pictures/mapstyle-ortho.jpg rename to front/src/assets/pictures/mapbuttons/mapstyle-ortho.jpg diff --git a/front/src/assets/pictures/mapstyle-osm-tracks.jpg b/front/src/assets/pictures/mapbuttons/mapstyle-osm-tracks.jpg similarity index 100% rename from front/src/assets/pictures/mapstyle-osm-tracks.jpg rename to front/src/assets/pictures/mapbuttons/mapstyle-osm-tracks.jpg diff --git a/front/src/assets/pictures/mapstyle-scan25.jpg b/front/src/assets/pictures/mapbuttons/mapstyle-scan25.jpg similarity index 100% rename from front/src/assets/pictures/mapstyle-scan25.jpg rename to front/src/assets/pictures/mapbuttons/mapstyle-scan25.jpg diff --git a/front/src/assets/pictures/osm.png b/front/src/assets/pictures/mapbuttons/osm.png similarity index 100% rename from front/src/assets/pictures/osm.png rename to front/src/assets/pictures/mapbuttons/osm.png diff --git a/front/src/assets/pictures/osmLight.png b/front/src/assets/pictures/mapbuttons/osmLight.png similarity index 100% rename from front/src/assets/pictures/osmLight.png rename to front/src/assets/pictures/mapbuttons/osmLight.png diff --git a/front/src/assets/pictures/searchicons/signalbox.svg b/front/src/assets/pictures/searchicons/signalbox.svg deleted file mode 100644 index c62676f79ae..00000000000 --- a/front/src/assets/pictures/searchicons/signalbox.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/front/src/assets/pictures/searchicons/station.svg b/front/src/assets/pictures/searchicons/station.svg deleted file mode 100644 index dfaf412c5fc..00000000000 --- a/front/src/assets/pictures/searchicons/station.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/front/src/assets/pictures/searchicons/track.svg b/front/src/assets/pictures/searchicons/track.svg deleted file mode 100644 index 0adc8c6fa0e..00000000000 --- a/front/src/assets/pictures/searchicons/track.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/front/src/assets/pictures/speedometer.svg b/front/src/assets/pictures/speedometer.svg deleted file mode 100644 index 6f4d8900bd6..00000000000 --- a/front/src/assets/pictures/speedometer.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/front/src/assets/pictures/timetable.png b/front/src/assets/pictures/timetable.png deleted file mode 100644 index 916d0a5aba1..00000000000 Binary files a/front/src/assets/pictures/timetable.png and /dev/null differ diff --git a/front/src/assets/pictures/tracks.png b/front/src/assets/pictures/tracks.png deleted file mode 100644 index 5fadc25f3fe..00000000000 Binary files a/front/src/assets/pictures/tracks.png and /dev/null differ diff --git a/front/src/assets/pictures/tracks_edit.png b/front/src/assets/pictures/tracks_edit.png deleted file mode 100644 index f1cdc5cd55f..00000000000 Binary files a/front/src/assets/pictures/tracks_edit.png and /dev/null differ diff --git a/front/src/assets/pictures/train.svg b/front/src/assets/pictures/train.svg deleted file mode 100644 index 965bffcb058..00000000000 --- a/front/src/assets/pictures/train.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/front/src/assets/pictures/trains_timetable.png b/front/src/assets/pictures/trains_timetable.png deleted file mode 100644 index 2ac1489e0d2..00000000000 Binary files a/front/src/assets/pictures/trains_timetable.png and /dev/null differ diff --git a/front/src/common/BootstrapSNCF/CardSNCF/CardSNCF.js b/front/src/common/BootstrapSNCF/CardSNCF/CardSNCF.js index 47477518c2d..00b3b6e3acf 100644 --- a/front/src/common/BootstrapSNCF/CardSNCF/CardSNCF.js +++ b/front/src/common/BootstrapSNCF/CardSNCF/CardSNCF.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Link } from 'react-router-dom'; import { connect } from 'react-redux'; +import './CardSNCF.scss'; class Card extends React.Component { static propTypes = { diff --git a/front/src/common/BootstrapSNCF/CardSNCF/CardSNCF.scss b/front/src/common/BootstrapSNCF/CardSNCF/CardSNCF.scss new file mode 100644 index 00000000000..94f01a93be3 --- /dev/null +++ b/front/src/common/BootstrapSNCF/CardSNCF/CardSNCF.scss @@ -0,0 +1,10 @@ +.card-img-top { + padding: 2rem; +} +.card-body { + display: flex; + align-items: center; + justify-content: center; + padding: .5rem; + height: 4rem; +} \ No newline at end of file diff --git a/front/src/common/BootstrapSNCF/InputSNCF.js b/front/src/common/BootstrapSNCF/InputSNCF.js index 71768a34d6b..e8479689a5c 100644 --- a/front/src/common/BootstrapSNCF/InputSNCF.js +++ b/front/src/common/BootstrapSNCF/InputSNCF.js @@ -51,7 +51,7 @@ class InputSNCF extends React.Component { name: PropTypes.string.isRequired, }), // Styling props - unit: PropTypes.string, + unit: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), sm: PropTypes.bool, whiteBG: PropTypes.bool, noMargin: PropTypes.bool, diff --git a/front/src/common/InfraSelector/InfraSelector.js b/front/src/common/InfraSelector/InfraSelector.js index a20f5d83c69..72a20423fd0 100644 --- a/front/src/common/InfraSelector/InfraSelector.js +++ b/front/src/common/InfraSelector/InfraSelector.js @@ -4,7 +4,7 @@ import { useDispatch, useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; import { setFailure } from 'reducers/main'; import { get } from 'common/requests'; -import icon from 'assets/pictures/tracks.png'; +import icon from 'assets/pictures/components/tracks.svg'; import InfraSelectorModal from 'common/InfraSelector/InfraSelectorModal'; import nextId from 'react-id-generator'; import { getInfraID } from 'reducers/osrdconf/selectors'; diff --git a/front/src/common/InfraSelector/InfraSelectorModal.js b/front/src/common/InfraSelector/InfraSelectorModal.js index 0a5698a9eaa..f0aaaa73531 100644 --- a/front/src/common/InfraSelector/InfraSelectorModal.js +++ b/front/src/common/InfraSelector/InfraSelectorModal.js @@ -4,8 +4,8 @@ import { useTranslation } from 'react-i18next'; import ModalSNCF from 'common/BootstrapSNCF/ModalSNCF/ModalSNCF'; import ModalHeaderSNCF from 'common/BootstrapSNCF/ModalSNCF/ModalHeaderSNCF'; import ModalBodySNCF from 'common/BootstrapSNCF/ModalSNCF/ModalBodySNCF'; -import icon from 'assets/pictures/tracks.png'; -import iconEdition from 'assets/pictures/tracks_edit.png'; +import icon from 'assets/pictures/components/tracks.svg'; +import iconEdition from 'assets/pictures/components/tracks_edit.svg'; import { get } from 'common/requests'; import { useDebounce } from 'utils/helpers'; import Loader from 'common/Loader'; diff --git a/front/src/common/Map/Map.scss b/front/src/common/Map/Map.scss index ec44504c2ba..2b03aee8da9 100644 --- a/front/src/common/Map/Map.scss +++ b/front/src/common/Map/Map.scss @@ -24,7 +24,7 @@ position: absolute; z-index: 0; min-height: 5rem; - max-height: 75vh; + max-height: calc(100% - 25vh); overflow: auto; top: 4rem; right: 3.5rem; diff --git a/front/src/common/Map/Settings/MapSettingsBackgroundSwitches.js b/front/src/common/Map/Settings/MapSettingsBackgroundSwitches.js index 8817e3e5416..bcb01137aa7 100644 --- a/front/src/common/Map/Settings/MapSettingsBackgroundSwitches.js +++ b/front/src/common/Map/Settings/MapSettingsBackgroundSwitches.js @@ -9,11 +9,11 @@ import { updateShowOSMtracksections, } from 'reducers/map'; import SwitchSNCF, { SWITCH_TYPES } from 'common/BootstrapSNCF/SwitchSNCF/SwitchSNCF'; -import iconIGNBDORTHO from 'assets/pictures/mapstyle-ortho.jpg'; -import iconIGNSCAN25 from 'assets/pictures/mapstyle-scan25.jpg'; -import iconIGNCadastre from 'assets/pictures/mapstyle-cadastre.jpg'; -import iconOSM from 'assets/pictures/mapstyle-normal.jpg'; -import iconOSMTracks from 'assets/pictures/mapstyle-osm-tracks.jpg'; +import iconIGNBDORTHO from 'assets/pictures/mapbuttons/mapstyle-ortho.jpg'; +import iconIGNSCAN25 from 'assets/pictures/mapbuttons/mapstyle-scan25.jpg'; +import iconIGNCadastre from 'assets/pictures/mapbuttons/mapstyle-cadastre.jpg'; +import iconOSM from 'assets/pictures/mapbuttons/mapstyle-normal.jpg'; +import iconOSMTracks from 'assets/pictures/mapbuttons/mapstyle-osm-tracks.jpg'; export default function MapSettingsBackgroundSwitches() { const { t } = useTranslation(['map-settings']); diff --git a/front/src/common/Map/Settings/MapSettingsMapStyle.js b/front/src/common/Map/Settings/MapSettingsMapStyle.js index 68f96209eb8..4ed79b064bd 100644 --- a/front/src/common/Map/Settings/MapSettingsMapStyle.js +++ b/front/src/common/Map/Settings/MapSettingsMapStyle.js @@ -2,9 +2,9 @@ import React from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { useTranslation } from 'react-i18next'; import { updateMapStyle } from 'reducers/map'; -import picNormalMode from 'assets/pictures/mapstyle-normal.jpg'; -import picDarkMode from 'assets/pictures/mapstyle-dark.jpg'; -import picBlueprint from 'assets/pictures/mapstyle-blueprint.jpg'; +import picNormalMode from 'assets/pictures/mapbuttons/mapstyle-normal.jpg'; +import picDarkMode from 'assets/pictures/mapbuttons/mapstyle-dark.jpg'; +import picBlueprint from 'assets/pictures/mapbuttons/mapstyle-blueprint.jpg'; export default function MapSettingsMapStyle() { const { t } = useTranslation(['map-settings']); diff --git a/front/src/main/App/index.js b/front/src/main/App/index.js index fbbd9202524..323f27a78b9 100644 --- a/front/src/main/App/index.js +++ b/front/src/main/App/index.js @@ -9,6 +9,7 @@ import HomeCarto from 'applications/carto/Home'; import HomeEditor from 'applications/editor/Home'; import HomeOSRD from 'applications/osrd/Home'; import HomeStdcm from 'applications/stdcm/Home'; +import HomeOpenData from 'applications/opendata/Home'; import HomeCustomGET from 'applications/customget/Home'; import Loader from 'common/Loader'; import { attemptLoginOnLaunch } from 'reducers/user'; @@ -29,6 +30,7 @@ export default function App() { } else { dispatch(attemptLoginOnLaunch()); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Conditionnal theming @@ -43,6 +45,7 @@ export default function App() { // Loading initial data useEffect(() => { dispatch(bootstrapOSRDConf(infraID)); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( @@ -54,6 +57,7 @@ export default function App() { } /> } /> } /> + } /> } /> } /> diff --git a/front/src/main/Home/Home.css b/front/src/main/Home/Home.css deleted file mode 100644 index 48409848b80..00000000000 --- a/front/src/main/Home/Home.css +++ /dev/null @@ -1,17 +0,0 @@ -.mastcontainer-no-mastnav { - padding-left: 0 !important; -} -.cardscontainer { - margin: 2rem; -} -.card { - cursor: pointer; - transition: .2s ease-in-out; -} -.card:hover { - opacity: .5; -} - -.disabled-link { - pointer-events: none; -} diff --git a/front/src/main/Home/Home.scss b/front/src/main/Home/Home.scss new file mode 100644 index 00000000000..d667e77d681 --- /dev/null +++ b/front/src/main/Home/Home.scss @@ -0,0 +1,40 @@ +.mastcontainer-no-mastnav { + padding-left: 0 !important; +} +.cardscontainer { + margin: 2rem; +} +.card { + cursor: pointer; + transition: .2s ease-in-out; +} +.card:hover { + opacity: .5; +} + +.disabled-link { + pointer-events: none; +} + +.application-title { + display: flex; + align-items: center; + justify-content: center; + padding: 1rem 0 0; + h1 { + padding-top: 2rem; + font-size: 3rem; + color: var(--coolgray9); + } + img { + width: 8rem; + } + @media screen and (max-width: 1024px) { + h1 { + font-size: 1.5rem; + } + img { + width: 4rem; + } + } +} \ No newline at end of file diff --git a/front/src/main/Home/index.js b/front/src/main/Home/index.js index 5cf8c515b7d..01cd2d910cb 100644 --- a/front/src/main/Home/index.js +++ b/front/src/main/Home/index.js @@ -1,45 +1,50 @@ -import './Home.css'; +import './Home.scss'; import Card from 'common/BootstrapSNCF/CardSNCF/CardSNCF'; import NavBarSNCF from 'common/BootstrapSNCF/NavBarSNCF'; import React from 'react'; -import cartoPic from 'assets/pictures/carto.png'; -import editorPic from 'assets/pictures/editor.png'; +import mapImg from 'assets/pictures/home/map.svg'; +import editorImg from 'assets/pictures/home/editor.svg'; +import stdcmImg from 'assets/pictures/home/stdcm.svg'; +import timetableImg from 'assets/pictures/home/timetable.svg'; +import customgetImg from 'assets/pictures/home/customget.svg'; +import opendataImg from 'assets/pictures/home/opendata.svg'; import logo from 'assets/logo_osrd_seul_blanc.svg'; import osrdLogo from 'assets/pictures/osrd.png'; -import timetablePic from 'assets/pictures/timetable.png'; -import customget from 'assets/pictures/customget.png'; import { useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; export default function Home() { const user = useSelector((state) => state.user); - const { t } = useTranslation(); + const { t } = useTranslation('home'); return ( <>
-
- OSRD logo +
+ OSRD logo

Open-Source Railway Designer

-
- +
+
-
- +
+
-
- +
+
-
- +
+
-
- +
+ +
+
+