mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
Add 'watch a video' button and video modal.
This commit is contained in:
committed by
Mrugesh Mohapatra
parent
5024e69b69
commit
6cc8e5bc58
@@ -53,7 +53,8 @@ export const ChallengeNode = PropTypes.shape({
|
||||
superBlock: PropTypes.string,
|
||||
tail: PropTypes.arrayOf(PropTypes.string),
|
||||
time: PropTypes.string,
|
||||
title: PropTypes.string
|
||||
title: PropTypes.string,
|
||||
videoUrl: PropTypes.string
|
||||
});
|
||||
|
||||
export const AllChallengeNode = PropTypes.shape({
|
||||
|
||||
@@ -13,6 +13,7 @@ import SidePanel from '../components/Side-Panel';
|
||||
import Output from '../components/Output';
|
||||
import CompletionModal from '../components/CompletionModal';
|
||||
import HelpModal from '../components/HelpModal';
|
||||
import VideoModal from '../components/VideoModal';
|
||||
import ResetModal from '../components/ResetModal';
|
||||
|
||||
import { randomCompliment } from '../utils/get-words';
|
||||
@@ -163,7 +164,8 @@ class ShowClassic extends PureComponent {
|
||||
challengeType,
|
||||
fields: { blockName, slug },
|
||||
title,
|
||||
description
|
||||
description,
|
||||
videoUrl
|
||||
}
|
||||
},
|
||||
files,
|
||||
@@ -223,6 +225,7 @@ class ShowClassic extends PureComponent {
|
||||
guideUrl={createGuideUrl(slug)}
|
||||
section={dasherize(blockName)}
|
||||
title={blockNameTitle}
|
||||
videoUrl={videoUrl}
|
||||
/>
|
||||
</ReflexElement>
|
||||
<ReflexSplitter propagate={true} {...this.resizeProps} />
|
||||
@@ -244,6 +247,7 @@ class ShowClassic extends PureComponent {
|
||||
|
||||
<CompletionModal />
|
||||
<HelpModal />
|
||||
<VideoModal videoUrl={videoUrl}/>
|
||||
<ResetModal />
|
||||
</Fragment>
|
||||
);
|
||||
@@ -261,6 +265,7 @@ export const query = graphql`
|
||||
title
|
||||
description
|
||||
challengeType
|
||||
videoUrl
|
||||
fields {
|
||||
slug
|
||||
blockName
|
||||
|
||||
@@ -34,7 +34,8 @@ const propTypes = {
|
||||
initConsole: PropTypes.func.isRequired,
|
||||
section: PropTypes.string,
|
||||
tests: PropTypes.arrayOf(PropTypes.object),
|
||||
title: PropTypes.string
|
||||
title: PropTypes.string,
|
||||
videoUrl: PropTypes.string
|
||||
};
|
||||
|
||||
export class SidePanel extends PureComponent {
|
||||
@@ -42,7 +43,7 @@ export class SidePanel extends PureComponent {
|
||||
super(props);
|
||||
this.bindTopDiv = this.bindTopDiv.bind(this);
|
||||
MathJax.Hub.Config({
|
||||
tex2jax: {
|
||||
tex2jax: {
|
||||
inlineMath: [['$', '$'], ['\\(', '\\)']],
|
||||
processEscapes: true,
|
||||
processClass: 'rosetta-code'
|
||||
@@ -74,16 +75,23 @@ export class SidePanel extends PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { title, description, guideUrl, tests, section } = this.props;
|
||||
const {
|
||||
title,
|
||||
description,
|
||||
guideUrl,
|
||||
tests,
|
||||
section,
|
||||
videoUrl
|
||||
} = this.props;
|
||||
return (
|
||||
<div className='instructions-panel' role='complementary'>
|
||||
<div ref={this.bindTopDiv} />
|
||||
<Spacer />
|
||||
<div>
|
||||
<ChallengeTitle>{title}</ChallengeTitle>
|
||||
<ChallengeDescription section={section} description={description} />
|
||||
<ChallengeDescription description={description} section={section} />
|
||||
</div>
|
||||
<ToolPanel guideUrl={guideUrl} />
|
||||
<ToolPanel guideUrl={guideUrl} videoUrl={videoUrl} />
|
||||
<TestSuite tests={tests} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -14,6 +14,7 @@ const mapDispatchToProps = dispatch =>
|
||||
{
|
||||
executeChallenge,
|
||||
openHelpModal: () => openModal('help'),
|
||||
openVideoModal: () => openModal('video'),
|
||||
openResetModal: () => openModal('reset')
|
||||
},
|
||||
dispatch
|
||||
@@ -23,14 +24,18 @@ const propTypes = {
|
||||
executeChallenge: PropTypes.func.isRequired,
|
||||
guideUrl: PropTypes.string,
|
||||
openHelpModal: PropTypes.func.isRequired,
|
||||
openResetModal: PropTypes.func.isRequired
|
||||
openResetModal: PropTypes.func.isRequired,
|
||||
openVideoModal: PropTypes.func.isRequired,
|
||||
videoUrl: PropTypes.string
|
||||
};
|
||||
|
||||
function ToolPanel({
|
||||
executeChallenge,
|
||||
openHelpModal,
|
||||
openVideoModal,
|
||||
openResetModal,
|
||||
guideUrl
|
||||
guideUrl,
|
||||
videoUrl
|
||||
}) {
|
||||
return (
|
||||
<Fragment>
|
||||
@@ -57,6 +62,16 @@ function ToolPanel({
|
||||
Get a hint
|
||||
</Button>
|
||||
) : null}
|
||||
{videoUrl ? (
|
||||
<Button
|
||||
block={true}
|
||||
bsStyle='primary'
|
||||
className='btn-primary-invert'
|
||||
onClick={openVideoModal}
|
||||
>
|
||||
Watch a video
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
block={true}
|
||||
bsStyle='primary'
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { Modal } from 'react-bootstrap';
|
||||
|
||||
import ga from '../../../analytics';
|
||||
import { closeModal, isVideoModalOpenSelector } from '../redux';
|
||||
|
||||
import './video-modal.css';
|
||||
|
||||
const mapStateToProps = state => ({ isOpen: isVideoModalOpenSelector(state) });
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{ closeVideoModal: () => closeModal('video') },
|
||||
dispatch
|
||||
);
|
||||
|
||||
const propTypes = {
|
||||
closeVideoModal: PropTypes.func.isRequired,
|
||||
isOpen: PropTypes.bool,
|
||||
videoUrl: PropTypes.string
|
||||
};
|
||||
|
||||
export class VideoModal extends PureComponent {
|
||||
render() {
|
||||
const { isOpen, closeVideoModal, videoUrl } = this.props;
|
||||
if (isOpen) {
|
||||
ga.modalview('/video-modal');
|
||||
}
|
||||
return (
|
||||
<Modal onHide={closeVideoModal} show={isOpen} dialogClassName="video-modal">
|
||||
<Modal.Header
|
||||
className='help-modal-header fcc-modal'
|
||||
closeButton={true}
|
||||
>
|
||||
<Modal.Title className='text-center'>Watch A Video</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<iframe frameborder="0" src={videoUrl}></iframe>
|
||||
<p>Tip: If the mini-browser is covering the code, click and drag to move it.
|
||||
Also, feel free to stop and edit the code in the video at any time.
|
||||
</p>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
VideoModal.displayName = 'VideoModal';
|
||||
VideoModal.propTypes = propTypes;
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(VideoModal);
|
||||
@@ -0,0 +1,10 @@
|
||||
.video-modal {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.video-modal iframe {
|
||||
width: 100%;
|
||||
height: 70vh;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ const initialState = {
|
||||
modal: {
|
||||
completion: false,
|
||||
help: false,
|
||||
video: false,
|
||||
reset: false
|
||||
},
|
||||
projectFormVaules: {},
|
||||
@@ -136,6 +137,7 @@ export const isCodeLockedSelector = state => state[ns].isCodeLocked;
|
||||
export const isCompletionModalOpenSelector = state =>
|
||||
state[ns].modal.completion;
|
||||
export const isHelpModalOpenSelector = state => state[ns].modal.help;
|
||||
export const isVideoModalOpenSelector = state => state[ns].modal.video;
|
||||
export const isResetModalOpenSelector = state => state[ns].modal.reset;
|
||||
export const isJSEnabledSelector = state => state[ns].isJSEnabled;
|
||||
export const successMessageSelector = state => state[ns].successMessage;
|
||||
|
||||
Reference in New Issue
Block a user