From d18414fa2d2ccf58a77f5e22ede7cd24e7fcb44f Mon Sep 17 00:00:00 2001 From: Sem Bauke Date: Tue, 6 Jun 2023 13:59:40 +0200 Subject: [PATCH] feat: add progress bar to lower jaw (#50167) Co-authored-by: IsmailTlemcani Co-authored-by: ahmad abdolsaheb --- .../ProgressBar/progress-bar-inner.tsx | 47 +++++++++----- .../Challenges/classic/lower-jaw.tsx | 10 ++- .../default/learn/challenges/progress-bar.ts | 62 ++++++++++++++++++- 3 files changed, 103 insertions(+), 16 deletions(-) diff --git a/client/src/components/ProgressBar/progress-bar-inner.tsx b/client/src/components/ProgressBar/progress-bar-inner.tsx index ea4b0ebf6d6..9a233ae69f6 100644 --- a/client/src/components/ProgressBar/progress-bar-inner.tsx +++ b/client/src/components/ProgressBar/progress-bar-inner.tsx @@ -1,4 +1,5 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useRef, useMemo } from 'react'; + import BezierEasing from 'bezier-easing'; interface ProgressBarInnerProps { @@ -10,17 +11,37 @@ interface ProgressBarInnerProps { const easing = BezierEasing(0.2, 0.5, 0.4, 1); const intervalLength = 10; let percent = 0; -let applyAnimation = true; +function useIsInViewport(ref: React.RefObject) { + const [isIntersecting, setIsIntersecting] = useState(false); + + const observer = useMemo( + () => + new IntersectionObserver(([entry]) => + setIsIntersecting(entry.isIntersecting) + ), + [] + ); + + useEffect(() => { + ref.current && observer.observe(ref.current); + return () => { + observer.disconnect(); + }; + }, [ref, observer]); + + return isIntersecting; +} function ProgressBarInner({ completedPercent, title, meta }: ProgressBarInnerProps): JSX.Element { const [shownPercent, setShownPercent] = useState(0); - const [progressInterval, setProgressInterval] = useState(0); const [progressBarInnerWidth, setProgressBarInnerWidth] = useState(0); + const [lastShopwnPercent, setLastShownPercent] = useState(0); const progressBarInnerWrap = useRef(null); + const isProgressBarInViewport = useIsInViewport(progressBarInnerWrap); const animateProgressBarInner = (completedPercent: number) => { if (completedPercent > 100) completedPercent = 100; @@ -38,21 +59,19 @@ function ProgressBarInner({ setShownPercent( Math.round(completedPercent * easing(percent / completedPercent)) ); - if (percent >= completedPercent) clearInterval(myInterval); + if (percent >= completedPercent) { + percent = 0; + clearInterval(myInterval); + } }, intervalLength); - - setProgressInterval(myInterval); }; useEffect(() => { - if (applyAnimation) animateProgressBarInner(completedPercent); - return () => { - if (progressInterval !== null) { - clearInterval(progressInterval); - applyAnimation = false; - } - }; + if (lastShopwnPercent !== completedPercent && isProgressBarInViewport) { + setLastShownPercent(completedPercent); + animateProgressBarInner(completedPercent); + } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [completedPercent]); + }, [isProgressBarInViewport]); useEffect(() => { if (progressBarInnerWrap.current) diff --git a/client/src/templates/Challenges/classic/lower-jaw.tsx b/client/src/templates/Challenges/classic/lower-jaw.tsx index 20a894db595..1c88a1810f9 100644 --- a/client/src/templates/Challenges/classic/lower-jaw.tsx +++ b/client/src/templates/Challenges/classic/lower-jaw.tsx @@ -9,7 +9,7 @@ import Help from '../../../assets/icons/help'; import Reset from '../../../assets/icons/reset'; import { MAX_MOBILE_WIDTH } from '../../../../../config/misc'; import { apiLocation } from '../../../../../config/env.json'; - +import ProgressBar from '../../../components/ProgressBar'; const lowerJawButtonStyle = 'btn-block btn'; interface LowerJawPanelProps { @@ -310,6 +310,14 @@ const LowerJaw = ({ /> )} + {challengeIsCompleted && ( + <> +
+
+ +
+ + )} { }); it( - 'Should show the progress bar showing the completed percent', + 'Should show the progress bar showing the completed percent on legacy challenges', { browser: 'electron' }, () => { cy.visit( @@ -22,4 +22,64 @@ describe('progress bar', () => { cy.get('.progress-bar-container').contains('1% complete'); } ); + + it( + 'Should show the progress bar showing the completed percent on modern challenges', + { browser: 'electron' }, + () => { + cy.visit( + '/learn/2022/responsive-web-design/learn-html-by-building-a-cat-photo-app/step-2' + ); + cy.get(`${'.react-monaco-editor-container'} textarea`, { timeout: 16000 }) + .click() + .focused() + .type('{ctrl}a') + .clear() + .type(`

CatPhotoApp

\n

Cat Photos

`); + cy.contains('Check Your Code (Ctrl + Enter)').click({ force: true }); + cy.contains('Submit and go to next challenge'); + cy.get('.progress-bar-container').contains('1% complete'); + } + ); + + it( + 'Should show the progress bar showing the completed percent on modern challenges', + { browser: 'electron' }, + () => { + cy.visit( + '/learn/2022/responsive-web-design/learn-html-by-building-a-cat-photo-app/step-3' + ); + cy.get(`${'.react-monaco-editor-container'} textarea`, { timeout: 16000 }) + .click() + .focused() + .type('{ctrl}a') + .clear() + .type( + `

Cat Photos

\n

See more cat photos in our gallery.

` + ); + cy.contains('Check Your Code (Ctrl + Enter)').click({ force: true }); + cy.contains('Submit and go to next challenge'); + cy.get('.progress-bar-container').contains('1% complete'); + } + ); + it( + 'Should show the progress bar showing the completed percent on modern challenges', + { browser: 'electron' }, + () => { + cy.visit( + '/learn/2022/responsive-web-design/learn-html-by-building-a-cat-photo-app/step-4' + ); + cy.get(`${'.react-monaco-editor-container'} textarea`, { timeout: 16000 }) + .click() + .focused() + .type('{ctrl}a') + .clear() + .type( + `\n

See more cat photos in our gallery.

` + ); + cy.contains('Check Your Code (Ctrl + Enter)').click({ force: true }); + cy.contains('Submit and go to next challenge'); + cy.get('.progress-bar-container').contains('3% complete'); + } + ); });