mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
refactor(client): use generic comp for multiple choice (#56825)
Co-authored-by: moT01 <20648924+moT01@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
9c73159f10
commit
50f0c23d15
@@ -32,8 +32,6 @@ import {
|
|||||||
import Scene from '../components/scene/scene';
|
import Scene from '../components/scene/scene';
|
||||||
import { isChallengeCompletedSelector } from '../redux/selectors';
|
import { isChallengeCompletedSelector } from '../redux/selectors';
|
||||||
|
|
||||||
// Styles
|
|
||||||
import '../video.css';
|
|
||||||
import './show.css';
|
import './show.css';
|
||||||
|
|
||||||
// Redux Setup
|
// Redux Setup
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Helmet from 'react-helmet';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Container, Col, Row, Button, Spacer } from '@freecodecamp/ui';
|
import { Container, Col, Row, Button, Spacer } from '@freecodecamp/ui';
|
||||||
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
// Local Utilities
|
// Local Utilities
|
||||||
import LearnLayout from '../../../components/layouts/learn';
|
import LearnLayout from '../../../components/layouts/learn';
|
||||||
@@ -24,6 +25,13 @@ import {
|
|||||||
import { isChallengeCompletedSelector } from '../redux/selectors';
|
import { isChallengeCompletedSelector } from '../redux/selectors';
|
||||||
import { BlockTypes } from '../../../../../shared/config/blocks';
|
import { BlockTypes } from '../../../../../shared/config/blocks';
|
||||||
import Scene from '../components/scene/scene';
|
import Scene from '../components/scene/scene';
|
||||||
|
import MultipleChoiceQuestions from '../components/multiple-choice-questions';
|
||||||
|
import ChallengeExplanation from '../components/challenge-explanation';
|
||||||
|
import HelpModal from '../components/help-modal';
|
||||||
|
|
||||||
|
// Styles
|
||||||
|
import './show.css';
|
||||||
|
import '../video.css';
|
||||||
|
|
||||||
// Redux Setup
|
// Redux Setup
|
||||||
const mapStateToProps = (state: unknown) => ({
|
const mapStateToProps = (state: unknown) => ({
|
||||||
@@ -35,7 +43,8 @@ const mapDispatchToProps = {
|
|||||||
updateChallengeMeta,
|
updateChallengeMeta,
|
||||||
challengeMounted,
|
challengeMounted,
|
||||||
updateSolutionFormValues,
|
updateSolutionFormValues,
|
||||||
openCompletionModal: () => openModal('completion')
|
openCompletionModal: () => openModal('completion'),
|
||||||
|
openHelpModal: () => openModal('help')
|
||||||
};
|
};
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
@@ -46,6 +55,7 @@ interface ShowQuizProps {
|
|||||||
initTests: (xs: Test[]) => void;
|
initTests: (xs: Test[]) => void;
|
||||||
isChallengeCompleted: boolean;
|
isChallengeCompleted: boolean;
|
||||||
openCompletionModal: () => void;
|
openCompletionModal: () => void;
|
||||||
|
openHelpModal: () => void;
|
||||||
pageContext: {
|
pageContext: {
|
||||||
challengeMeta: ChallengeMeta;
|
challengeMeta: ChallengeMeta;
|
||||||
};
|
};
|
||||||
@@ -63,10 +73,12 @@ const ShowGeneric = ({
|
|||||||
block,
|
block,
|
||||||
blockType,
|
blockType,
|
||||||
description,
|
description,
|
||||||
|
explanation,
|
||||||
challengeType,
|
challengeType,
|
||||||
fields: { tests },
|
fields: { blockName, tests },
|
||||||
helpCategory,
|
helpCategory,
|
||||||
instructions,
|
instructions,
|
||||||
|
questions,
|
||||||
title,
|
title,
|
||||||
translationPending,
|
translationPending,
|
||||||
scene,
|
scene,
|
||||||
@@ -80,6 +92,7 @@ const ShowGeneric = ({
|
|||||||
initTests,
|
initTests,
|
||||||
updateChallengeMeta,
|
updateChallengeMeta,
|
||||||
openCompletionModal,
|
openCompletionModal,
|
||||||
|
openHelpModal,
|
||||||
isChallengeCompleted
|
isChallengeCompleted
|
||||||
}: ShowQuizProps) => {
|
}: ShowQuizProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -142,9 +155,36 @@ const ShowGeneric = ({
|
|||||||
setAssignmentsCompleted(a => (isCompleted ? a + 1 : a - 1));
|
setAssignmentsCompleted(a => (isCompleted ? a + 1 : a - 1));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// multiple choice questions
|
||||||
|
const [selectedMcqOptions, setSelectedMcqOptions] = useState(
|
||||||
|
questions.map<number | null>(() => null)
|
||||||
|
);
|
||||||
|
const [submittedMcqAnswers, setSubmittedMcqAnswers] = useState(
|
||||||
|
questions.map<number | null>(() => null)
|
||||||
|
);
|
||||||
|
const [showFeedback, setShowFeedback] = useState(false);
|
||||||
|
|
||||||
|
const handleMcqOptionChange = (
|
||||||
|
questionIndex: number,
|
||||||
|
answerIndex: number
|
||||||
|
): void => {
|
||||||
|
setSelectedMcqOptions(prev =>
|
||||||
|
prev.map((option, index) =>
|
||||||
|
index === questionIndex ? answerIndex : option
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// submit
|
// submit
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
if (assignments.length == 0 || allAssignmentsCompleted) {
|
const hasCompletedAssignments =
|
||||||
|
assignments.length === 0 || allAssignmentsCompleted;
|
||||||
|
const mcqSolutions = questions.map(question => question.solution - 1);
|
||||||
|
const mcqCorrect = isEqual(mcqSolutions, selectedMcqOptions);
|
||||||
|
|
||||||
|
setSubmittedMcqAnswers(selectedMcqOptions);
|
||||||
|
setShowFeedback(true);
|
||||||
|
if (hasCompletedAssignments && mcqCorrect) {
|
||||||
openCompletionModal();
|
openCompletionModal();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -214,15 +254,34 @@ const ShowGeneric = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{!!questions && (
|
||||||
|
<MultipleChoiceQuestions
|
||||||
|
questions={questions}
|
||||||
|
selectedOptions={selectedMcqOptions}
|
||||||
|
handleOptionChange={handleMcqOptionChange}
|
||||||
|
submittedMcqAnswers={submittedMcqAnswers}
|
||||||
|
showFeedback={showFeedback}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{explanation ? (
|
||||||
|
<ChallengeExplanation explanation={explanation} />
|
||||||
|
) : null}
|
||||||
|
|
||||||
<Button block={true} variant='primary' onClick={handleSubmit}>
|
<Button block={true} variant='primary' onClick={handleSubmit}>
|
||||||
{blockType === BlockTypes.review
|
{blockType === BlockTypes.review
|
||||||
? t('buttons.submit')
|
? t('buttons.submit')
|
||||||
: t('buttons.check-answer')}
|
: t('buttons.check-answer')}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Spacer size='xxs' />
|
||||||
|
<Button block={true} variant='primary' onClick={openHelpModal}>
|
||||||
|
{t('buttons.ask-for-help')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Spacer size='l' />
|
<Spacer size='l' />
|
||||||
</Col>
|
</Col>
|
||||||
<CompletionModal />
|
<CompletionModal />
|
||||||
|
<HelpModal challengeTitle={title} challengeBlock={blockName} />
|
||||||
</Row>
|
</Row>
|
||||||
</Container>
|
</Container>
|
||||||
</LearnLayout>
|
</LearnLayout>
|
||||||
@@ -248,6 +307,7 @@ export const query = graphql`
|
|||||||
blockType
|
blockType
|
||||||
challengeType
|
challengeType
|
||||||
description
|
description
|
||||||
|
explanation
|
||||||
helpCategory
|
helpCategory
|
||||||
instructions
|
instructions
|
||||||
fields {
|
fields {
|
||||||
@@ -258,6 +318,14 @@ export const query = graphql`
|
|||||||
testString
|
testString
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
questions {
|
||||||
|
text
|
||||||
|
answers {
|
||||||
|
answer
|
||||||
|
feedback
|
||||||
|
}
|
||||||
|
solution
|
||||||
|
}
|
||||||
scene {
|
scene {
|
||||||
setup {
|
setup {
|
||||||
background
|
background
|
||||||
|
|||||||
@@ -1,478 +0,0 @@
|
|||||||
// Package Utilities
|
|
||||||
import { graphql } from 'gatsby';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import Helmet from 'react-helmet';
|
|
||||||
import { ObserveKeys } from 'react-hotkeys';
|
|
||||||
import type { TFunction } from 'i18next';
|
|
||||||
import { withTranslation } from 'react-i18next';
|
|
||||||
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, Spacer } from '@freecodecamp/ui';
|
|
||||||
import ShortcutsModal from '../components/shortcuts-modal';
|
|
||||||
|
|
||||||
// Local Utilities
|
|
||||||
import LearnLayout from '../../../components/layouts/learn';
|
|
||||||
import { ChallengeNode, ChallengeMeta, Test } from '../../../redux/prop-types';
|
|
||||||
import Hotkeys from '../components/hotkeys';
|
|
||||||
import VideoPlayer from '../components/video-player';
|
|
||||||
import CompletionModal from '../components/completion-modal';
|
|
||||||
import HelpModal from '../components/help-modal';
|
|
||||||
import Scene from '../components/scene/scene';
|
|
||||||
import PrismFormatted from '../components/prism-formatted';
|
|
||||||
import ChallengeTitle from '../components/challenge-title';
|
|
||||||
import ChallegeExplanation from '../components/challenge-explanation';
|
|
||||||
import MultipleChoiceQuestions from '../components/multiple-choice-questions';
|
|
||||||
import Assignments from '../components/assignments';
|
|
||||||
import {
|
|
||||||
challengeMounted,
|
|
||||||
updateChallengeMeta,
|
|
||||||
openModal,
|
|
||||||
updateSolutionFormValues,
|
|
||||||
initTests
|
|
||||||
} from '../redux/actions';
|
|
||||||
import { isChallengeCompletedSelector } from '../redux/selectors';
|
|
||||||
|
|
||||||
// Styles
|
|
||||||
import './show.css';
|
|
||||||
import '../video.css';
|
|
||||||
|
|
||||||
// Redux Setup
|
|
||||||
const mapStateToProps = createSelector(
|
|
||||||
isChallengeCompletedSelector,
|
|
||||||
(isChallengeCompleted: boolean) => ({
|
|
||||||
isChallengeCompleted
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const mapDispatchToProps = (dispatch: Dispatch) =>
|
|
||||||
bindActionCreators(
|
|
||||||
{
|
|
||||||
initTests,
|
|
||||||
updateChallengeMeta,
|
|
||||||
challengeMounted,
|
|
||||||
updateSolutionFormValues,
|
|
||||||
openCompletionModal: () => openModal('completion'),
|
|
||||||
openHelpModal: () => openModal('help')
|
|
||||||
},
|
|
||||||
dispatch
|
|
||||||
);
|
|
||||||
|
|
||||||
// Types
|
|
||||||
interface ShowOdinProps {
|
|
||||||
challengeMounted: (arg0: string) => void;
|
|
||||||
data: { challengeNode: ChallengeNode };
|
|
||||||
initTests: (xs: Test[]) => void;
|
|
||||||
isChallengeCompleted: boolean;
|
|
||||||
openCompletionModal: () => void;
|
|
||||||
openHelpModal: () => void;
|
|
||||||
pageContext: {
|
|
||||||
challengeMeta: ChallengeMeta;
|
|
||||||
};
|
|
||||||
t: TFunction;
|
|
||||||
updateChallengeMeta: (arg0: ChallengeMeta) => void;
|
|
||||||
updateSolutionFormValues: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ShowOdinState {
|
|
||||||
subtitles: string;
|
|
||||||
downloadURL: string | null;
|
|
||||||
selectedMcqOptions: (number | null)[];
|
|
||||||
submittedMcqAnswers: (number | null)[];
|
|
||||||
showFeedback: boolean;
|
|
||||||
assignmentsCompleted: number;
|
|
||||||
allAssignmentsCompleted: boolean;
|
|
||||||
videoIsLoaded: boolean;
|
|
||||||
isScenePlaying: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Component
|
|
||||||
class ShowOdin extends Component<ShowOdinProps, ShowOdinState> {
|
|
||||||
static displayName: string;
|
|
||||||
private container: React.RefObject<HTMLElement> = React.createRef();
|
|
||||||
|
|
||||||
constructor(props: ShowOdinProps) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
const {
|
|
||||||
data: {
|
|
||||||
challengeNode: {
|
|
||||||
challenge: { assignments, questions }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
subtitles: '',
|
|
||||||
downloadURL: null,
|
|
||||||
selectedMcqOptions: questions.map(() => null),
|
|
||||||
submittedMcqAnswers: questions.map(() => null),
|
|
||||||
showFeedback: false,
|
|
||||||
assignmentsCompleted: 0,
|
|
||||||
allAssignmentsCompleted: assignments.length == 0,
|
|
||||||
videoIsLoaded: false,
|
|
||||||
isScenePlaying: false
|
|
||||||
};
|
|
||||||
|
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount(): void {
|
|
||||||
const {
|
|
||||||
challengeMounted,
|
|
||||||
data: {
|
|
||||||
challengeNode: {
|
|
||||||
challenge: {
|
|
||||||
fields: { tests },
|
|
||||||
title,
|
|
||||||
challengeType,
|
|
||||||
helpCategory
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
pageContext: { challengeMeta },
|
|
||||||
initTests,
|
|
||||||
updateChallengeMeta
|
|
||||||
} = this.props;
|
|
||||||
initTests(tests);
|
|
||||||
updateChallengeMeta({
|
|
||||||
...challengeMeta,
|
|
||||||
title,
|
|
||||||
challengeType,
|
|
||||||
helpCategory
|
|
||||||
});
|
|
||||||
challengeMounted(challengeMeta.id);
|
|
||||||
this.container.current?.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps: ShowOdinProps): void {
|
|
||||||
const {
|
|
||||||
data: {
|
|
||||||
challengeNode: {
|
|
||||||
challenge: { title: prevTitle }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} = prevProps;
|
|
||||||
const {
|
|
||||||
challengeMounted,
|
|
||||||
data: {
|
|
||||||
challengeNode: {
|
|
||||||
challenge: { title: currentTitle, challengeType, helpCategory }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
pageContext: { challengeMeta },
|
|
||||||
updateChallengeMeta
|
|
||||||
} = this.props;
|
|
||||||
if (prevTitle !== currentTitle) {
|
|
||||||
updateChallengeMeta({
|
|
||||||
...challengeMeta,
|
|
||||||
title: currentTitle,
|
|
||||||
challengeType,
|
|
||||||
helpCategory
|
|
||||||
});
|
|
||||||
challengeMounted(challengeMeta.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
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 = (
|
|
||||||
event: React.ChangeEvent<HTMLInputElement>,
|
|
||||||
totalAssignments: number
|
|
||||||
): void => {
|
|
||||||
const assignmentsCompleted = event.target.checked
|
|
||||||
? this.state.assignmentsCompleted + 1
|
|
||||||
: this.state.assignmentsCompleted - 1;
|
|
||||||
const allAssignmentsCompleted = totalAssignments === assignmentsCompleted;
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
assignmentsCompleted,
|
|
||||||
allAssignmentsCompleted
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onVideoLoad = () => {
|
|
||||||
this.setState({
|
|
||||||
videoIsLoaded: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
setIsScenePlaying = (shouldPlay: boolean) => {
|
|
||||||
this.setState({
|
|
||||||
isScenePlaying: shouldPlay
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
data: {
|
|
||||||
challengeNode: {
|
|
||||||
challenge: {
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
instructions,
|
|
||||||
explanation,
|
|
||||||
superBlock,
|
|
||||||
block,
|
|
||||||
videoId,
|
|
||||||
videoLocaleIds,
|
|
||||||
bilibiliIds,
|
|
||||||
fields: { blockName },
|
|
||||||
questions,
|
|
||||||
assignments,
|
|
||||||
translationPending,
|
|
||||||
scene
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
openHelpModal,
|
|
||||||
pageContext: {
|
|
||||||
challengeMeta: { nextChallengePath, prevChallengePath }
|
|
||||||
},
|
|
||||||
t,
|
|
||||||
isChallengeCompleted
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const blockNameTitle = `${t(
|
|
||||||
`intro:${superBlock}.blocks.${block}.title`
|
|
||||||
)} - ${title}`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Hotkeys
|
|
||||||
executeChallenge={this.handleSubmit}
|
|
||||||
containerRef={this.container}
|
|
||||||
nextChallengePath={nextChallengePath}
|
|
||||||
prevChallengePath={prevChallengePath}
|
|
||||||
playScene={() => this.setIsScenePlaying(true)}
|
|
||||||
>
|
|
||||||
<LearnLayout>
|
|
||||||
<Helmet
|
|
||||||
title={`${blockNameTitle} | ${t('learn.learn')} | freeCodeCamp.org`}
|
|
||||||
/>
|
|
||||||
<Container>
|
|
||||||
<Row>
|
|
||||||
{videoId && (
|
|
||||||
<Col lg={10} lgOffset={1} md={10} mdOffset={1}>
|
|
||||||
<Spacer size='m' />
|
|
||||||
<VideoPlayer
|
|
||||||
bilibiliIds={bilibiliIds}
|
|
||||||
onVideoLoad={this.onVideoLoad}
|
|
||||||
title={title}
|
|
||||||
videoId={videoId}
|
|
||||||
videoIsLoaded={this.state.videoIsLoaded}
|
|
||||||
videoLocaleIds={videoLocaleIds}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Col md={8} mdOffset={2} sm={10} smOffset={1} xs={12}>
|
|
||||||
<Spacer size='m' />
|
|
||||||
<ChallengeTitle
|
|
||||||
isCompleted={isChallengeCompleted}
|
|
||||||
translationPending={translationPending}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</ChallengeTitle>
|
|
||||||
<PrismFormatted className={'line-numbers'} text={description} />
|
|
||||||
<Spacer size='m' />
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
{scene && (
|
|
||||||
<Scene
|
|
||||||
scene={scene}
|
|
||||||
isPlaying={this.state.isScenePlaying}
|
|
||||||
setIsPlaying={this.setIsScenePlaying}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Col md={8} mdOffset={2} sm={10} smOffset={1} xs={12}>
|
|
||||||
{instructions && (
|
|
||||||
<PrismFormatted
|
|
||||||
className={'line-numbers'}
|
|
||||||
text={instructions}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<ObserveKeys>
|
|
||||||
{assignments.length > 0 && (
|
|
||||||
<Assignments
|
|
||||||
assignments={assignments}
|
|
||||||
allAssignmentsCompleted={
|
|
||||||
this.state.allAssignmentsCompleted
|
|
||||||
}
|
|
||||||
handleAssignmentChange={this.handleAssignmentChange}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<MultipleChoiceQuestions
|
|
||||||
questions={questions}
|
|
||||||
selectedOptions={this.state.selectedMcqOptions}
|
|
||||||
handleOptionChange={this.handleMcqOptionChange}
|
|
||||||
submittedMcqAnswers={this.state.submittedMcqAnswers}
|
|
||||||
showFeedback={this.state.showFeedback}
|
|
||||||
/>
|
|
||||||
</ObserveKeys>
|
|
||||||
|
|
||||||
{explanation ? (
|
|
||||||
<ChallegeExplanation explanation={explanation} />
|
|
||||||
) : (
|
|
||||||
<Spacer size='m' />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button
|
|
||||||
block={true}
|
|
||||||
size='medium'
|
|
||||||
variant='primary'
|
|
||||||
onClick={this.handleSubmit}
|
|
||||||
>
|
|
||||||
{t('buttons.check-answer')}
|
|
||||||
</Button>
|
|
||||||
<Spacer size='xxs' />
|
|
||||||
<Button
|
|
||||||
block={true}
|
|
||||||
size='medium'
|
|
||||||
variant='primary'
|
|
||||||
onClick={openHelpModal}
|
|
||||||
>
|
|
||||||
{t('buttons.ask-for-help')}
|
|
||||||
</Button>
|
|
||||||
<Spacer size='l' />
|
|
||||||
</Col>
|
|
||||||
<CompletionModal />
|
|
||||||
<HelpModal challengeTitle={title} challengeBlock={blockName} />
|
|
||||||
</Row>
|
|
||||||
</Container>
|
|
||||||
<ShortcutsModal />
|
|
||||||
</LearnLayout>
|
|
||||||
</Hotkeys>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ShowOdin.displayName = 'ShowOdin';
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(withTranslation()(ShowOdin));
|
|
||||||
|
|
||||||
export const query = graphql`
|
|
||||||
query TheOdinProject($id: String!) {
|
|
||||||
challengeNode(id: { eq: $id }) {
|
|
||||||
challenge {
|
|
||||||
videoId
|
|
||||||
videoLocaleIds {
|
|
||||||
espanol
|
|
||||||
italian
|
|
||||||
portuguese
|
|
||||||
}
|
|
||||||
bilibiliIds {
|
|
||||||
aid
|
|
||||||
bvid
|
|
||||||
cid
|
|
||||||
}
|
|
||||||
title
|
|
||||||
description
|
|
||||||
instructions
|
|
||||||
explanation
|
|
||||||
challengeType
|
|
||||||
helpCategory
|
|
||||||
superBlock
|
|
||||||
block
|
|
||||||
fields {
|
|
||||||
slug
|
|
||||||
blockName
|
|
||||||
tests {
|
|
||||||
text
|
|
||||||
testString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
questions {
|
|
||||||
text
|
|
||||||
answers {
|
|
||||||
answer
|
|
||||||
feedback
|
|
||||||
}
|
|
||||||
solution
|
|
||||||
}
|
|
||||||
scene {
|
|
||||||
setup {
|
|
||||||
background
|
|
||||||
characters {
|
|
||||||
character
|
|
||||||
position {
|
|
||||||
x
|
|
||||||
y
|
|
||||||
z
|
|
||||||
}
|
|
||||||
opacity
|
|
||||||
}
|
|
||||||
audio {
|
|
||||||
filename
|
|
||||||
startTime
|
|
||||||
startTimestamp
|
|
||||||
finishTimestamp
|
|
||||||
}
|
|
||||||
alwaysShowDialogue
|
|
||||||
}
|
|
||||||
commands {
|
|
||||||
background
|
|
||||||
character
|
|
||||||
position {
|
|
||||||
x
|
|
||||||
y
|
|
||||||
z
|
|
||||||
}
|
|
||||||
opacity
|
|
||||||
startTime
|
|
||||||
finishTime
|
|
||||||
dialogue {
|
|
||||||
text
|
|
||||||
align
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
translationPending
|
|
||||||
assignments
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@@ -32,9 +32,6 @@ import {
|
|||||||
} from '../redux/actions';
|
} from '../redux/actions';
|
||||||
import { isChallengeCompletedSelector } from '../redux/selectors';
|
import { isChallengeCompletedSelector } from '../redux/selectors';
|
||||||
|
|
||||||
// Styles
|
|
||||||
import '../video.css';
|
|
||||||
|
|
||||||
// Redux Setup
|
// Redux Setup
|
||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
isChallengeCompletedSelector,
|
isChallengeCompletedSelector,
|
||||||
|
|||||||
@@ -36,11 +36,6 @@ const video = path.resolve(
|
|||||||
'../../src/templates/Challenges/video/show.tsx'
|
'../../src/templates/Challenges/video/show.tsx'
|
||||||
);
|
);
|
||||||
|
|
||||||
const odin = path.resolve(
|
|
||||||
__dirname,
|
|
||||||
'../../src/templates/Challenges/odin/show.tsx'
|
|
||||||
);
|
|
||||||
|
|
||||||
const exam = path.resolve(
|
const exam = path.resolve(
|
||||||
__dirname,
|
__dirname,
|
||||||
'../../src/templates/Challenges/exam/show.tsx'
|
'../../src/templates/Challenges/exam/show.tsx'
|
||||||
@@ -69,7 +64,6 @@ const views = {
|
|||||||
quiz,
|
quiz,
|
||||||
video,
|
video,
|
||||||
codeAlly,
|
codeAlly,
|
||||||
odin,
|
|
||||||
exam,
|
exam,
|
||||||
msTrophy,
|
msTrophy,
|
||||||
fillInTheBlank,
|
fillInTheBlank,
|
||||||
|
|||||||
@@ -96,13 +96,13 @@ export const viewTypes = {
|
|||||||
[codeAllyPractice]: 'codeAlly',
|
[codeAllyPractice]: 'codeAlly',
|
||||||
[codeAllyCert]: 'codeAlly',
|
[codeAllyCert]: 'codeAlly',
|
||||||
[multifileCertProject]: 'classic',
|
[multifileCertProject]: 'classic',
|
||||||
[theOdinProject]: 'odin',
|
[theOdinProject]: 'generic',
|
||||||
[colab]: 'frontend',
|
[colab]: 'frontend',
|
||||||
[exam]: 'exam',
|
[exam]: 'exam',
|
||||||
[msTrophy]: 'msTrophy',
|
[msTrophy]: 'msTrophy',
|
||||||
[multipleChoice]: 'odin',
|
[multipleChoice]: 'generic',
|
||||||
[python]: 'modern',
|
[python]: 'modern',
|
||||||
[dialogue]: 'generic', // TODO: use generic challengeType for dialogues
|
[dialogue]: 'generic',
|
||||||
[fillInTheBlank]: 'fillInTheBlank',
|
[fillInTheBlank]: 'fillInTheBlank',
|
||||||
[multifilePythonCertProject]: 'classic',
|
[multifilePythonCertProject]: 'classic',
|
||||||
[generic]: 'generic'
|
[generic]: 'generic'
|
||||||
|
|||||||
Reference in New Issue
Block a user