diff --git a/client/less/challenge.less b/client/less/challenge.less index 5ccb4ca610..7aeca53da8 100644 --- a/client/less/challenge.less +++ b/client/less/challenge.less @@ -51,9 +51,9 @@ } } -#testSuite { +.challenge-test-suite { margin-top: 10px; - > div >.row { + & .row { margin: 0!important; } } diff --git a/client/less/main.less b/client/less/main.less index fdda85a0a5..49ee08fe87 100644 --- a/client/less/main.less +++ b/client/less/main.less @@ -1,4 +1,4 @@ -@import "lib/bootstrap/bootstrap"; + @import "lib/bootstrap/bootstrap"; @import "lib/bootstrap-social/bootstrap-social"; @import "lib/ionicons/ionicons"; @import "lib/animate.min.less"; @@ -682,21 +682,11 @@ form.update-email .btn{ padding-bottom: 117%; } -#directions { - text-align: left; - font-size: 15px; -} - .graph-rect { fill: #ddd !important } - -/** - * Challenge styling - */ - -form.code span { +.CodeMirror span { font-size: 18px; font-family: "Ubuntu Mono"; padding-bottom: 0px; @@ -713,7 +703,7 @@ form.code span { font-family: "Ubuntu Mono"; } -#mainEditorPanel { +.challenges-editor { height: 100%; width: 99%; } @@ -723,10 +713,6 @@ form.code span { overflow-y: auto; } -#mainEditorPanel .panel-body { - padding-bottom: 0px; -} - div.CodeMirror-scroll { padding-bottom: 30px; } @@ -742,6 +728,11 @@ div.CodeMirror-scroll { min-height: 650px; } +.challenge-log .CodeMirror { + height: 100%; + width: 100%; +} + .btn { font-weight: 400; white-space: normal; diff --git a/common/app/App.jsx b/common/app/App.jsx index 0afb196a5b..130ac7f50f 100644 --- a/common/app/App.jsx +++ b/common/app/App.jsx @@ -5,8 +5,13 @@ import { compose } from 'redux'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import { fetchUser } from './redux/actions'; +import { + fetchUser, + updateWindowHeight, + updateNavHeight +} from './redux/actions'; import contain from './utils/professor-x'; +import getWindowHeight from './utils/get-window-height'; import Nav from './components/Nav'; @@ -43,7 +48,9 @@ export class FreeCodeCamp extends React.Component { username: PropTypes.string, points: PropTypes.number, picture: PropTypes.string, - toast: PropTypes.object + toast: PropTypes.object, + updateNavHeight: PropTypes.func, + updateWindowHeight: PropTypes.func }; componentWillReceiveProps({ toast: nextToast = {} }) { @@ -60,9 +67,13 @@ export class FreeCodeCamp extends React.Component { } } + componentDidMount() { + this.props.updateWindowHeight(getWindowHeight()); + } + render() { - const { username, points, picture } = this.props; - const navProps = { username, points, picture }; + const { username, points, picture, updateNavHeight } = this.props; + const navProps = { username, points, picture, updateNavHeight }; return (
@@ -81,7 +92,7 @@ export class FreeCodeCamp extends React.Component { const wrapComponent = compose( // connect Component to Redux Store - connect(mapStateToProps, { fetchUser }), + connect(mapStateToProps, { updateWindowHeight, updateNavHeight, fetchUser }), // handles prefetching data contain(fetchContainerOptions) ); diff --git a/common/app/components/Nav/Nav.jsx b/common/app/components/Nav/Nav.jsx index c748684474..1d1cc0fe3d 100644 --- a/common/app/components/Nav/Nav.jsx +++ b/common/app/components/Nav/Nav.jsx @@ -1,4 +1,5 @@ import React, { PropTypes } from 'react'; +import ReactDOM from 'react-dom'; import { LinkContainer } from 'react-router-bootstrap'; import { Col, @@ -35,9 +36,15 @@ export default class extends React.Component { points: PropTypes.number, picture: PropTypes.string, signedIn: PropTypes.bool, - username: PropTypes.string + username: PropTypes.string, + updateNavHeight: PropTypes.func }; + componentDidMount() { + const navBar = ReactDOM.findDOMNode(this); + this.props.updateNavHeight(navBar.clientHeight); + } + renderLinks() { return navLinks.map(({ content, link, react, target }, index) => { if (react) { diff --git a/common/app/redux/actions.js b/common/app/redux/actions.js index f04893fdcc..a095809461 100644 --- a/common/app/redux/actions.js +++ b/common/app/redux/actions.js @@ -30,3 +30,6 @@ export const updatePoints = createAction(types.updatePoints); // hardGoTo(path: String) => Action export const hardGoTo = createAction(types.hardGoTo); + +export const updateWindowHeight = createAction(types.updateWindowHeight); +export const updateNavHeight = createAction(types.updateNavHeight); diff --git a/common/app/redux/reducer.js b/common/app/redux/reducer.js index ef189c9390..6ef681c631 100644 --- a/common/app/redux/reducer.js +++ b/common/app/redux/reducer.js @@ -19,10 +19,17 @@ export default handleActions( ...state, points }), - [types.updatePoints]: (state, { payload: points }) => ({ ...state, points + }), + [types.updateWindowHeight]: (state, { payload: windowHeight }) => ({ + ...state, + windowHeight + }), + [types.updateNavHeight]: (state, { payload: navHeight }) => ({ + ...state, + navHeight }) }, { @@ -31,6 +38,8 @@ export default handleActions( picture: null, points: 0, isSignedIn: false, - csrfToken: '' + csrfToken: '', + windowHeight: 0, + navHeight: 0 } ); diff --git a/common/app/redux/types.js b/common/app/redux/types.js index ce9282a50b..8c1349c658 100644 --- a/common/app/redux/types.js +++ b/common/app/redux/types.js @@ -10,5 +10,8 @@ export default createTypes([ 'updatePoints', 'handleError', // used to hit the server - 'hardGoTo' + 'hardGoTo', + + 'updateWindowHeight', + 'updateNavHeight' ], 'app'); diff --git a/common/app/routes/Bonfires/README.md b/common/app/routes/Bonfires/README.md deleted file mode 100644 index 047bc43183..0000000000 --- a/common/app/routes/Bonfires/README.md +++ /dev/null @@ -1 +0,0 @@ -This folder contains things relative to the bonfires' screens diff --git a/common/app/routes/Jobs/components/NewJob.jsx b/common/app/routes/Jobs/components/NewJob.jsx index 3645d8c1e9..9f657d19ef 100644 --- a/common/app/routes/Jobs/components/NewJob.jsx +++ b/common/app/routes/Jobs/components/NewJob.jsx @@ -1,5 +1,6 @@ import { helpers } from 'rx'; import React, { PropTypes } from 'react'; +import PureComponent from 'react-pure-render/component'; import { push } from 'react-router-redux'; import { reduxForm } from 'redux-form'; // import debug from 'debug'; @@ -106,7 +107,7 @@ function getBsStyle(field) { 'success'; } -export class NewJob extends React.Component { +export class NewJob extends PureComponent { static displayName = 'NewJob'; static propTypes = { diff --git a/common/app/routes/Jobs/components/NewJobCompleted.jsx b/common/app/routes/Jobs/components/NewJobCompleted.jsx index 8767960e38..7d40199844 100644 --- a/common/app/routes/Jobs/components/NewJobCompleted.jsx +++ b/common/app/routes/Jobs/components/NewJobCompleted.jsx @@ -5,6 +5,10 @@ import { Button, Col, Row } from 'react-bootstrap'; export default class extends React.createClass { static displayName = 'NewJobCompleted'; + shouldComponentUpdate() { + return false; + } + render() { return (
diff --git a/common/app/routes/Jobs/components/ShowJob.jsx b/common/app/routes/Jobs/components/ShowJob.jsx index 8adc2b7dc7..b6bdc2030c 100644 --- a/common/app/routes/Jobs/components/ShowJob.jsx +++ b/common/app/routes/Jobs/components/ShowJob.jsx @@ -1,5 +1,6 @@ import React, { PropTypes } from 'react'; import { Row, Col, Thumbnail } from 'react-bootstrap'; +import PureComponent from 'react-pure-render/component'; import urlRegexFactory from 'url-regex'; const urlRegex = urlRegexFactory(); @@ -18,15 +19,16 @@ function addATags(text) { }); } -export default React.createClass({ - displayName: 'ShowJob', - propTypes: { +export default class extends PureComponent { + static displayName = 'ShowJob'; + + static propTypes = { job: PropTypes.object, params: PropTypes.object, showApply: PropTypes.bool, preview: PropTypes.bool, message: PropTypes.string - }, + }; renderHeader({ company, position }) { return ( @@ -39,39 +41,39 @@ export default React.createClass({
); - }, + } renderHowToApply(showApply, preview, message, howToApply) { if (!showApply) { return ( - -

{ message }

- + +

{ message }

+
); } return ( - -
- -
- { preview ? 'How do I apply?' : message } -
-
- -
- -
+ +
+ +
+ { preview ? 'How do I apply?' : message } +
+
+ +
+ +
); - }, + } render() { const { @@ -142,4 +144,4 @@ export default React.createClass({
); } -}); +} diff --git a/common/app/routes/challenges/components/Challenge.jsx b/common/app/routes/challenges/components/Challenge.jsx new file mode 100644 index 0000000000..87a7ee7fca --- /dev/null +++ b/common/app/routes/challenges/components/Challenge.jsx @@ -0,0 +1,49 @@ +import React, { PropTypes } from 'react'; + +import PureComponent from 'react-pure-render/component'; +import { Col } from 'react-bootstrap'; + +import Editor from './Editor.jsx'; +import SidePanel from './Side-Panel.jsx'; +import Preview from './Preview.jsx'; + +export default class extends PureComponent { + static displayName = 'Challenge'; + + static propTypes = { + showPreview: PropTypes.bool + }; + + renderPreview(showPreview) { + if (!showPreview) { + return null; + } + return ( + + + + ); + } + + render() { + const { showPreview } = this.props; + + return ( +
+ + + + + + + { this.renderPreview(showPreview) } +
+ ); + } +} diff --git a/common/app/routes/challenges/components/Challenges.jsx b/common/app/routes/challenges/components/Challenges.jsx new file mode 100644 index 0000000000..4c9919cd65 --- /dev/null +++ b/common/app/routes/challenges/components/Challenges.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +import PureComponent from 'react-pure-render/component'; + +import Challenge from './Challenge.jsx'; + +export default class extends PureComponent { + static displayName = 'Challenges'; + static propTypes = {}; + + render() { + return ( + + ); + } +} diff --git a/common/app/routes/challenges/components/Editor.jsx b/common/app/routes/challenges/components/Editor.jsx new file mode 100644 index 0000000000..69546ae6e6 --- /dev/null +++ b/common/app/routes/challenges/components/Editor.jsx @@ -0,0 +1,54 @@ +import React, { PropTypes } from 'react'; +import { createSelector } from 'reselect'; +import { connect } from 'react-redux'; + +import Codemirror from 'react-codemirror'; +import NoSSR from 'react-no-ssr'; +import PureComponent from 'react-pure-render/component'; + +const mapStateToProps = createSelector( + state => state.app.windowHeight, + state => state.app.navHeight, + (windowHeight, navHeight) => ({ height: windowHeight - navHeight - 50 }) +); + +const options = { + lint: true, + lineNumbers: true, + mode: 'javascript', + theme: 'monokai', + runnable: true, + matchBrackets: true, + autoCloseBrackets: true, + scrollbarStyle: 'null', + lineWrapping: true, + gutters: ['CodeMirror-lint-markers'] +}; + +export class Editor extends PureComponent { + static displayName = 'Editor'; + static propTypes = { + height: PropTypes.number + }; + + render() { + const { height } = this.props; + const style = {}; + if (height) { + style.height = height + 'px'; + } + return ( +
+ + + +
+ ); + } +} + +export default connect(mapStateToProps)(Editor); diff --git a/common/app/routes/challenges/components/Output.jsx b/common/app/routes/challenges/components/Output.jsx new file mode 100644 index 0000000000..053ee36f36 --- /dev/null +++ b/common/app/routes/challenges/components/Output.jsx @@ -0,0 +1,38 @@ +import React from 'react'; +import PureComponent from 'react-pure-render/component'; +import Codemirror from 'react-codemirror'; + +const defaultOutput = `/** + * Your output will go here. + * Any console.log() -type + * statements will appear in + * your browser\'s DevTools + * JavaScript console. + */`; + +const defaultOptions = { + lineNumbers: false, + mode: 'text', + theme: 'monokai', + readOnly: 'nocursor', + lineWrapping: true +}; + +export default class extends PureComponent { + static displayName = 'Output'; + + static defaultProps = { + output: defaultOutput + }; + + render() { + const { output } = this.props; + return ( +
+ +
+ ); + } +} diff --git a/common/app/routes/challenges/components/Preview.jsx b/common/app/routes/challenges/components/Preview.jsx new file mode 100644 index 0000000000..db567fa854 --- /dev/null +++ b/common/app/routes/challenges/components/Preview.jsx @@ -0,0 +1,22 @@ +import React from 'react'; +import PureComponent from 'react-pure-render/component'; + +export default class extends PureComponent { + static displayName = 'Preview'; + + render() { + return ( +
+
+ +
+