diff --git a/src/components/common/HeightObserver.tsx b/src/components/common/HeightObserver.tsx deleted file mode 100644 index 9725c459..00000000 --- a/src/components/common/HeightObserver.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { useEffect, useRef } from 'react'; - -type Props = { - children: JSX.Element; - onHeightChange: (newHeight: number) => void; -}; - -const HeightObserver = ({ children, onHeightChange }: Props) => { - const containerRef = useRef(null); - - useEffect(() => { - const observer = new ResizeObserver((entries) => { - entries.forEach((entry) => { - const { clientHeight } = entry.target; - onHeightChange && onHeightChange(clientHeight); - }); - }); - - if (containerRef.current) { - observer.observe(containerRef.current); - } - - return () => { - observer.disconnect(); - }; - }, [onHeightChange]); - - return
{children}
; -}; - -export default HeightObserver; diff --git a/src/components/common/animations/ReorderAnimation.tsx b/src/components/common/animations/ReorderAnimation.tsx deleted file mode 100644 index fa21304a..00000000 --- a/src/components/common/animations/ReorderAnimation.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import { useTransition } from '@react-spring/web'; - -import { useState } from 'react'; -import { animated } from 'react-spring'; - -import { countKeysApparition } from '../../../utils/array'; - -const ANIMATION_DURATION_MS = 350; - -type DataElementType = { elementType: number }; - -export type TransitionData = { - key: string; - height?: number; - marginBottom: number; - y?: number; - data: T; -}; - -type Props = { - isAnimating: boolean; - elements: TransitionData[]; - renderElement: ( - item: TransitionData, - onHeightChange: (key: string, height: number) => void - ) => JSX.Element; -}; - -const duplicatedKeys = ( - elements: TransitionData[] -) => - Object.entries(countKeysApparition(elements, 'key')) - .filter(([key, count]) => key && count > 1) - .map(([key, _]) => key); - -const printDuplicatedKeysWarning = ( - elements: TransitionData[] -) => - duplicatedKeys(elements).forEach((k) => - console.warn( - `Be carefull, the key "${k}" is not unique ! This can cause issues with the animation.` - ) - ); - -export const ReorderAnimation = ({ - isAnimating, - elements, - renderElement, -}: Props) => { - const [totalHeight, setTotalHeight] = useState(0); - // Tracks the heights of each element to recompute totalHeight when heights changed. - // That means, if a element's height changed, the map is updated and he totalHeight - // recomputed with all the heights, including the updated one. - const elementHeights = new Map(); - // this variable is used to set the next element to the good y - let transitionHeight = 0; - const transitionAnimation = isAnimating - ? { - from: { height: 0, opacity: 0 }, - leave: { height: 0, opacity: 0 }, - enter: ({ y, height }: { y: number; height: number }) => ({ - y, - height, - opacity: 1, - }), - update: ({ y, height }: { y: number; height: number }) => ({ - y, - height, - }), - } - : { - from: (_item: TransitionData, _: number) => ({ - opacity: 0, - }), - enter: { opacity: 1 }, - }; - - const transitions = useTransition( - elements.map((e) => { - transitionHeight += e.marginBottom; - return { - ...e, - y: transitionHeight - e.marginBottom, - }; - }), - { - // the key must be unique and not change to keep the same component for the animation. - key: (item: TransitionData) => `${item.key}`, - ...transitionAnimation, - config: { duration: ANIMATION_DURATION_MS }, - } - ); - - const handleElementHeight = (key: string, height: number) => { - elementHeights.set(key, height); - const element = elements.find((e) => e.key === key); - if (element) { - element.height = height; - } - setTotalHeight( - Array.from(elementHeights.entries()).reduce( - (acc, [k, h]) => - acc + h + (elements.find((e) => e.key === k)?.marginBottom ?? 0), - 0 - ) - ); - }; - - printDuplicatedKeysWarning(elements); - - return ( -
- {transitions( - (style, item, _t, index) => - item.key && ( - - {renderElement(item, handleElementHeight)} - - ) - )} -
- ); -}; - -export default ReorderAnimation; diff --git a/src/components/play/PlayViewQuestionType.tsx b/src/components/play/PlayViewQuestionType.tsx index c9c92445..c06899bf 100644 --- a/src/components/play/PlayViewQuestionType.tsx +++ b/src/components/play/PlayViewQuestionType.tsx @@ -97,7 +97,6 @@ export const PlayViewQuestionType = ({ } showCorrection={showCorrection} showCorrectness={showCorrectness} - numberOfSubmit={numberOfSubmit} numberOfRetry={numberOfRetry} /> ); diff --git a/src/components/play/multipleChoices/PlayMultipleChoices.tsx b/src/components/play/multipleChoices/PlayMultipleChoices.tsx index 36ba54ee..6fe253ef 100644 --- a/src/components/play/multipleChoices/PlayMultipleChoices.tsx +++ b/src/components/play/multipleChoices/PlayMultipleChoices.tsx @@ -5,10 +5,6 @@ import { Stack, Typography } from '@mui/material'; import { buildMultipleChoiceHintPlayCy } from '../../../config/selectors'; import { QUIZ_TRANSLATIONS } from '../../../langs/constants'; -import HeightObserver from '../../common/HeightObserver'; -import ReorderAnimation, { - TransitionData, -} from '../../common/animations/ReorderAnimation'; import { ChoiceState, MultipleChoiceAppDataData, @@ -17,9 +13,6 @@ import { } from '../../types/types'; import ChoiceButton from './ChoiceButton'; -const DEFAULT_MARGIN = 10; -const HINT_MARGIN = 10; - const computeChoiceState = ( { value, isCorrect }: MultipleChoicesChoice, userChoices: string[] | undefined, @@ -43,29 +36,8 @@ const computeChoiceState = ( } }; -const sectionTitles = [ - { - title: QUIZ_TRANSLATIONS.MULTIPLE_CHOICE_SECTION_TITLE_CORRECT, - state: ChoiceState.CORRECT, - }, - { - title: QUIZ_TRANSLATIONS.MULTIPLE_CHOICE_SECTION_TITLE_MISSING, - state: ChoiceState.MISSING, - }, - { - title: QUIZ_TRANSLATIONS.MULTIPLE_CHOICE_SECTION_TITLE_INCORRECT, - state: ChoiceState.INCORRECT, - }, - - { - title: QUIZ_TRANSLATIONS.MULTIPLE_CHOICE_SECTION_TITLE_UNSELECTED, - state: ChoiceState.UNSELECTED, - }, -]; - enum ElementType { Answer, - SectionTitle, Hint, } @@ -74,44 +46,27 @@ type AnswerDataType = { choice: MultipleChoicesChoice; elementType: ElementType.Answer; }; -type TitleDataType = { title: string; elementType: ElementType.SectionTitle }; type HintDataType = { idx: number; hint: string; elementType: ElementType.Hint; }; -type DataType = AnswerDataType | TitleDataType | HintDataType; +type DataType = AnswerDataType | HintDataType; +type DataTypes = DataType | DataType[]; const choiceToAnswer = ( choice: MultipleChoicesChoice, - idx: number, - marginBottom: number -): TransitionData => ({ - key: `answer-${choice.value}-${idx}`, - marginBottom, - data: { idx, choice, elementType: ElementType.Answer }, -}); - -const choiceToTitle = (title: string): TransitionData => ({ - key: `title-${title}`, - marginBottom: DEFAULT_MARGIN, - data: { - title, - elementType: ElementType.SectionTitle, - }, + idx: number +): AnswerDataType => ({ + idx, + choice, + elementType: ElementType.Answer, }); -const choiceToHint = ( - choiceIdx: number, - hint: string -): TransitionData => ({ - key: `hint-${hint}-${choiceIdx}`, - marginBottom: HINT_MARGIN, - data: { - hint, - idx: choiceIdx, - elementType: ElementType.Hint, - }, +const choiceToHint = (choiceIdx: number, hint: string): HintDataType => ({ + hint, + idx: choiceIdx, + elementType: ElementType.Hint, }); const isChoiceSelected = ( @@ -122,8 +77,12 @@ const isChoiceSelected = ( export const showHint = ( isSelected: boolean, showCorrectness: boolean, - showCorrection: boolean -) => showCorrection || (showCorrectness && isSelected); + showCorrection: boolean, + choiceState: ChoiceState +) => + isSelected && + (showCorrectness || showCorrection) && + choiceState === ChoiceState.INCORRECT; type Props = { choices: MultipleChoicesAppSettingData['choices']; @@ -131,7 +90,6 @@ type Props = { lastUserAnswer?: MultipleChoiceAppDataData; showCorrection: boolean; showCorrectness: boolean; - numberOfSubmit: number; numberOfRetry: number; setResponse: (d: MultipleChoiceAppDataData['choices']) => void; }; @@ -142,18 +100,16 @@ const PlayMultipleChoices = ({ lastUserAnswer, showCorrection, showCorrectness, - numberOfSubmit, numberOfRetry, setResponse, }: Props): JSX.Element => { const { t } = useTranslation(); - const [elements, setElements] = useState[]>([]); + const [elements, setElements] = useState([]); const isReadonly = showCorrection || showCorrectness; const choiceStates = choices.map((choice) => computeChoiceState(choice, lastUserAnswer?.choices, showCorrection) ); - const isAnimating = numberOfSubmit + numberOfRetry > 0; const showError = choiceStates.some( (state) => @@ -163,45 +119,25 @@ const PlayMultipleChoices = ({ !showCorrection; useEffect(() => { - const answers = choices.map((c, idx) => - choiceToAnswer(c, idx, DEFAULT_MARGIN) - ); + const answers = choices.map((c, idx) => choiceToAnswer(c, idx)); // set the "gaming" view if (!showCorrection && !showCorrectness) { setElements(answers); } else { // set the "correctness" or "correction" view setElements( - sectionTitles.flatMap((sectionTitle, i) => { - const sectionAnswers = choiceStates.some( - (state) => sectionTitle.state === state + answers.map((answer, idx) => { + const hint = answer.choice.explanation; + const displayHint = showHint( + isChoiceSelected(response.choices, answer.choice), + showCorrectness, + showCorrection, + choiceStates[idx] ); - if (!sectionAnswers) { - return []; - } - - return [ - choiceToTitle(t(sectionTitles[i].title)), - ...answers - .filter((_, idx) => sectionTitle.state === choiceStates[idx]) - .map((answer) => { - const hint = answer.data.choice.explanation; - const displayHint = showHint( - isChoiceSelected(response.choices, answer.data.choice), - showCorrectness, - showCorrection - ); - - return displayHint && hint - ? [ - { ...answer, marginBottom: 0 }, - choiceToHint(answer.data.idx, hint), - ] - : answer; - }) - .flat(), - ]; + return displayHint && hint + ? [answer, choiceToHint(answer.idx, hint)] + : answer; }) ); } @@ -228,67 +164,47 @@ const PlayMultipleChoices = ({ } }; - const renderElement = ( - item: TransitionData, - onHeightChange: (key: string, height: number) => void - ) => { - let element: JSX.Element | undefined; - - switch (item.data.elementType) { + const renderElement = (el: DataType) => { + switch (el.elementType) { case ElementType.Answer: { - const isSelected = isChoiceSelected(response.choices, item.data.choice); - element = ( + const isSelected = isChoiceSelected(response.choices, el.choice); + return ( ); - break; } - case ElementType.SectionTitle: - element = ( - - {item.data.title} - - ); - break; case ElementType.Hint: - element = ( + return ( - {item.data.hint} + {el.hint} ); - break; } - return ( - onHeightChange(item.key, height)} - > - {element} - - ); + return undefined; }; return ( - + + {elements.map((el) => { + // The element can be a button or an array with a button and a hint + if (Array.isArray(el)) { + return {el.map((e) => renderElement(e))}; + } + return renderElement(el); + })} + {showError && ( {t(QUIZ_TRANSLATIONS.MULTIPLE_CHOICE_NOT_CORRECT)}