diff --git a/client/src/templates/Challenges/components/CompletionVideoModal.js b/client/src/templates/Challenges/components/CompletionVideoModal.js deleted file mode 100644 index ff30e4b9dc..0000000000 --- a/client/src/templates/Challenges/components/CompletionVideoModal.js +++ /dev/null @@ -1,301 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import noop from 'lodash/noop'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import { Button, Modal } from '@freecodecamp/react-bootstrap'; -import { useStaticQuery, graphql } from 'gatsby'; -import SanitizedSpan from '../components/SanitizedSpan'; - -import Login from '../../../components/Header/components/Login'; - -import './completion-modal.css'; - -import { - closeModal, - submitChallenge, - completedChallengesIds, - isCompletionModalOpenSelector, - challengeFilesSelector, - challengeMetaSelector, - lastBlockChalSubmitted -} from '../redux'; - -import { isSignedInSelector, executeGA } from '../../../redux'; - -const mapStateToProps = createSelector( - challengeFilesSelector, - challengeMetaSelector, - completedChallengesIds, - isCompletionModalOpenSelector, - isSignedInSelector, - (files, { title, id }, completedChallengesIds, isOpen, isSignedIn) => ({ - files, - title, - id, - completedChallengesIds, - isOpen, - isSignedIn - }) -); - -const mapDispatchToProps = function(dispatch) { - const dispatchers = { - close: () => dispatch(closeModal('completion')), - submitChallenge: () => { - dispatch(submitChallenge()); - }, - lastBlockChalSubmitted: () => { - dispatch(lastBlockChalSubmitted()); - }, - executeGA - }; - return () => dispatchers; -}; - -const propTypes = { - answers: PropTypes.array, - blockName: PropTypes.string, - close: PropTypes.func.isRequired, - completedChallengesIds: PropTypes.array, - currentBlockIds: PropTypes.array, - executeGA: PropTypes.func, - files: PropTypes.object.isRequired, - id: PropTypes.string, - isOpen: PropTypes.bool, - isSignedIn: PropTypes.bool.isRequired, - lastBlockChalSubmitted: PropTypes.func, - question: PropTypes.string, - solution: PropTypes.number, - submitChallenge: PropTypes.func.isRequired, - title: PropTypes.string -}; - -export function getCompletedPercent( - completedChallengesIds = [], - currentBlockIds = [], - currentChallengeId -) { - completedChallengesIds = completedChallengesIds.includes(currentChallengeId) - ? completedChallengesIds - : [...completedChallengesIds, currentChallengeId]; - - const completedChallengesInBlock = completedChallengesIds.filter(id => { - return currentBlockIds.includes(id); - }); - - const completedPercent = Math.round( - (completedChallengesInBlock.length / currentBlockIds.length) * 100 - ); - - return completedPercent > 100 ? 100 : completedPercent; -} - -export class CompletionModalInner extends Component { - constructor(props) { - super(props); - this.handleSubmit = this.handleSubmit.bind(this); - this.handleKeypress = this.handleKeypress.bind(this); - } - - state = { - downloadURL: null, - completedPercent: 0, - selectedOption: 0, - answer: 1, - showWrong: false - }; - - static getDerivedStateFromProps(props, state) { - const { files, isOpen } = props; - if (!isOpen) { - return null; - } - const { downloadURL } = state; - if (downloadURL) { - URL.revokeObjectURL(downloadURL); - } - let newURL = null; - if (Object.keys(files).length) { - const filesForDownload = Object.keys(files) - .map(key => files[key]) - .reduce((allFiles, { path, contents }) => { - const beforeText = `** start of ${path} **\n\n`; - const afterText = `\n\n** end of ${path} **\n\n`; - allFiles += - files.length > 1 ? beforeText + contents + afterText : contents; - return allFiles; - }, ''); - const blob = new Blob([filesForDownload], { - type: 'text/json' - }); - newURL = URL.createObjectURL(blob); - } - - const { completedChallengesIds, currentBlockIds, id, isSignedIn } = props; - let completedPercent = isSignedIn - ? getCompletedPercent(completedChallengesIds, currentBlockIds, id) - : 0; - return { downloadURL: newURL, completedPercent: completedPercent }; - } - - handleKeypress(e) { - if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { - e.preventDefault(); - // Since Hotkeys also listens to Ctrl + Enter we have to stop this event - // getting to it. - e.stopPropagation(); - this.handleSubmit(); - } - } - - handleSubmit() { - if (this.props.solution - 1 === this.state.selectedOption) { - this.setState({ - showWrong: false - }); - this.props.submitChallenge(); - this.checkBlockCompletion(); - } else { - this.setState({ - showWrong: true - }); - } - } - - // check block completion for donation - checkBlockCompletion() { - if ( - this.state.completedPercent === 100 && - !this.props.completedChallengesIds.includes(this.props.id) - ) { - this.props.lastBlockChalSubmitted(); - } - } - - componentWillUnmount() { - if (this.state.downloadURL) { - URL.revokeObjectURL(this.state.downloadURL); - } - this.props.close(); - } - - handleOptionChange = changeEvent => { - console.log(this.state.selectedOption); - this.setState({ - selectedOption: parseInt(changeEvent.target.value, 10) - }); - }; - - render() { - const { close, isOpen, isSignedIn, question, answers } = this.props; - - if (isOpen) { - executeGA({ type: 'modal', data: '/completion-modal' }); - } - - return ( - - - Video Quiz - - - -
- {answers.map((option, index) => ( -
- -
- ))} -
-
- Wrong. Try again. -
-
- - {isSignedIn ? null : ( - - Sign in to save your progress - - )} - - -
- ); - } -} - -CompletionModalInner.propTypes = propTypes; - -const useCurrentBlockIds = blockName => { - const { - allChallengeNode: { edges } - } = useStaticQuery(graphql` - query getCurrentBlockNodesVid { - allChallengeNode(sort: { fields: [superOrder, order, challengeOrder] }) { - edges { - node { - fields { - blockName - } - id - } - } - } - } - `); - - const currentBlockIds = edges - .filter(edge => edge.node.fields.blockName === blockName) - .map(edge => edge.node.id); - return currentBlockIds; -}; - -const CompletionModal = props => { - const currentBlockIds = useCurrentBlockIds(props.blockName || ''); - return ; -}; - -CompletionModal.displayName = 'CompletionModal'; -CompletionModal.propTypes = propTypes; - -export default connect( - mapStateToProps, - mapDispatchToProps -)(CompletionModal); diff --git a/client/src/templates/Challenges/redux/completion-epic.js b/client/src/templates/Challenges/redux/completion-epic.js index 71022acbed..7d8dc0b126 100644 --- a/client/src/templates/Challenges/redux/completion-epic.js +++ b/client/src/templates/Challenges/redux/completion-epic.js @@ -50,8 +50,12 @@ function postChallenge(update, username) { } function submitModern(type, state) { + const challengeType = state.challenge.challengeMeta.challengeType; const tests = challengeTestsSelector(state); - if (tests.length > 0 && tests.every(test => test.pass && !test.err)) { + if ( + challengeType === 11 || + (tests.length > 0 && tests.every(test => test.pass && !test.err)) + ) { if (type === types.checkChallenge) { return of({ type: 'this was a check challenge' }); } diff --git a/client/src/templates/Challenges/video/Show.js b/client/src/templates/Challenges/video/Show.js index 6f3616323c..20e8cf38aa 100644 --- a/client/src/templates/Challenges/video/Show.js +++ b/client/src/templates/Challenges/video/Show.js @@ -1,3 +1,4 @@ +// Package Utilities import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { Button, Grid, Col, Row } from '@freecodecamp/react-bootstrap'; @@ -6,24 +7,35 @@ import { bindActionCreators } from 'redux'; import { graphql } from 'gatsby'; import Helmet from 'react-helmet'; import YouTube from 'react-youtube'; +import { createSelector } from 'reselect'; +// Local Utilities +import SanitizedSpan from '../components/SanitizedSpan'; import { ChallengeNode } from '../../../redux/propTypes'; +import LearnLayout from '../../../components/layouts/Learn'; +import ChallengeTitle from '../components/Challenge-Title'; +import ChallengeDescription from '../components/Challenge-Description'; +import Spacer from '../../../components/helpers/Spacer'; +import CompletionModal from '../components/CompletionModal'; +import Hotkeys from '../components/Hotkeys'; import { + isChallengeCompletedSelector, challengeMounted, updateChallengeMeta, openModal, updateProjectFormValues } from '../redux'; -import LearnLayout from '../../../components/layouts/Learn'; -import ChallengeTitle from '../components/Challenge-Title'; -import ChallengeDescription from '../components/Challenge-Description'; -import Spacer from '../../../components/helpers/Spacer'; -import CompletionVideoModal from '../components/CompletionVideoModal'; -import HelpModal from '../components/HelpModal'; -import Hotkeys from '../components/Hotkeys'; +// Styles +import './show.css'; -const mapStateToProps = () => ({}); +// Redux Setup +const mapStateToProps = createSelector( + isChallengeCompletedSelector, + isChallengeCompleted => ({ + isChallengeCompleted + }) +); const mapDispatchToProps = dispatch => bindActionCreators( { @@ -35,12 +47,14 @@ const mapDispatchToProps = dispatch => dispatch ); +// Proptypes const propTypes = { challengeMounted: PropTypes.func.isRequired, data: PropTypes.shape({ challengeNode: ChallengeNode }), description: PropTypes.string, + isChallengeCompleted: PropTypes.bool, openCompletionModal: PropTypes.func.isRequired, pageContext: PropTypes.shape({ challengeMeta: PropTypes.object @@ -49,12 +63,19 @@ const propTypes = { updateProjectFormValues: PropTypes.func.isRequired }; +// Component export class Project extends Component { constructor(props) { super(props); this.state = { - subtitles: '' + subtitles: '', + downloadURL: null, + selectedOption: 0, + answer: 1, + showWrong: false }; + + this.handleSubmit = this.handleSubmit.bind(this); } componentDidMount() { @@ -95,6 +116,25 @@ export class Project extends Component { } } + handleSubmit(solution, openCompletionModal) { + if (solution - 1 === this.state.selectedOption) { + this.setState({ + showWrong: false + }); + openCompletionModal(); + } else { + this.setState({ + showWrong: true + }); + } + } + + handleOptionChange = changeEvent => { + this.setState({ + selectedOption: parseInt(changeEvent.target.value, 10) + }); + }; + render() { const { data: { @@ -109,10 +149,11 @@ export class Project extends Component { openCompletionModal, pageContext: { challengeMeta: { introPath, nextChallengePath, prevChallengePath } - } + }, + isChallengeCompleted } = this.props; - const blockNameTitle = `${blockName} - ${title}`; + const blockNameTitle = `${blockName} - ${title}`; return ( (this._container = c)} @@ -126,7 +167,9 @@ export class Project extends Component { - {blockNameTitle} + + {blockNameTitle} +
- + + + +
+ {answers.map((option, index) => ( + + ))} +
+ +
+ Wrong. Try again. +
+ - - + diff --git a/client/src/templates/Challenges/video/show.css b/client/src/templates/Challenges/video/show.css new file mode 100644 index 0000000000..5879c8b9d2 --- /dev/null +++ b/client/src/templates/Challenges/video/show.css @@ -0,0 +1,43 @@ +.video-quiz-options { + padding: 1px 16px; + background-color: var(--tertiary-background); +} + +.video-quiz-option-label { + display: block; + margin: 20px; + cursor: pointer; + display: flex; + font-weight: normal; +} + +.video-quiz-input-hidden { + position: absolute; + left: -9999px; +} + +.video-quiz-input-visible { + margin-right: 15px; + position: relative; + top: 2px; + display: inline-block; + min-width: 20px; + min-height: 20px; + max-width: 20px; + max-height: 20px; + border-radius: 50%; + background-color: var(--secondary-background); + border: 2px solid var(--primary-color); + position: relative; +} + +.video-quiz-selected-input { + width: 10px; + height: 10px; + position: absolute; + top: 50%; + left: 50%; + background-color: var(--primary-color); + border-radius: 50%; + transform: translate(-50%, -50%); +} diff --git a/client/utils/challengeTypes.js b/client/utils/challengeTypes.js index f026be0557..4158942735 100644 --- a/client/utils/challengeTypes.js +++ b/client/utils/challengeTypes.js @@ -125,5 +125,6 @@ exports.helpCategory = { 'data-analysis-with-python': 'Certification Projects', 'data-analysis-with-python-projects': 'Certification Projects', 'machine-learning-with-python': 'Certification Projects', - 'machine-learning-with-python-projects': 'Certification Projects' + 'machine-learning-with-python-projects': 'Certification Projects', + 'python-for-everybody': 'Python' }; diff --git a/curriculum/schema/challengeSchema.js b/curriculum/schema/challengeSchema.js index f7846817cd..11bc46fa16 100644 --- a/curriculum/schema/challengeSchema.js +++ b/curriculum/schema/challengeSchema.js @@ -10,7 +10,7 @@ function getSchemaForLang(lang) { challengeOrder: Joi.number(), challengeType: Joi.number() .min(0) - .max(10) + .max(11) .required(), checksum: Joi.number(), dashedName: Joi.string(),