From f4443e16dd5afd01af51c8825794f9e3edc00499 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Mon, 13 Mar 2017 16:17:07 -0700 Subject: [PATCH] feat(nav): make navbar static (#13673) * feat(nav): make navbar static make the navbar in react layout and the static layout stick to the top of the screen * feat(challenges): Make classic view flex Classic view now uses flex to control it's height. This was necessary to control view and allow navbar to be static on other pages. This breaks mobile view and other non-classic challenge views * feat(app): Add logic to make screen expand on tablet * fix(app): let routes define their content structure * fix(less): use American spelling of gray * fix(classic-preview): make preview smaller to prevent scroll * feat(classic-frame): Make frame border less distinct * fix(challenges): scope test suite less to challenges * feat(challenges): make generic ChallengeTitle component --- client/less/challenge.less | 237 ------------------ client/less/flexgrid.less | 117 +++++++++ client/less/lib/bootstrap/variables.less | 11 +- client/less/main.less | 60 ++--- client/less/map.less | 19 +- client/sagas/index.js | 4 +- client/sagas/window-saga.js | 35 --- common/app/App.jsx | 38 ++- common/app/app.less | 14 ++ common/app/components/Nav/Nav.jsx | 9 +- common/app/index.less | 2 + common/app/ns.json | 1 + common/app/redux/actions.js | 5 - common/app/redux/reducer.js | 10 - common/app/redux/types.js | 4 - .../challenges/{components => }/Bug-Modal.jsx | 12 +- .../app/routes/challenges/Challenge-Title.jsx | 30 +++ ...rSkeleton.jsx => Code-Mirror-Skeleton.jsx} | 1 + .../challenges/{components => }/Output.jsx | 9 +- .../challenges/{components => }/Show.jsx | 72 +++--- .../{components => }/Solution-Input.jsx | 2 +- .../{components => }/Test-Suite.jsx | 4 +- common/app/routes/challenges/challenges.less | 81 ++++++ .../challenges/components/classic/Preview.jsx | 28 --- common/app/routes/challenges/index.js | 6 +- common/app/routes/challenges/ns.json | 1 + .../backend/Back-End.jsx | 17 +- .../routes/challenges/views/backend/index.js | 1 + .../classic}/Classic-Modal.jsx | 14 +- .../{components => views}/classic/Classic.jsx | 10 +- .../{components => views}/classic/Editor.jsx | 32 +-- .../challenges/views/classic/Preview.jsx | 20 ++ .../classic/Side-Panel.jsx | 30 +-- .../classic/Tool-Panel.jsx | 0 .../challenges/views/classic/classic.less | 109 ++++++++ .../routes/challenges/views/classic/index.js | 1 + .../routes/challenges/views/classic/ns.json | 1 + common/app/routes/challenges/views/index.less | 2 + .../{components => views}/map/Block.jsx | 0 .../{components => views}/map/Challenge.jsx | 0 .../{components => views}/map/Header.jsx | 5 +- .../{components => views}/map/Map.jsx | 43 ++-- .../{components => views}/map/Super-Block.jsx | 0 .../app/routes/challenges/views/map/index.js | 1 + .../{components => views}/project/Forms.jsx | 2 +- .../{components => views}/project/Project.jsx | 19 +- .../project/Side-Panel.jsx | 23 +- .../project/Tool-Panel.jsx | 0 .../routes/challenges/views/project/index.js | 1 + .../{components => views}/step/Step.jsx | 48 ++-- .../app/routes/challenges/views/step/index.js | 1 + .../app/routes/challenges/views/step/ns.json | 1 + .../routes/challenges/views/step/step.less | 54 ++++ .../{components => views}/video/Lecture.jsx | 15 +- .../{components => views}/video/Questions.jsx | 0 .../{components => views}/video/Video.jsx | 30 +-- .../routes/challenges/views/video/index.js | 1 + common/app/routes/index.less | 1 + common/index.less | 1 + gulpfile.js | 12 +- server/views/layout-react.jade | 2 +- server/views/layout.jade | 2 +- server/views/partials/navbar.jade | 2 +- 63 files changed, 670 insertions(+), 643 deletions(-) create mode 100644 client/less/flexgrid.less delete mode 100644 client/sagas/window-saga.js create mode 100644 common/app/app.less create mode 100644 common/app/index.less create mode 100644 common/app/ns.json rename common/app/routes/challenges/{components => }/Bug-Modal.jsx (87%) create mode 100644 common/app/routes/challenges/Challenge-Title.jsx rename common/app/routes/challenges/{components/CodeMirrorSkeleton.jsx => Code-Mirror-Skeleton.jsx} (99%) rename common/app/routes/challenges/{components => }/Output.jsx (81%) rename common/app/routes/challenges/{components => }/Show.jsx (69%) rename common/app/routes/challenges/{components => }/Solution-Input.jsx (91%) rename common/app/routes/challenges/{components => }/Test-Suite.jsx (95%) create mode 100644 common/app/routes/challenges/challenges.less delete mode 100644 common/app/routes/challenges/components/classic/Preview.jsx create mode 100644 common/app/routes/challenges/ns.json rename common/app/routes/challenges/{components => views}/backend/Back-End.jsx (92%) create mode 100644 common/app/routes/challenges/views/backend/index.js rename common/app/routes/challenges/{components => views/classic}/Classic-Modal.jsx (90%) rename common/app/routes/challenges/{components => views}/classic/Classic.jsx (95%) rename common/app/routes/challenges/{components => views}/classic/Editor.jsx (83%) create mode 100644 common/app/routes/challenges/views/classic/Preview.jsx rename common/app/routes/challenges/{components => views}/classic/Side-Panel.jsx (85%) rename common/app/routes/challenges/{components => views}/classic/Tool-Panel.jsx (100%) create mode 100644 common/app/routes/challenges/views/classic/classic.less create mode 100644 common/app/routes/challenges/views/classic/index.js create mode 100644 common/app/routes/challenges/views/classic/ns.json create mode 100644 common/app/routes/challenges/views/index.less rename common/app/routes/challenges/{components => views}/map/Block.jsx (100%) rename common/app/routes/challenges/{components => views}/map/Challenge.jsx (100%) rename common/app/routes/challenges/{components => views}/map/Header.jsx (96%) rename common/app/routes/challenges/{components => views}/map/Map.jsx (64%) rename common/app/routes/challenges/{components => views}/map/Super-Block.jsx (100%) create mode 100644 common/app/routes/challenges/views/map/index.js rename common/app/routes/challenges/{components => views}/project/Forms.jsx (98%) rename common/app/routes/challenges/{components => views}/project/Project.jsx (81%) rename common/app/routes/challenges/{components => views}/project/Side-Panel.jsx (64%) rename common/app/routes/challenges/{components => views}/project/Tool-Panel.jsx (100%) create mode 100644 common/app/routes/challenges/views/project/index.js rename common/app/routes/challenges/{components => views}/step/Step.jsx (89%) create mode 100644 common/app/routes/challenges/views/step/index.js create mode 100644 common/app/routes/challenges/views/step/ns.json create mode 100644 common/app/routes/challenges/views/step/step.less rename common/app/routes/challenges/{components => views}/video/Lecture.jsx (88%) rename common/app/routes/challenges/{components => views}/video/Questions.jsx (100%) rename common/app/routes/challenges/{components => views}/video/Video.jsx (77%) create mode 100644 common/app/routes/challenges/views/video/index.js create mode 100644 common/app/routes/index.less create mode 100644 common/index.less diff --git a/client/less/challenge.less b/client/less/challenge.less index e5c79dc0c9..e69de29bb2 100644 --- a/client/less/challenge.less +++ b/client/less/challenge.less @@ -1,237 +0,0 @@ -.challenges-instructions-panel { - clear: both; - overflow-x: hidden; - overflow-y: auto; - padding-left: 5px; - padding-right: 5px; - margin-bottom: 10px; -} - -.challenge-step-description { - font-size: 1.5em; -} -.challenge-step-counter { - font-size: 20px; - line-height: 44px; -} - -.challenge-step-forward-leave { - transition: opacity .4s ease-in, transform .3s ease-in-out; - opacity: 1; - transform: translate(0, 0); -} - -.challenge-step-forward-leave-active { - opacity: 0; - transform: translate(-100%, 0); -} - -.challenge-step-forward-enter { - transition: opacity .4s ease-in, transform .3s ease-in-out; - opacity: 0; - transform: translate(100%, 0); -} - -.challenge-step-forward-enter-active { - opacity: 1; - transform: translate(0, 0); -} - -.challenge-step-backward-leave { - transition: opacity .4s ease-in, transform .3s ease-in-out; - opacity: 1; - transform: translate(0, 0); -} - -.challenge-step-backward-leave-active { - opacity: 0; - transform: translate(100%, 0); -} - -.challenge-step-backward-enter { - transition: opacity .4s ease-in, transform .3s ease-in-out; - opacity: 0; - transform: translate(-100%, 0); -} - -.challenge-step-backward-enter-active { - opacity: 1; - transform: translate(0, 0); -} - -.challenge-instructions-title { - margin-top: 0; - i { - margin-left: 5px; - line-height: 20px; - } -} - -.challenge-instructions { - margin-bottom: 5px; - h4 { - margin-bottom: 0; - } - blockquote { - font-size: 90%; - font-family: @font-family-monospace; - color: @code-color; - background-color: #fffbe5; - border-radius: @border-radius-base; - border: 1px solid @pre-border-color; - white-space: pre; - padding: 5px 10px; - margin-bottom: 10px; - margin-top: -5px; - overflow: auto; - } - dfn { - font-family: @font-family-monospace; - color: @code-color; - background-color: @code-bg; - border-radius: @border-radius-base; - padding: 1px 5px; - } - & a, #MDN-links a { - color: #31708f; - } - & a::after, #MDN-links a::after { - font-size: 70%; - font-family: FontAwesome; - content: " \f08e"; - } - ol { - font-size: 16px; - } -} - -.challenge-test-suite { - margin-top: 10px; - & .row { - margin: 0!important; - } -} - -.test-output { - font-size: 15px; - font-family: "Ubuntu Mono"; - margin-top: 8px; - line-height:20px; - word-wrap: break-word; -} - -.grayed-out-test-output { - color: @gray-light; -} - -.big-icon { - font-size: 30px; -} - -.error-icon { - color: @brand-danger; - top: 50%; -} - -.success-icon { - color: @brand-primary; -} - -.refresh-icon { - color: @icon-grey; -} - -.night { - .challenge-instructions blockquote { - background-color: #242424; - border-color: #515151; - color: #ABABAB - } - .challenge-instructions dfn { - background-color: #242424; - color: #02a902; - } - .CodeMirror { - background-color:#242424; - color:#ABABAB; - &-gutters { - background-color:#242424; - color:#ABABAB; - } - .cm-bracket, .cm-tag { - color:#5CAFD6; - } - .cm-property, .cm-string { - color:#B5753A; - } - .cm-keyword, .cm-attribute { - color:#9BBBDC; - } - } - .refresh-icon { - color: @icon-light-grey; - } -} - -.challenges-editor { - height: auto; - width: 100%; - overflow-y: auto; -} - -.challenges-preview { - clear: both; - overflow: hidden; -} - -@media only screen and (min-width: 1031px) { - .iframe-scroll { - z-index: 1; - } -} -@media only screen and (max-width: 1030px) { - .iframe-scroll { - height: auto; - overflow: auto; - } - .iphone-position { - display: none; - } -} - -iframe.iphone { - border: none; - @media(min-width: 1031px) { - width: 280px; - height: 497px; - position: absolute; - top: 75px; - right: 35px; - overflow-y: scroll; - } - @media(max-width: 1030px) { - width: 100%; - border-radius: 5px; - overflow-y: visible; - height: 500px; - } - @media (min-width: 1200px) and (max-width: 1250px){ - right: 22px; - } -} - -// To adjust right margin, negative values bring the image closer to the edge of the screen -.iphone-position { - position: absolute; - top: -45px; - z-index: -1; - right: -195px; - @media (min-width: 1200px) and (max-width: 1250px){ - right: -207px; - } -} - -// YouTube embed -.embed-responsive-item { - max-width: 100%; -} diff --git a/client/less/flexgrid.less b/client/less/flexgrid.less new file mode 100644 index 0000000000..de154c4482 --- /dev/null +++ b/client/less/flexgrid.less @@ -0,0 +1,117 @@ +.justify-mixin(start) { justify-content: flex-start } +.justify-mixin(end) { justify-content: flex-end } +.justify-mixin(center) { justify-content: center } +.justify-mixin(around) { justify-content: space-around } +.justify-mixin(between) { justify-content: between } +.justify-mixin(@_) {} + +.items-mixin(top) { align-items: flex-start; } +.items-mixin(bottom) { align-items: flex-end; } +.items-mixin(center) { align-items: center; } +.items-mixin(stretch) { align-items: stretch; } +.items-mixin(baseline) { align-items: baseline; } +.items-mixin(@_) {} + +.item-mixin(top) { align-self: flex-start; } +.item-mixin(bottom) { align-self: flex-end; } +.item-mixin(center) { align-self: center; } +.item-mixin(stretch) { align-self: stretch; } +.item-mixin(baseline) { align-self: baseline; } +.item-mixin(@_) {} + +.content-mixin(top) { align-content: flex-start; } +.content-mixin(bottom) { align-content: flex-end; } +.content-mixin(center) { align-content: center; } +.content-mixin(stretch) { align-content: stretch; } +.content-mixin(baseline) { align-content: baseline; } +.content-mixin(@_) {} + +.grid(@direction: row; @items: none; @justify: none; @content: none) { + display: flex; + flex-wrap: wrap; + flex-direction: @direction; + + .justify-mixin(@justify); + .items-mixin(@item); + .content-mixin(@content); +} + +.row(@items: none; @justify: none; @content: none) { + .grid(@direction: row, @items, @justify, @content); +} + +.column(@items: none; @justify: none; @content: none) { + .grid(@direction: column; @items; @justify; @content); +} + +.margin-mixin(@g) when not (@g = 0) { + margin: @g / 2; +} + +.cell(@i: 1; @item; @g: @grid-gutter-width; @cols: @grid-columns) { + flex-basis: %('calc(100% * %s - %s)', @i / @cols, @g); + min-width: 0; // FF adjustment for responsive images + .item-mixin(@item); + .margin-mixin(@g); +} + +.cell-offset(@i: 1; @g: @grid-gutter-width; @cols: @grid-columns) { + margin-left: e%('calc(100% * %s + %s)', @i / @cols, @g / 2) !important; +} + +.padding-mixin(@pad) when (@pad) { + padding-left: @pad; + padding-right: @pad; +} + +// center an element +.center(@value: 50%; @padding: 0) { + margin-left: auto; + margin-right: auto; + max-width: @value; + width: 100%; + + .padding-mixin(@padding); +} + +.between(@min; @max; @rules) { + // BS logic to do string building with conditions + .condition-wrapper(@new) { + .redefine-condition() { + @condition: @new; + } + } + .init-condition() { + .condition-wrapper('only screen'); + } + .init-condition(); + .add-min-media(@min) when (iskeyword(@min)) { + .redefine-condition(); + .condition-wrapper(%('%s and (min-width: @{screen-%s})', @condition, @min)); + } + + .add-max-media(@max) when (iskeyword(@max)) { + .redefine-condition(); + .condition-wrapper(%('%s and (max-width: @{screen-%s})', @condition, @max)); + } + .add-min-media(@min); + .add-max-media(@max); + + .add-query() { + .redefine-condition(); + @query: e(@condition); + @media @query { + @rules(); + } + } + + .add-query(); +} + +.below(@max, @rules) { + .between(@empty; @max; @rules); +} + +.above(@min, @rules) { + .between(@min; @empty; @rules); +} diff --git a/client/less/lib/bootstrap/variables.less b/client/less/lib/bootstrap/variables.less index 5539c048e6..4498534de0 100755 --- a/client/less/lib/bootstrap/variables.less +++ b/client/less/lib/bootstrap/variables.less @@ -1,6 +1,7 @@ // // Variables // -------------------------------------------------- +@empty: ~''; //== Colors @@ -20,8 +21,8 @@ @brand-warning: #f0ad4e; @brand-danger: #d9534f; -@icon-grey: #575757; -@icon-light-grey: #888888; +@icon-gray: #575757; +@icon-light-gray: #888888; //== Scaffolding // //## Settings for some of the most global styles. @@ -305,6 +306,10 @@ //** Deprecated `@screen-lg-desktop` as of v3.0.1 @screen-lg-desktop: @screen-lg-min; +// Large screen / wide desktop +@screen-xl: 1400px; +@screen-lg-min: @screen-lg; + // So media queries don't overlap when required, provide a maximum @screen-xs-max: (@screen-sm-min - 1); @screen-sm-max: (@screen-md-min - 1); @@ -345,6 +350,8 @@ //** For `@screen-lg-min` and up. @container-lg: @container-large-desktop; +@container-xl: (1340px + @grid-gutter-width); + //== Navbar // diff --git a/client/less/main.less b/client/less/main.less index d70dc76365..683ccae15e 100644 --- a/client/less/main.less +++ b/client/less/main.less @@ -1,8 +1,9 @@ -@import "lib/bootstrap/bootstrap"; -@import "lib/bootstrap-social/bootstrap-social"; -@import "lib/ionicons/ionicons"; -@import "lib/animate"; -@import "lib/bootstrap/variables"; +@import "./lib/bootstrap/bootstrap"; +@import "./lib/bootstrap-social/bootstrap-social"; +@import "./lib/ionicons/ionicons"; +@import "./lib/animate"; +@import "./lib/bootstrap/variables"; +@import "./flexgrid.less"; html,body,div,span,a,li,td,th { font-family: 'Lato', sans-serif; @@ -34,13 +35,6 @@ pre.wrappable { word-wrap: break-word; /* IE 5+ */ } -html { - position: relative; - min-height: 100%; - // hack to prevent horizontal overflow problem on showHTML view - overflow-x: hidden; -} - //input[type=checkbox] { // /* Double-sized Checkboxes */ // -ms-transform: scale(2); /* IE */ @@ -54,28 +48,14 @@ html { border-color: @brand-primary; } -body.full-screen-body-background { +.full-screen-body-background { background-color: @body-bg; } -body.top-and-bottom-margins { - padding-top: 80px; - margin-bottom: 60px; -} - -body.no-top-and-bottom-margins { +.no-top-and-bottom-margins { margin: 75px 20px 0px 20px; } -body.react-layout { - margin-top: 75px; - margin-bottom: 15px; - width: auto; - padding-left: 15px; - padding-right: 15px; - min-height: 650px; -} - h1, h2 { font-weight: 400; } @@ -165,9 +145,12 @@ h1, h2, h3, h4, h5, h6, p, li { } } +// defined in bootstrap +@navbar-total-height: @navbar-height + @navbar-margin-bottom; .nav-height { - height: 50px; border: none; + height: @navbar-height; + width: 100%; } .landing-icon { @@ -1241,11 +1224,14 @@ and (max-width : 400px) { } } - -@import "code-mirror.less"; -@import "challenge.less"; -@import "toastr.less"; -@import "map.less"; -@import "sk-wave.less"; -@import "classic-modal.less"; -@import "skeleton-shimmer.less"; +// surrounding downstream import with &{} +// creates locally scoped imports +// and prevents vaiables from overwriting each other +&{ @import "./code-mirror.less"; } +&{ @import "./challenge.less"; } +&{ @import "./toastr.less"; } +&{ @import "./map.less"; } +&{ @import "./sk-wave.less"; } +&{ @import "./classic-modal.less"; } +&{ @import "./skeleton-shimmer.less"; } +&{ @import "../../common/index.less"; } diff --git a/client/less/map.less b/client/less/map.less index a4b07d3b68..58bf701ae0 100644 --- a/client/less/map.less +++ b/client/less/map.less @@ -54,27 +54,10 @@ width: 100%; } -.map-fixed-header { - position: fixed; - background: white; - width: 100%; - padding-top: 5px; - z-index: 1; - left: 0; - top: 0; -} - -.drawer .map-fixed-header { - padding-top: 30px; - position: static; - margin-bottom: -100px; -} - .map-accordion { - width: 100%; - margin-top: 100px; max-width: 700px; overflow-y: auto; + width: 100%; .map-accordion-panel-title { padding-bottom: 0px; diff --git a/client/sagas/index.js b/client/sagas/index.js index 8fe5789425..004e8f75f0 100644 --- a/client/sagas/index.js +++ b/client/sagas/index.js @@ -7,7 +7,6 @@ import hardGoToSaga from './hard-go-to-saga.js'; import mouseTrapSaga from './mouse-trap-saga.js'; import nightModeSaga from './night-mode-saga.js'; import titleSaga from './title-saga.js'; -import windowSaga from './window-saga.js'; export default [ analyticsSaga, @@ -18,6 +17,5 @@ export default [ hardGoToSaga, mouseTrapSaga, nightModeSaga, - titleSaga, - windowSaga + titleSaga ]; diff --git a/client/sagas/window-saga.js b/client/sagas/window-saga.js deleted file mode 100644 index 0e3302186d..0000000000 --- a/client/sagas/window-saga.js +++ /dev/null @@ -1,35 +0,0 @@ -import { Observable } from 'rx'; -import types from '../../common/app/redux/types'; -import { updateWindowHeight } from '../../common/app/redux/actions'; - -const { initWindowHeight } = types; -function getWindowSize(document, window) { - const body = document.getElementsByTagName('body')[0]; - return window.innerHeight || - document.docElement.clientHeight || - body.clientHeight || - 0; -} - -function listenForResize(document, window) { - return Observable.fromEvent(window, 'resize') - .debounce(250) - .startWith({}) - .map(() => getWindowSize(document, window)); -} - -export default function windowSaga( - action$, - getState, - { isDev, document, window } -) { - return action$ - .filter(({ type }) => type === initWindowHeight) - .flatMap(() => { - if (isDev) { - return listenForResize(document, window); - } - return Observable.just(getWindowSize(document, window)); - }) - .map(updateWindowHeight); -} diff --git a/common/app/App.jsx b/common/app/App.jsx index 72e4f7790c..bb488ad925 100644 --- a/common/app/App.jsx +++ b/common/app/App.jsx @@ -1,12 +1,11 @@ import React, { PropTypes } from 'react'; -import { Button, Row } from 'react-bootstrap'; +import { Button } from 'react-bootstrap'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; +import ns from './ns.json'; import { fetchUser, - initWindowHeight, - updateNavHeight, updateAppLang, trackEvent, loadCurrentChallenge, @@ -21,15 +20,13 @@ import Toasts from './toasts/Toasts.jsx'; import { userSelector } from './redux/selectors'; const mapDispatchToProps = { - initWindowHeight, - updateNavHeight, + closeDropdown, fetchUser, - submitChallenge, - updateAppLang, - trackEvent, loadCurrentChallenge, openDropdown, - closeDropdown + submitChallenge, + trackEvent, + updateAppLang }; const mapStateToProps = createSelector( @@ -58,7 +55,6 @@ const propTypes = { children: PropTypes.node, closeDropdown: PropTypes.func.isRequired, fetchUser: PropTypes.func, - initWindowHeight: PropTypes.func, isNavDropdownOpen: PropTypes.bool, isSignedIn: PropTypes.bool, loadCurrentChallenge: PropTypes.func.isRequired, @@ -71,7 +67,6 @@ const propTypes = { toast: PropTypes.object, trackEvent: PropTypes.func.isRequired, updateAppLang: PropTypes.func.isRequired, - updateNavHeight: PropTypes.func, username: PropTypes.string }; @@ -84,7 +79,6 @@ export class FreeCodeCamp extends React.Component { } componentDidMount() { - this.props.initWindowHeight(); if (!this.props.isSignedIn) { this.props.fetchUser(); } @@ -110,7 +104,6 @@ export class FreeCodeCamp extends React.Component { username, points, picture, - updateNavHeight, trackEvent, loadCurrentChallenge, openDropdown, @@ -118,23 +111,22 @@ export class FreeCodeCamp extends React.Component { isNavDropdownOpen } = this.props; const navProps = { - username, - points, - picture, - updateNavHeight, - trackEvent, + closeDropdown, + isNavDropdownOpen, loadCurrentChallenge, openDropdown, - closeDropdown, - isNavDropdownOpen + picture, + points, + trackEvent, + username }; return ( -
+
); diff --git a/common/app/app.less b/common/app/app.less new file mode 100644 index 0000000000..3705a8db5f --- /dev/null +++ b/common/app/app.less @@ -0,0 +1,14 @@ +// should match ./ns.json value and filename +@ns: app; + +.@{ns}-container { + .column(); + width: 100vw; +} + +.@{ns}-content { + .center(@value: @container-xl, @padding: @grid-gutter-width); + // makes the inital content height 0px + // then lets it grow to fit the rest of the space + flex: 1 0 0px; +} diff --git a/common/app/components/Nav/Nav.jsx b/common/app/components/Nav/Nav.jsx index 4c6fa7c9af..033c4af227 100644 --- a/common/app/components/Nav/Nav.jsx +++ b/common/app/components/Nav/Nav.jsx @@ -1,5 +1,4 @@ import React, { PropTypes } from 'react'; -import ReactDOM from 'react-dom'; import { LinkContainer } from 'react-router-bootstrap'; import { Col, @@ -41,7 +40,6 @@ const propTypes = { showLoading: PropTypes.bool, signedIn: PropTypes.bool, trackEvent: PropTypes.func.isRequired, - updateNavHeight: PropTypes.func, username: PropTypes.string }; @@ -55,11 +53,6 @@ export default class FCCNav extends React.Component { }); } - componentDidMount() { - const navBar = ReactDOM.findDOMNode(this); - this.props.updateNavHeight(navBar.clientHeight); - } - handleMapClickOnMap(e) { e.preventDefault(); this.props.trackEvent({ @@ -172,7 +165,7 @@ export default class FCCNav extends React.Component { return ( diff --git a/common/app/index.less b/common/app/index.less new file mode 100644 index 0000000000..69bf5eb725 --- /dev/null +++ b/common/app/index.less @@ -0,0 +1,2 @@ +&{ @import "./app.less"; } +&{ @import "./routes/index.less"; } diff --git a/common/app/ns.json b/common/app/ns.json new file mode 100644 index 0000000000..74999efc4e --- /dev/null +++ b/common/app/ns.json @@ -0,0 +1 @@ +"app" diff --git a/common/app/redux/actions.js b/common/app/redux/actions.js index a4602bc899..6768f58627 100644 --- a/common/app/redux/actions.js +++ b/common/app/redux/actions.js @@ -113,11 +113,6 @@ export const delayedRedirect = createAction(types.delayedRedirect); // hardGoTo(path: String) => Action export const hardGoTo = createAction(types.hardGoTo); -export const initWindowHeight = createAction(types.initWindowHeight); -export const updateWindowHeight = createAction(types.updateWindowHeight); -export const updateNavHeight = createAction(types.updateNavHeight); - - // data export const updateChallengesData = createAction(types.updateChallengesData); export const updateHikesData = createAction(types.updateHikesData); diff --git a/common/app/redux/reducer.js b/common/app/redux/reducer.js index b0e14cb50a..754345ac76 100644 --- a/common/app/redux/reducer.js +++ b/common/app/redux/reducer.js @@ -7,8 +7,6 @@ const initialState = { user: '', lang: '', csrfToken: '', - windowHeight: 0, - navHeight: 0, theme: 'default' }; @@ -41,14 +39,6 @@ export default handleActions( ...state, points }), - [types.updateWindowHeight]: (state, { payload: windowHeight }) => ({ - ...state, - windowHeight - }), - [types.updateNavHeight]: (state, { payload: navHeight }) => ({ - ...state, - navHeight - }), [types.delayedRedirect]: (state, { payload }) => ({ ...state, delayedRedirect: payload diff --git a/common/app/redux/types.js b/common/app/redux/types.js index 1eb0a70635..766b695c85 100644 --- a/common/app/redux/types.js +++ b/common/app/redux/types.js @@ -22,10 +22,6 @@ export default createTypes([ 'hardGoTo', 'delayedRedirect', - 'initWindowHeight', - 'updateWindowHeight', - 'updateNavHeight', - // data handling 'updateChallengesData', 'updateHikesData', diff --git a/common/app/routes/challenges/components/Bug-Modal.jsx b/common/app/routes/challenges/Bug-Modal.jsx similarity index 87% rename from common/app/routes/challenges/components/Bug-Modal.jsx rename to common/app/routes/challenges/Bug-Modal.jsx index f60f2b5019..b22f7d0809 100644 --- a/common/app/routes/challenges/components/Bug-Modal.jsx +++ b/common/app/routes/challenges/Bug-Modal.jsx @@ -2,10 +2,13 @@ import React, { PropTypes } from 'react'; import { connect } from 'react-redux'; import { Button, Modal } from 'react-bootstrap'; import PureComponent from 'react-pure-render/component'; -import { createIssue, openIssueSearch, closeBugModal } from '../redux/actions'; + +import ns from './ns.json'; + +import { createIssue, openIssueSearch, closeBugModal } from './redux/actions'; const mapStateToProps = state => ({ isOpen: state.challengesApp.isBugOpen }); -const actions = { createIssue, openIssueSearch, closeBugModal }; +const mapDispatchToProps = { createIssue, openIssueSearch, closeBugModal }; const bugLink = 'http://forum.freecodecamp.com/t/how-to-report-a-bug/19543'; const propTypes = { @@ -16,7 +19,6 @@ const propTypes = { }; export class BugModal extends PureComponent { - render() { const { isOpen, @@ -28,7 +30,7 @@ export class BugModal extends PureComponent { - + Did you find a bug? + ); + } + return ( +

+ { children || 'Happy Coding!' } + { icon } +
+

+ ); +} + +ChallengeTitle.displayName = 'ChallengeTitle'; +ChallengeTitle.propTypes = propTypes; diff --git a/common/app/routes/challenges/components/CodeMirrorSkeleton.jsx b/common/app/routes/challenges/Code-Mirror-Skeleton.jsx similarity index 99% rename from common/app/routes/challenges/components/CodeMirrorSkeleton.jsx rename to common/app/routes/challenges/Code-Mirror-Skeleton.jsx index a79d220b36..a9fc788d79 100644 --- a/common/app/routes/challenges/components/CodeMirrorSkeleton.jsx +++ b/common/app/routes/challenges/Code-Mirror-Skeleton.jsx @@ -5,6 +5,7 @@ import { Grid, Col, Row } from 'react-bootstrap'; const propTypes = { content: PropTypes.string }; + export default class CodeMirrorSkeleton extends PureComponent { renderLine(line, i) { diff --git a/common/app/routes/challenges/components/Output.jsx b/common/app/routes/challenges/Output.jsx similarity index 81% rename from common/app/routes/challenges/components/Output.jsx rename to common/app/routes/challenges/Output.jsx index 476c4324d4..eb27d3c860 100644 --- a/common/app/routes/challenges/components/Output.jsx +++ b/common/app/routes/challenges/Output.jsx @@ -2,14 +2,15 @@ import React, { PureComponent, PropTypes } from 'react'; import NoSSR from 'react-no-ssr'; import Codemirror from 'react-codemirror'; -import CodeMirrorSkeleton from './CodeMirrorSkeleton.jsx'; +import ns from './ns.json'; +import CodeMirrorSkeleton from './Code-Mirror-Skeleton.jsx'; const defaultOptions = { lineNumbers: false, + lineWrapping: true, mode: 'javascript', - theme: 'monokai', readOnly: 'nocursor', - lineWrapping: true + theme: 'monokai' }; const propTypes = { @@ -21,7 +22,7 @@ export default class Output extends PureComponent { render() { const { output, defaultOutput } = this.props; return ( -
+
}> ; } - - render() { - const { viewType } = this.props; - return ( -
- { this.renderView(viewType) } -
- ); - } } -Challenges.displayName = 'Challenges'; -Challenges.propTypes = propTypes; +Show.displayName = 'Show(ChallengeView)'; +Show.propTypes = propTypes; export default compose( - connect(mapStateToProps, bindableActions), + connect(mapStateToProps, mapDispatchToProps), contain(fetchOptions) -)(Challenges); +)(Show); diff --git a/common/app/routes/challenges/components/Solution-Input.jsx b/common/app/routes/challenges/Solution-Input.jsx similarity index 91% rename from common/app/routes/challenges/components/Solution-Input.jsx rename to common/app/routes/challenges/Solution-Input.jsx index afbf2cdc23..bb242c254f 100644 --- a/common/app/routes/challenges/components/Solution-Input.jsx +++ b/common/app/routes/challenges/Solution-Input.jsx @@ -1,6 +1,6 @@ import React, { PropTypes } from 'react'; import { HelpBlock, FormGroup, FormControl } from 'react-bootstrap'; -import { getValidationState, DOMOnlyProps } from '../../../utils/form'; +import { getValidationState, DOMOnlyProps } from '../../utils/form'; const propTypes = { placeholder: PropTypes.string, diff --git a/common/app/routes/challenges/components/Test-Suite.jsx b/common/app/routes/challenges/Test-Suite.jsx similarity index 95% rename from common/app/routes/challenges/components/Test-Suite.jsx rename to common/app/routes/challenges/Test-Suite.jsx index 4d8d7e3269..d11837d911 100644 --- a/common/app/routes/challenges/components/Test-Suite.jsx +++ b/common/app/routes/challenges/Test-Suite.jsx @@ -2,6 +2,8 @@ import React, { PropTypes, PureComponent } from 'react'; import classnames from 'classnames'; import { Col, Row } from 'react-bootstrap'; +import ns from './ns.json'; + const propTypes = { tests: PropTypes.arrayOf(PropTypes.object) }; @@ -41,7 +43,7 @@ export default class TestSuite extends PureComponent { const { tests } = this.props; return (
{ this.renderTests(tests) } diff --git a/common/app/routes/challenges/challenges.less b/common/app/routes/challenges/challenges.less new file mode 100644 index 0000000000..efda7469a8 --- /dev/null +++ b/common/app/routes/challenges/challenges.less @@ -0,0 +1,81 @@ +// should be the same as the filename and ./ns.json +@ns: challenges; + + +.@{ns}-title { + margin-top: 0; + i { + margin-left: 5px; + line-height: 20px; + } +} + +.@{ns}-grayed-out-test-output { + color: @gray-light; +} + +.@{ns}-test-suite { + margin-top: 10px; + & .row { + margin: 0!important; + } + + .big-icon { + font-size: 30px; + } + + .error-icon { + color: @brand-danger; + top: 50%; + } + + .success-icon { + color: @brand-primary; + } + + .refresh-icon { + color: @icon-gray; + } +} + +.night { + .@{ns}-instructions blockquote { + background-color: #242424; + border-color: #515151; + color: #ABABAB + } + .@{ns}-instructions dfn { + background-color: #242424; + color: #02a902; + } + .@{ns}-editor .CodeMirror { + background-color:#242424; + color:#ABABAB; + &-gutters { + background-color:#242424; + color:#ABABAB; + } + .cm-bracket, .cm-tag { + color:#5CAFD6; + } + .cm-property, .cm-string { + color:#B5753A; + } + .cm-keyword, .cm-attribute { + color:#9BBBDC; + } + } + .refresh-icon { + color: @icon-light-gray; + } +} + +.@{ns}-test-output { + font-size: 15px; + font-family: "Ubuntu Mono"; + margin-top: 8px; + line-height:20px; + word-wrap: break-word; +} + +&{ @import "./views/index.less"; } diff --git a/common/app/routes/challenges/components/classic/Preview.jsx b/common/app/routes/challenges/components/classic/Preview.jsx deleted file mode 100644 index ce0fdb1e13..0000000000 --- a/common/app/routes/challenges/components/classic/Preview.jsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import PureComponent from 'react-pure-render/component'; - -const mainId = 'fcc-main-frame'; - -export default class Preview extends PureComponent { - render() { - return ( -
-
- -
-