Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

front: stdcm: update simulation progress button design #9786

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions front/public/locales/en/stdcm.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
"indicateAnteriorPath": "Indicate anterior path",
"indicatePosteriorPath": "Indicate posterior path",
"leaveAt": "Leave at {{ time }}",
"loaderImageLegend": "Morning view of the locomotive shed at Le Bourget depot",
"noConfigurationFound": {
"title": "A configuration problem prevents you from performing a search",
"text": "Please contact the maintenance team so they can get you back on track."
Expand All @@ -35,9 +34,9 @@
"notificationTitle": "Phase 1: from D-7 to D-1 5pm, on the Perrigny-Miramas axis.",
"pleaseWait": "Please wait…",
"simulation": {
"averageRequestTime": "For your request, the time required is generally 90 seconds.",
"calculatingSimulation": "Calculation in progress...",
"getSimulation": "Get the simulation",
"infoMessage": "This simulation doesn't ensure the availability of the path.",
"modifySearchCriteria": "You can modify your search criteria to find a solution.",
"pendingSimulation": "Simulation in progress",
"results": {
Expand Down Expand Up @@ -67,8 +66,8 @@
"startNewQuery": "Start a new query",
"status": {
"completed": "Calculation completed",
"errorMessage": "We are sorry that we could not process your request. The STDCM team has been notified and will be aware of the situation soon.",
"failed": "Calculation failed"
"errorMessage": "We are sorry that we could not process your request. The LMR team has been notified and will be aware of the situation as soon as possible.",
"failed": "A technical problem occurred"
},
"upgrade": {
"arrivalIncompatible": "Arrival time incompatible",
Expand All @@ -79,7 +78,7 @@
"unqualifiedDriver": "Unqualified driver"
}
},
"stopCalculation": "Stop calculation"
"stopCalculation": "Stop"
},
"spaceTimeGraphic": "Space-Time graph",
"speedSpaceChart": "Speed Space Chart",
Expand Down
9 changes: 4 additions & 5 deletions front/public/locales/fr/stdcm.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
"indicateAnteriorPath": "Indiquer le sillon antérieur",
"indicatePosteriorPath": "Indiquer le sillon postérieur",
"leaveAt": "Partir à {{ time }}",
"loaderImageLegend": "Vue matinale du relais de locomotives du dépôt du Bourget",
"noConfigurationFound": {
"title": "Un problème de configuration vous empêche de faire une recherche",
"text": "Veuillez contacter l'équipe de maintenance pour qu'elle puisse vous remettre sur les rails."
Expand All @@ -35,9 +34,9 @@
"notificationTitle": "Phase 1 : de J-7 à J-1 17h, sur l’axe Perrigny—Miramas.",
"pleaseWait": "Veuillez patientez…",
"simulation": {
"averageRequestTime": "Pour votre demande, le temps nécessaire est généralement de 90 secondes.",
"calculatingSimulation": "Calcul en cours...",
"getSimulation": "Obtenir la simulation",
"infoMessage": "Cette simulation n'offre pas la garantie de disponibilité du sillon.",
"modifySearchCriteria": "Vous pouvez modifier vos critères de recherche pour trouver une solution.",
"pendingSimulation": "simulation en cours",
"results": {
Expand Down Expand Up @@ -67,8 +66,8 @@
"startNewQuery": "Démarrez une nouvelle requête",
"status": {
"completed": "Calcul terminé",
"errorMessage": "Nous sommes désolé de ne pas avoir pu traiter votre demande. L’équipe STDCM a été notifiée et prendra connaissance de la situation prochainement.",
"failed": "Le calcul n'a pas abouti"
"errorMessage": "Nous sommes désolés de ne pas avoir pu traiter votre demande. L’équipe LMR a été notifiée et prendra connaissance de la situation dès que possible.",
"failed": "Un problème technique est survenu"
},
"upgrade": {
"arrivalIncompatible": "Heure arrivée incompatible",
Expand All @@ -79,7 +78,7 @@
"unqualifiedDriver": "Conducteur non habilité"
}
},
"stopCalculation": "Arrêter le calcul"
"stopCalculation": "Arrêter"
},
"spaceTimeGraphic": "Graphique Espace-Temps",
"speedSpaceChart": "Graphique Espace Vitesse",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';

import { Button } from '@osrd-project/ui-core';
import { ArrowDown, ArrowUp } from '@osrd-project/ui-icons';
Expand All @@ -24,6 +24,7 @@ import StdcmSimulationParams from '../StdcmSimulationParams';
import StdcmVias from './StdcmVias';
import { ArrivalTimeTypes, StdcmConfigErrorTypes } from '../../types';
import checkStdcmConfigErrors from '../../utils/checkStdcmConfigErrors';
import StdcmLoader from '../StdcmLoader';
import StdcmWarningBox from '../StdcmWarningBox';

/**
Expand All @@ -36,6 +37,7 @@ type StdcmConfigProps = {
launchStdcmRequest: () => Promise<void>;
retainedSimulationIndex: number;
showBtnToLaunchSimulation: boolean;
cancelStdcmRequest: () => void;
};

const StdcmConfig = ({
Expand All @@ -44,8 +46,10 @@ const StdcmConfig = ({
launchStdcmRequest,
retainedSimulationIndex,
showBtnToLaunchSimulation,
cancelStdcmRequest,
}: StdcmConfigProps) => {
const { t } = useTranslation('stdcm');
const launchButtonRef = useRef<HTMLDivElement>(null);

const { infra } = useInfraStatus();
const dispatch = useAppDispatch();
Expand All @@ -72,6 +76,7 @@ const StdcmConfig = ({
const scenarioID = useSelector(getScenarioID);

const pathfinding = useStaticPathfinding(infra);
const formRef = useRef<HTMLDivElement>(null);

const [formErrors, setFormErrors] = useState<StdcmConfigErrors>();

Expand Down Expand Up @@ -156,7 +161,7 @@ const StdcmConfig = ({
<StdcmConsist disabled={disabled} />
</div>
<div className="stdcm__separator" />
<div className="stdcm-simulation-itinerary">
<div ref={formRef} className="stdcm-simulation-itinerary">
<StdcmOrigin disabled={disabled} />
<StdcmVias disabled={disabled} />
<StdcmDestination disabled={disabled} />
Expand All @@ -168,18 +173,22 @@ const StdcmConfig = ({
className="posterior-linked-path"
linkedOp={{ extremityType: 'origin', id: destination.id }}
/>

<div
className={cx('stdcm-launch-request', {
'wizz-effect': pathfinding?.status !== 'success' || formErrors,
})}
ref={launchButtonRef}
>
{showBtnToLaunchSimulation && (
<Button
data-testid="launch-simulation-button"
label={t('simulation.getSimulation')}
onClick={startSimulation}
/>
)}
<Button
data-testid="launch-simulation-button"
className={cx({
'fade-out': !showBtnToLaunchSimulation,
})}
label={t('simulation.getSimulation')}
onClick={startSimulation}
isDisabled={disabled || !showBtnToLaunchSimulation}
/>
{formErrors && (
<StdcmWarningBox
errorInfos={formErrors}
Expand All @@ -188,6 +197,14 @@ const StdcmConfig = ({
/>
)}
</div>

{isPending && (
<StdcmLoader
cancelStdcmRequest={cancelStdcmRequest}
launchButtonRef={launchButtonRef}
formRef={formRef}
/>
)}
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ const StdcmConsist = ({ disabled = false }: StdcmConfigCardProps) => {
min={0}
value={totalMass ?? ''}
onChange={onTotalMassChange}
disabled={disabled}
/>
<Input
id="length"
Expand All @@ -169,6 +170,7 @@ const StdcmConsist = ({ disabled = false }: StdcmConfigCardProps) => {
min={0}
value={totalLength ?? ''}
onChange={onTotalLengthChange}
disabled={disabled}
/>
</div>
<div className="stdcm-consist__properties">
Expand All @@ -186,6 +188,7 @@ const StdcmConsist = ({ disabled = false }: StdcmConfigCardProps) => {
min={0}
value={maxSpeed ?? ''}
onChange={onMaxSpeedChange}
disabled={disabled}
/>
</div>
</StdcmCard>
Expand Down
121 changes: 92 additions & 29 deletions front/src/applications/stdcm/components/StdcmLoader.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,105 @@
import { forwardRef } from 'react';
import { useEffect, useRef, useState, type RefObject } from 'react';

import { Button } from '@osrd-project/ui-core';
import cx from 'classnames';
import { useTranslation } from 'react-i18next';

import stdcmLoaderImg from 'assets/pictures/views/stdcm_loader.jpg';
import type { LoaderStatus } from '../types';

const LOADER_HEIGHT = 176;
const LOADER_OFFSET = 32;

type StdcmLoaderProps = {
cancelStdcmRequest: () => void;
launchButtonRef: RefObject<HTMLDivElement>;
formRef: RefObject<HTMLDivElement>;
};

const StdcmLoader = forwardRef(
({ cancelStdcmRequest }: StdcmLoaderProps, ref: React.Ref<HTMLDivElement>) => {
const { t } = useTranslation('stdcm');
return (
<div className="stdcm-loader-background">
<div
ref={ref}
className="stdcm-loader d-flex flex-column justify-content-center align-items-center"
>
<div className="stdcm-loader__wrapper">
<h1>{t('simulation.calculatingSimulation')}</h1>
<p>{t('simulation.averageRequestTime')}</p>
</div>
<div className="stdcm-loader__cancel-btn">
<Button
data-testid="cancel-simulation-button"
variant="Cancel"
label={t('simulation.stopCalculation')}
size="small"
onClick={cancelStdcmRequest}
/>
</div>
<img src={stdcmLoaderImg} alt={t('simulation.pendingSimulation')} />
<p className="stdcm-loader__img-signature">{t('loaderImageLegend')}</p>
const StdcmLoader = ({ cancelStdcmRequest, launchButtonRef, formRef }: StdcmLoaderProps) => {
const { t } = useTranslation('stdcm');
const loaderRef = useRef<HTMLDivElement>(null);

const { top } = launchButtonRef.current!.getBoundingClientRect();
const windowHeight = window.innerHeight;

const [loaderStatus, setLoaderStatus] = useState<LoaderStatus>({
status: windowHeight - top - 32 > LOADER_HEIGHT ? 'loader-absolute' : 'loader-fixed-bottom',
firstLaunch: true,
});

useEffect(() => {
// Depending on the scroll, change the position of the loader between fixed, sticky or absolute
const handleScroll = () => {
if (!loaderRef.current || !launchButtonRef.current || !formRef.current) return;

const { scrollY, innerHeight } = window;

const isLoaderFitting =
innerHeight - launchButtonRef.current.getBoundingClientRect().top >
LOADER_HEIGHT + LOADER_OFFSET;

// Loader doesn't fit between the bottom of the form and bottom of the viewport
if (!isLoaderFitting) {
setLoaderStatus({
firstLaunch: false,
status: 'loader-fixed-bottom',
});
return;
}

const currentFormHeight = formRef.current.clientHeight;
const topFormPosition = formRef.current.getBoundingClientRect().top;
const launchButtonHeight = launchButtonRef.current.clientHeight;
const shouldLoaderStickTop =
scrollY >
currentFormHeight + scrollY + topFormPosition - launchButtonHeight - LOADER_OFFSET;

// Loader reaches the top of the screen minus its top offset
if (shouldLoaderStickTop) {
setLoaderStatus({
firstLaunch: false,
status: 'loader-fixed-top',
});
return;
}

setLoaderStatus({
firstLaunch: false,
status: 'loader-absolute',
});
};

window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);

return (
<div
ref={loaderRef}
className={cx('stdcm-loader', `${loaderStatus.status}`, {
'with-fade-in-animation':
loaderStatus.status === 'loader-absolute' && loaderStatus.firstLaunch,
'with-slide-animation':
loaderStatus.status === 'loader-fixed-bottom' && loaderStatus.firstLaunch,
})}
>
<div className="stdcm-loader__wrapper">
<h2>{t('simulation.calculatingSimulation')}</h2>
<div className="stdcm-loader__cancel-btn">
<Button
data-testid="cancel-simulation-button"
variant="Cancel"
label={t('simulation.stopCalculation')}
size="small"
onClick={cancelStdcmRequest}
/>
</div>
</div>
);
}
);
<p className="stdcm-loader__info-message">{t('simulation.infoMessage')}</p>
</div>
);
};

export default StdcmLoader;
5 changes: 5 additions & 0 deletions front/src/applications/stdcm/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,8 @@ export type StdcmLinkedPathResult = {
};

export type ExtremityPathStepType = 'origin' | 'destination';

export type LoaderStatus = {
status: 'loader-fixed-bottom' | 'loader-fixed-top' | 'loader-absolute';
firstLaunch: boolean;
};
15 changes: 3 additions & 12 deletions front/src/applications/stdcm/views/StdcmView.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState, useRef } from 'react';
import { useEffect, useState } from 'react';

import { isEqual, isNil } from 'lodash';

Expand All @@ -12,7 +12,6 @@ import { replaceElementAtIndex } from 'utils/array';
import StdcmEmptyConfigError from '../components/StdcmEmptyConfigError';
import StdcmConfig from '../components/StdcmForm/StdcmConfig';
import StdcmHeader from '../components/StdcmHeader';
import StdcmLoader from '../components/StdcmLoader';
import StdcmResults from '../components/StdcmResults';
import StdcmStatusBanner from '../components/StdcmStatusBanner';
import useStdcmEnvironment, { NO_CONFIG_FOUND_MSG } from '../hooks/useStdcmEnv';
Expand Down Expand Up @@ -48,9 +47,7 @@ const StdcmView = () => {
useOsrdConfActions() as StdcmConfSliceActions;

const selectedSimulation = simulationsList[selectedSimulationIndex];
const showResults =
!isPending && (showStatusBanner || simulationsList.length > 0 || hasConflicts);
const loaderRef = useRef<HTMLDivElement>(null);
const showResults = showStatusBanner || simulationsList.length > 0 || hasConflicts;

const handleRetainSimulation = () => setRetainedSimulationIndex(selectedSimulationIndex);

Expand Down Expand Up @@ -168,12 +165,6 @@ const StdcmView = () => {
}
}, [simulationsList]);

useEffect(() => {
if (isPending) {
loaderRef?.current?.scrollIntoView({ behavior: 'smooth' });
}
}, [isPending]);

// If we've got an error during the loading of the stdcm env which is not the "no config error" message,
// we let the error boundary manage it
if (error && error.message !== NO_CONFIG_FOUND_MSG) throw error;
Expand All @@ -192,9 +183,9 @@ const StdcmView = () => {
showBtnToLaunchSimulation={showBtnToLaunchSimulation}
retainedSimulationIndex={retainedSimulationIndex}
launchStdcmRequest={launchStdcmRequest}
cancelStdcmRequest={cancelStdcmRequest}
/>

{isPending && <StdcmLoader cancelStdcmRequest={cancelStdcmRequest} ref={loaderRef} />}
{showStatusBanner && <StdcmStatusBanner isFailed={isCalculationFailed} />}

{showResults && (
Expand Down
Binary file removed front/src/assets/pictures/views/stdcm_loader.jpg
Binary file not shown.
Loading
Loading