diff --git a/client/i18n/locales/english/intro.json b/client/i18n/locales/english/intro.json index 3c7dde9188c..415de15b4a5 100644 --- a/client/i18n/locales/english/intro.json +++ b/client/i18n/locales/english/intro.json @@ -1697,7 +1697,10 @@ "intro": ["placeholder"], "blocks": { "efpl": { "title": "0", "intro": [] }, - "xpmy": { "title": "1", "intro": [] }, + "lecture-what-is-html": { + "title": "What is HTML?", + "intro": ["Learn what HTML is in these lecture videos."] + }, "workshop-cat-photo-app": { "title": "Build a Cat Photo App", "intro": [ diff --git a/client/i18n/locales/english/translations.json b/client/i18n/locales/english/translations.json index ac7237b4b23..36d9999ecb7 100644 --- a/client/i18n/locales/english/translations.json +++ b/client/i18n/locales/english/translations.json @@ -390,6 +390,7 @@ "assignment-not-complete": "Please complete the assignments", "assignments": "Assignments", "question": "Question", + "questions": "Questions", "explanation": "Explanation", "solution-link": "Solution Link", "source-code-link": "Source Code Link", @@ -491,8 +492,8 @@ "fill-in-the-blank": "Fill in the blank", "blank": "blank", "quiz": { - "correct-answer": "Correct", - "incorrect-answer": "Incorrect", + "correct-answer": "Correct!", + "incorrect-answer": "Incorrect.", "unanswered-questions": "The following questions are unanswered: {{ unansweredQuestions }}. You must answer all questions.", "have-n-correct-questions": "You have {{ correctAnswerCount }} out of {{ total }} questions correct." }, diff --git a/client/src/pages/learn/front-end-development/lecture-what-is-html/index.md b/client/src/pages/learn/front-end-development/lecture-what-is-html/index.md new file mode 100644 index 00000000000..42b48890084 --- /dev/null +++ b/client/src/pages/learn/front-end-development/lecture-what-is-html/index.md @@ -0,0 +1,9 @@ +--- +title: Introduction to What is HTML? +block: lecture-what-is-html +superBlock: front-end-development +--- + +## Introduction to What is HTML? + +Learn what HTML is in these lecture videos. diff --git a/client/src/templates/Challenges/components/multiple-choice-questions.tsx b/client/src/templates/Challenges/components/multiple-choice-questions.tsx index 291c7077bc7..3cb7f1e3c17 100644 --- a/client/src/templates/Challenges/components/multiple-choice-questions.tsx +++ b/client/src/templates/Challenges/components/multiple-choice-questions.tsx @@ -7,74 +7,109 @@ import ChallengeHeading from './challenge-heading'; import PrismFormatted from './prism-formatted'; type MultipleChoiceQuestionsProps = { - questions: Question; - selectedOption: number | null; - isWrongAnswer: boolean; - handleOptionChange: ( - changeEvent: React.ChangeEvent - ) => void; + questions: Question[]; + selectedOptions: (number | null)[]; + handleOptionChange: (questionIndex: number, answerIndex: number) => void; + submittedMcqAnswers: (number | null)[]; + showFeedback: boolean; }; +function removeParagraphTags(text: string): string { + return text.replace(/^

|<\/p>$/g, ''); +} + function MultipleChoiceQuestions({ - questions: { text, answers }, - selectedOption, - isWrongAnswer, - handleOptionChange + questions, + selectedOptions, + handleOptionChange, + submittedMcqAnswers, + showFeedback }: MultipleChoiceQuestionsProps): JSX.Element { const { t } = useTranslation(); - const feedback = - selectedOption !== null ? answers[selectedOption].feedback : undefined; - return ( <> - - -

- {answers.map(({ answer }, index) => ( - - ))} -
- {isWrongAnswer && ( - <> - -
- {feedback ? ( - - ) : ( - t('learn.wrong-answer') - )} + 1 ? t('learn.questions') : t('learn.question') + } + /> + {questions.map((question, questionIndex) => ( +
+ +
+ {question.answers.map(({ answer }, answerIndex) => { + const isSubmittedAnswer = + submittedMcqAnswers[questionIndex] === answerIndex; + const feedback = + questions[questionIndex].answers[answerIndex].feedback; + const isCorrect = + submittedMcqAnswers[questionIndex] === + // -1 because the solution is 1-indexed + questions[questionIndex].solution - 1; + + return ( + + + {showFeedback && isSubmittedAnswer && ( +
+ {isCorrect + ? t('learn.quiz.correct-answer') + : t('learn.quiz.incorrect-answer')} + {feedback && ( + <> +   + + + )} +
+ )} +
+ ); + })}
- - )} + +
+ ))} ); diff --git a/client/src/templates/Challenges/odin/show.tsx b/client/src/templates/Challenges/odin/show.tsx index 3d22cbf8592..8ceb311117d 100644 --- a/client/src/templates/Challenges/odin/show.tsx +++ b/client/src/templates/Challenges/odin/show.tsx @@ -9,6 +9,7 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import type { Dispatch } from 'redux'; import { createSelector } from 'reselect'; +import { isEqual } from 'lodash-es'; import { Container, Col, Row, Button } from '@freecodecamp/ui'; import ShortcutsModal from '../components/shortcuts-modal'; @@ -78,9 +79,9 @@ interface ShowOdinProps { interface ShowOdinState { subtitles: string; downloadURL: string | null; - selectedOption: number | null; - answer: number; - isWrongAnswer: boolean; + selectedMcqOptions: (number | null)[]; + submittedMcqAnswers: (number | null)[]; + showFeedback: boolean; assignmentsCompleted: number; allAssignmentsCompleted: boolean; videoIsLoaded: boolean; @@ -94,14 +95,23 @@ class ShowOdin extends Component { constructor(props: ShowOdinProps) { super(props); + + const { + data: { + challengeNode: { + challenge: { assignments, questions } + } + } + } = this.props; + this.state = { subtitles: '', downloadURL: null, - selectedOption: null, - answer: 1, - isWrongAnswer: false, + selectedMcqOptions: questions.map(() => null), + submittedMcqAnswers: questions.map(() => null), + showFeedback: false, assignmentsCompleted: 0, - allAssignmentsCompleted: false, + allAssignmentsCompleted: assignments.length == 0, videoIsLoaded: false, isScenePlaying: false }; @@ -166,34 +176,43 @@ class ShowOdin extends Component { } } - handleSubmit( - solution: number, - openCompletionModal: () => void, - assignments: string[] - ) { - const hasAssignments = assignments.length > 0; - const completed = this.state.allAssignmentsCompleted; - const isCorrect = solution - 1 === this.state.selectedOption; + handleSubmit = () => { + const { + data: { + challengeNode: { + challenge: { questions } + } + }, + openCompletionModal + } = this.props; - if (isCorrect) { - this.setState({ - isWrongAnswer: false - }); - if (!hasAssignments || completed) openCompletionModal(); - } else { - this.setState({ - isWrongAnswer: true - }); - } - } + // subract 1 because the solutions are 1-indexed + const mcqSolutions = questions.map(question => question.solution - 1); - handleOptionChange = ( - changeEvent: React.ChangeEvent - ): void => { this.setState({ - isWrongAnswer: false, - selectedOption: parseInt(changeEvent.target.value, 10) + submittedMcqAnswers: this.state.selectedMcqOptions, + showFeedback: true }); + + const allMcqAnswersCorrect = isEqual( + mcqSolutions, + this.state.selectedMcqOptions + ); + + if (this.state.allAssignmentsCompleted && allMcqAnswersCorrect) { + openCompletionModal(); + } + }; + + handleMcqOptionChange = ( + questionIndex: number, + answerIndex: number + ): void => { + this.setState(state => ({ + selectedMcqOptions: state.selectedMcqOptions.map((option, index) => + index === questionIndex ? answerIndex : option + ) + })); }; handleAssignmentChange = ( @@ -245,7 +264,6 @@ class ShowOdin extends Component { } } }, - openCompletionModal, openHelpModal, pageContext: { challengeMeta: { nextChallengePath, prevChallengePath } @@ -254,18 +272,13 @@ class ShowOdin extends Component { isChallengeCompleted } = this.props; - const question = questions[0]; - const { solution } = question; - const blockNameTitle = `${t( `intro:${superBlock}.blocks.${block}.title` )} - ${title}`; return ( { - this.handleSubmit(solution, openCompletionModal, assignments); - }} + executeChallenge={this.handleSubmit} containerRef={this.container} nextChallengePath={nextChallengePath} prevChallengePath={prevChallengePath} @@ -331,10 +344,11 @@ class ShowOdin extends Component { )} @@ -348,13 +362,7 @@ class ShowOdin extends Component { block={true} size='medium' variant='primary' - onClick={() => - this.handleSubmit( - solution, - openCompletionModal, - assignments - ) - } + onClick={this.handleSubmit} > {t('buttons.check-answer')} diff --git a/client/src/templates/Challenges/video.css b/client/src/templates/Challenges/video.css index dcbe0cfd18c..ede2677cc31 100644 --- a/client/src/templates/Challenges/video.css +++ b/client/src/templates/Challenges/video.css @@ -115,6 +115,36 @@ input:focus-visible + .video-quiz-input-visible { background: none; } -.multiple-choice-feedback p { - margin-bottom: 0; +.mcq-correct-border { + border-left-color: var(--success-background); +} + +.mcq-incorrect-border { + border-left-color: var(--danger-background); +} + +.mcq-correct { + color: var(--success-color); + border-left-color: var(--success-background); +} + +.mcq-incorrect { + color: var(--danger-color); + border-left-color: var(--danger-background); +} + +.mcq-hide-border { + border-bottom: none; +} + +.mcq-feedback { + border-top: none; +} + +.mcq-prism-correct code { + color: var(--success-color); +} + +.mcq-prism-incorrect code { + color: var(--danger-color); } diff --git a/client/src/templates/Challenges/video/show.tsx b/client/src/templates/Challenges/video/show.tsx index 9ad57e592df..5a8fcddd5bc 100644 --- a/client/src/templates/Challenges/video/show.tsx +++ b/client/src/templates/Challenges/video/show.tsx @@ -9,6 +9,7 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import type { Dispatch } from 'redux'; import { createSelector } from 'reselect'; +import { isEqual } from 'lodash-es'; import { Container, Col, Row, Button } from '@freecodecamp/ui'; // Local Utilities @@ -75,9 +76,9 @@ interface ShowVideoProps { interface ShowVideoState { subtitles: string; downloadURL: string | null; - selectedOption: number | null; - answer: number; - showWrong: boolean; + selectedMcqOptions: (number | null)[]; + submittedMcqAnswers: (number | null)[]; + showFeedback: boolean; videoIsLoaded: boolean; } @@ -88,16 +89,23 @@ class ShowVideo extends Component { constructor(props: ShowVideoProps) { super(props); + + const { + data: { + challengeNode: { + challenge: { questions } + } + } + } = this.props; + this.state = { subtitles: '', downloadURL: null, - selectedOption: null, - answer: 1, - showWrong: false, + selectedMcqOptions: questions.map(() => null), + submittedMcqAnswers: questions.map(() => null), + showFeedback: false, videoIsLoaded: false }; - - this.handleSubmit = this.handleSubmit.bind(this); } componentDidMount(): void { @@ -157,26 +165,43 @@ class ShowVideo extends Component { } } - handleSubmit(solution: number, openCompletionModal: () => void) { - if (solution - 1 === this.state.selectedOption) { - this.setState({ - showWrong: false - }); - openCompletionModal(); - } else { - this.setState({ - showWrong: true - }); - } - } + handleSubmit = () => { + const { + data: { + challengeNode: { + challenge: { questions } + } + }, + openCompletionModal + } = this.props; + + // subract 1 because the solutions are 1-indexed + const mcqSolutions = questions.map(question => question.solution - 1); - handleOptionChange = ( - changeEvent: React.ChangeEvent - ): void => { this.setState({ - showWrong: false, - selectedOption: parseInt(changeEvent.target.value, 10) + submittedMcqAnswers: this.state.selectedMcqOptions, + showFeedback: true }); + + const allMcqAnswersCorrect = isEqual( + mcqSolutions, + this.state.selectedMcqOptions + ); + + if (allMcqAnswersCorrect) { + openCompletionModal(); + } + }; + + handleMcqOptionChange = ( + questionIndex: number, + answerIndex: number + ): void => { + this.setState(state => ({ + selectedMcqOptions: state.selectedMcqOptions.map((option, index) => + index === questionIndex ? answerIndex : option + ) + })); }; onVideoLoad = () => { @@ -204,7 +229,6 @@ class ShowVideo extends Component { } } }, - openCompletionModal, openHelpModal, pageContext: { challengeMeta: { nextChallengePath, prevChallengePath } @@ -213,18 +237,13 @@ class ShowVideo extends Component { isChallengeCompleted } = this.props; - const question = questions[0]; - const { solution } = question; - const blockNameTitle = `${t( `intro:${superBlock}.blocks.${block}.title` )} - ${title}`; return ( { - this.handleSubmit(solution, openCompletionModal); - }} + executeChallenge={this.handleSubmit} containerRef={this.container} nextChallengePath={nextChallengePath} prevChallengePath={prevChallengePath} @@ -260,19 +279,18 @@ class ShowVideo extends Component { diff --git a/curriculum/challenges/_meta/lecture-what-is-html/meta.json b/curriculum/challenges/_meta/lecture-what-is-html/meta.json new file mode 100644 index 00000000000..f35972c5dca --- /dev/null +++ b/curriculum/challenges/_meta/lecture-what-is-html/meta.json @@ -0,0 +1,10 @@ +{ + "name": "What is HTML?", + "isUpcomingChange": true, + "dashedName": "lecture-what-is-html", + "order": 1, + "superBlock": "front-end-development", + "challengeOrder": [{ "id": "66f6db08d55022680a3cfbc9", "title": "What is HTML and what role does it play on the web?" }], + "helpCategory": "HTML-CSS", + "blockType": "lecture" +} diff --git a/curriculum/challenges/_meta/upcoming-python-project/meta.json b/curriculum/challenges/_meta/upcoming-python-project/meta.json index a4e226aa41e..d1fee0fdecf 100644 --- a/curriculum/challenges/_meta/upcoming-python-project/meta.json +++ b/curriculum/challenges/_meta/upcoming-python-project/meta.json @@ -10,5 +10,13 @@ { "id": "64afc37bf3b37856e035b85e", "title": "Upcoming Python Project" - } + }, + { + "id": "6703d8f42ebd112db6f7788a", + "title": "Video Layout" + }, + { + "id": "6703d9382ebd112db6f7788b", + "title": "Odin Layout" + } ]} diff --git a/curriculum/challenges/english/20-upcoming-python/upcoming-python-project/6703d8f42ebd112db6f7788a.md b/curriculum/challenges/english/20-upcoming-python/upcoming-python-project/6703d8f42ebd112db6f7788a.md new file mode 100644 index 00000000000..6a0ce3dc26c --- /dev/null +++ b/curriculum/challenges/english/20-upcoming-python/upcoming-python-project/6703d8f42ebd112db6f7788a.md @@ -0,0 +1,121 @@ +--- +id: 6703d8f42ebd112db6f7788a +title: Video Layout +challengeType: 11 +videoId: nVAaxZ34khk +dashedName: video-layout +--- + +# --description-- + +Watch the video and answer the questions below. + +# --questions-- + +## --text-- + +Question 1? + +## --answers-- + +`answer 1` + +### --feedback-- + +Feedback. + +--- + +`answer 2` + +### --feedback-- + +Feedback. + +--- + +`answer 3` + +### --feedback-- + +Feedback. + +--- + +`answer 4` + +## --video-solution-- + +4 + +## --text-- + +Question 2? + +## --answers-- + +`answer 1` + +### --feedback-- + +Feedback. + +--- + +`answer 2` + +### --feedback-- + +Feedback. + +--- + +`answer 3` + +--- + +`answer 4` + +### --feedback-- + +Feedback. + +## --video-solution-- + +3 + +## --text-- + +Question 3? + +## --answers-- + +`answer 1` + +--- + +`answer 2` + +### --feedback-- + +Feedback. + +--- + +`answer 3` + +### --feedback-- + +Feedback. + +--- + +`answer 4` + +### --feedback-- + +Feedback. + +## --video-solution-- + +1 diff --git a/curriculum/challenges/english/20-upcoming-python/upcoming-python-project/6703d9382ebd112db6f7788b.md b/curriculum/challenges/english/20-upcoming-python/upcoming-python-project/6703d9382ebd112db6f7788b.md new file mode 100644 index 00000000000..c47f2bcd7e6 --- /dev/null +++ b/curriculum/challenges/english/20-upcoming-python/upcoming-python-project/6703d9382ebd112db6f7788b.md @@ -0,0 +1,133 @@ +--- +id: 6703d9382ebd112db6f7788b +title: Odin Layout +challengeType: 19 +videoId: nVAaxZ34khk +dashedName: odin-layout +--- + +# --description-- + +Watch the video and answer the questions below. + +# --assignment-- + +assignment 1 + +--- + +assignemnt 2 + +# --questions-- + +## --text-- + +Question 1? + +## --answers-- + +`answer 1` + +### --feedback-- + +Here's some `feedback`. + +--- + +`answer 2` + +### --feedback-- + +More JS feedback: + +```js +console.log('incorrect'); +``` + +--- + +`answer 3` + +### --feedback-- + +Feedback. + +--- + +`answer 4` + +## --video-solution-- + +4 + +## --text-- + +Question 2? + +## --answers-- + +`answer 1` + +### --feedback-- + +Feedback. + +--- + +`answer 2` + +### --feedback-- + +Feedback. + +--- + +`answer 3` + +--- + +`answer 4` + +### --feedback-- + +Feedback. + +## --video-solution-- + +3 + +## --text-- + +Question 3? + +## --answers-- + +`answer 1` + +--- + +`answer 2` + +### --feedback-- + +Feedback. + +--- + +`answer 3` + +### --feedback-- + +Feedback. + +--- + +`answer 4` + +### --feedback-- + +Feedback. + +## --video-solution-- + +1 diff --git a/curriculum/challenges/english/25-front-end-development/lecture-what-is-html/66f6db08d55022680a3cfbc9.md b/curriculum/challenges/english/25-front-end-development/lecture-what-is-html/66f6db08d55022680a3cfbc9.md new file mode 100644 index 00000000000..72e701c3174 --- /dev/null +++ b/curriculum/challenges/english/25-front-end-development/lecture-what-is-html/66f6db08d55022680a3cfbc9.md @@ -0,0 +1,121 @@ +--- +id: 66f6db08d55022680a3cfbc9 +title: What Is HTML and What Role Does It Play on the Web? +challengeType: 11 +videoId: nVAaxZ34khk +dashedName: what-is-html-and-what-role-does-it-play-on-the-web +--- + +# --description-- + +Watch the video and answer the questions below. + +# --questions-- + +## --text-- + +What does HTML stand for? + +## --answers-- + +HyperText Maker Language + +### --feedback-- + +Focus on the term for describing the structure and presentation of web pages. + +--- + +HyperText Marker Language + +### --feedback-- + +Focus on the term for describing the structure and presentation of web pages. + +--- + +HyperText Markdown Language + +### --feedback-- + +Focus on the term for describing the structure and presentation of web pages. + +--- + +HyperText Markup Language + +## --video-solution-- + +4 + +## --text-- + +Which of the following is the correct syntax for a closing tag? + +## --answers-- + +`<;p>` + +### --feedback-- + +Think about the additional symbol for defining tags apart from left-angle and right-angle brackets. + +--- + +`

` + +### --feedback-- + +Think about the additional symbol for defining tags apart from left-angle and right-angle brackets. + +--- + +`

` + +--- + +`` + +### --feedback-- + +Think about the additional symbol for defining tags apart from left-angle and right-angle brackets. + +## --video-solution-- + +3 + +## --text-- + +Which of the following is a valid attribute used inside image elements? + +## --answers-- + +`src` + +--- + +`bold` + +### --feedback-- + +Consider what you often use inside an opening tag to supply additional information to the element. + +--- + +`closing` + +### --feedback-- + +Consider what you often use inside an opening tag to supply additional information to the element. + +--- + +`div` + +### --feedback-- + +Consider what you often use inside an opening tag to supply additional information to the element. + +## --video-solution-- + +1