Skip to content

Commit eaa4315

Browse files
committed
front: animate new intermediate op in stdcm
- Add an animation when a new intermediate op is added - Scroll to keep it in the viewport if it overflows - Adapat e2e tests in stdcm to ensure buttons are clickable while it's animating Signed-off-by: SharglutDev <p.filimon75@gmail.com>
1 parent ea19025 commit eaa4315

File tree

4 files changed

+69
-3
lines changed

4 files changed

+69
-3
lines changed

front/src/applications/stdcm/components/StdcmForm/StdcmVias.tsx

+42-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useMemo } from 'react';
1+
import { useLayoutEffect, useMemo, useState } from 'react';
22

33
import { Location } from '@osrd-project/ui-icons';
44
import { useTranslation } from 'react-i18next';
@@ -27,6 +27,8 @@ const StdcmVias = ({ disabled = false }: StdcmConfigCardProps) => {
2727
useOsrdConfActions() as StdcmConfSliceActions;
2828
const pathSteps = useSelector(getStdcmPathSteps);
2929

30+
const [newIntermediateOpIndex, setNewIntermediateOpIndex] = useState<number>();
31+
3032
const intermediatePoints = useMemo(() => pathSteps.slice(1, -1), [pathSteps]);
3133

3234
const updateStopType = (newStopType: StdcmStopTypes, pathStep: StdcmPathStep) => {
@@ -44,6 +46,44 @@ const StdcmVias = ({ disabled = false }: StdcmConfigCardProps) => {
4446
);
4547
};
4648

49+
/**
50+
* As the new intermediateOp block animates, we want to scroll to keep the box in the viewport.
51+
* To do so, we install an animation frame listener (requestAnimationFrame) which updates the scroll position
52+
* each time an animation frame is triggered.
53+
* An animation end listener is also installed to cancel the animation frame listener.
54+
* To properly clean up when the component is unmounted, we return a cleanup function that removes both listeners.
55+
*/
56+
useLayoutEffect(() => {
57+
if (!newIntermediateOpIndex) return undefined;
58+
59+
const newElement = document.querySelector(
60+
`.stdcm-vias-bundle:nth-child(${newIntermediateOpIndex}) > :last-child`
61+
);
62+
63+
if (!newElement) return undefined;
64+
65+
let requestId: number;
66+
67+
const scrollWithAnimation = () => {
68+
newElement.scrollIntoView({
69+
block: 'nearest',
70+
behavior: 'auto',
71+
});
72+
73+
requestId = requestAnimationFrame(scrollWithAnimation);
74+
};
75+
76+
requestId = requestAnimationFrame(scrollWithAnimation);
77+
78+
const cancelListener = () => cancelAnimationFrame(requestId);
79+
80+
newElement.addEventListener('animationend', cancelListener);
81+
return () => {
82+
newElement.removeEventListener('animationend', cancelListener);
83+
cancelListener();
84+
};
85+
}, [newIntermediateOpIndex]);
86+
4787
const updateStopDuration = (stopTime: string, pathStep: StdcmPathStep) => {
4888
const stopFor = stopTime ? Number(stopTime) : undefined;
4989
dispatch(
@@ -60,6 +100,7 @@ const StdcmVias = ({ disabled = false }: StdcmConfigCardProps) => {
60100

61101
const addViaOnClick = (pathStepIndex: number) => {
62102
dispatch(addStdcmVia(pathStepIndex));
103+
setNewIntermediateOpIndex(pathStepIndex);
63104
};
64105

65106
return (

front/src/styles/scss/applications/stdcm/_home.scss

+22
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,28 @@
106106
gap: 16px;
107107
}
108108

109+
.stdcm-vias-bundle {
110+
animation: bouncin-in 0.75s cubic-bezier(0.567, -0.475, 0, 1);
111+
112+
.stdcm-card {
113+
scroll-margin-bottom: 15px;
114+
}
115+
}
116+
117+
@keyframes bouncin-in {
118+
0% {
119+
opacity: 0;
120+
height: 0;
121+
// To make the new block poping from above the clicked button
122+
transform: translateY(-45px);
123+
margin-bottom: -30px; // To make the button clicked bounce top
124+
}
125+
100% {
126+
opacity: 1;
127+
height: 296px; // height of add OP button + intermedita OP card
128+
}
129+
}
130+
109131
.stdcm-vias-list {
110132
margin-block: 16px;
111133

front/tests/006-stdcm.spec.ts

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ test.use({
1414
test.describe('Verify train schedule elements and filters', () => {
1515
test.slow(); // Mark test as slow due to multiple steps
1616

17+
test.use({ viewport: { width: 1920, height: 1080 } });
18+
1719
let infra: Infra;
1820
let OSRDLanguage: string;
1921

front/tests/pages/stdcm-page-model.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ class STDCMPage {
390390
const fillVia = async (selectedSuggestion: Locator) => {
391391
await this.addViaButton.nth(viaNumber - 1).click();
392392
expect(await this.addViaButton.count()).toBe(viaNumber + 1);
393+
await expect(this.getViaCi(viaNumber)).toBeVisible();
393394
await this.getViaCi(viaNumber).fill(viaSearch);
394395
await selectedSuggestion.click();
395396
await expect(this.getViaCh(viaNumber)).toHaveValue('BV');
@@ -496,7 +497,7 @@ class STDCMPage {
496497
// Wait for the download event
497498
await this.page.waitForTimeout(500);
498499
const downloadPromise = this.page.waitForEvent('download', { timeout: 120000 });
499-
await this.downloadSimulationButton.click({ force: true });
500+
await this.downloadSimulationButton.dispatchEvent('click');
500501
const download = await downloadPromise.catch(() => {
501502
throw new Error('Download event was not triggered.');
502503
});
@@ -510,7 +511,7 @@ class STDCMPage {
510511
}
511512

512513
async clickOnStartNewQueryButton() {
513-
await this.startNewQueryButton.click();
514+
await this.startNewQueryButton.dispatchEvent('click');
514515
}
515516

516517
async mapMarkerVisibility() {

0 commit comments

Comments
 (0)