feat: react challenges (#16099)
* chore(packages): Update redux utils * feat(Panes): Invert control of panes map creation * feat(Modern): Add view * feat(Panes): Decouple panes from Challenges * fix(Challenges): Decouple challenge views from panes map * fix(Challenge/views): PanesMap => mapStateToPanesMap This clarifies what these functions are doing * fix(Challenges): Add view type * fix(Panes): Remove unneeded panes container * feat(Panes): Invert control of pane content render This decouples the Panes from the content they render, allowing for greater flexibility. * feat(Modern): Add side panel This is common between modern and classic * feat(seed): Array to string file content * fix(files): Modern files should be polyvinyls * feat(Modern): Create editors per file * fix(seed/React): Incorrect keyfile name * feat(Modern): Highligh jsx correctly This adds highlighting for jsx. Unfortunately, this disables linting for non-javascript files as jshint will only work for those * feat(rechallenge): Add jsx ext to babel transformer * feat(seed): Normalize challenge files head/tail/content * refactor(rechallenge/build): Rename function * fix(code-storage): Pull in files from localStorage * feat(Modern/React): Add Enzyme to test runner This enables testing of React challenges * feat(Modern): Add submission type * refactor(Panes): Rename panes map update action
This commit is contained in:
committed by
Quincy Larson
parent
8b9be0242a
commit
dced96da8e
@ -3,7 +3,7 @@ import { Scheduler, Observable } from 'rx';
|
|||||||
import { ofType } from 'redux-epic';
|
import { ofType } from 'redux-epic';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
buildClassic,
|
buildFromFiles,
|
||||||
buildBackendChallenge
|
buildBackendChallenge
|
||||||
} from '../utils/build.js';
|
} from '../utils/build.js';
|
||||||
import {
|
import {
|
||||||
@ -41,7 +41,7 @@ export default function executeChallengeEpic(actions, { getState }) {
|
|||||||
.map(frameTests)
|
.map(frameTests)
|
||||||
.startWith(initOutput('// running test'));
|
.startWith(initOutput('// running test'));
|
||||||
}
|
}
|
||||||
return buildClassic(files, required, shouldProxyConsole)
|
return buildFromFiles(files, required, shouldProxyConsole)
|
||||||
.flatMap(payload => {
|
.flatMap(payload => {
|
||||||
const actions = [
|
const actions = [
|
||||||
frameMain(payload)
|
frameMain(payload)
|
||||||
|
@ -3,6 +3,8 @@ import { ofType } from 'redux-epic';
|
|||||||
/* eslint-disable import/no-unresolved */
|
/* eslint-disable import/no-unresolved */
|
||||||
import loopProtect from 'loop-protect';
|
import loopProtect from 'loop-protect';
|
||||||
/* eslint-enable import/no-unresolved */
|
/* eslint-enable import/no-unresolved */
|
||||||
|
import { ShallowWrapper, ReactWrapper } from 'enzyme';
|
||||||
|
import Adapter15 from 'enzyme-adapter-react-15';
|
||||||
import {
|
import {
|
||||||
types,
|
types,
|
||||||
|
|
||||||
@ -80,6 +82,19 @@ function frameTests({ build, sources, checkChallengePayload } = {}, document) {
|
|||||||
const { frame: tests } = getFrameDocument(document, testId);
|
const { frame: tests } = getFrameDocument(document, testId);
|
||||||
refreshFrame(tests);
|
refreshFrame(tests);
|
||||||
tests.Rx = Rx;
|
tests.Rx = Rx;
|
||||||
|
// add enzyme
|
||||||
|
// TODO: do programatically
|
||||||
|
// TODO: webpack lazyload this
|
||||||
|
tests.Enzyme = {
|
||||||
|
shallow: (node, options) => new ShallowWrapper(node, null, {
|
||||||
|
...options,
|
||||||
|
adapter: new Adapter15()
|
||||||
|
}),
|
||||||
|
mount: (node, options) => new ReactWrapper(node, null, {
|
||||||
|
...options,
|
||||||
|
adapter: new Adapter15()
|
||||||
|
})
|
||||||
|
};
|
||||||
// default for classic challenges
|
// default for classic challenges
|
||||||
// should not be used for modern
|
// should not be used for modern
|
||||||
tests.__source = sources['index'] || '';
|
tests.__source = sources['index'] || '';
|
||||||
|
@ -9,6 +9,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
var source = document.__source;
|
var source = document.__source;
|
||||||
var __getUserInput = document.__getUserInput || (x => x);
|
var __getUserInput = document.__getUserInput || (x => x);
|
||||||
var checkChallengePayload = document.__checkChallengePayload;
|
var checkChallengePayload = document.__checkChallengePayload;
|
||||||
|
if (document.Enzyme) {
|
||||||
|
window.Enzyme = document.Enzyme;
|
||||||
|
}
|
||||||
|
|
||||||
document.__getJsOutput = function getJsOutput() {
|
document.__getJsOutput = function getJsOutput() {
|
||||||
if (window.__err || !common.shouldRun()) {
|
if (window.__err || !common.shouldRun()) {
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
import cond from 'lodash/cond';
|
import _ from 'lodash';
|
||||||
import identity from 'lodash/identity';
|
|
||||||
import matchesProperty from 'lodash/matchesProperty';
|
|
||||||
import stubTrue from 'lodash/stubTrue';
|
|
||||||
import conforms from 'lodash/conforms';
|
|
||||||
|
|
||||||
import * as babel from 'babel-core';
|
import * as babel from 'babel-core';
|
||||||
import presetEs2015 from 'babel-preset-es2015';
|
import presetEs2015 from 'babel-preset-es2015';
|
||||||
@ -14,7 +10,8 @@ import loopProtect from 'loop-protect';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
transformHeadTailAndContents,
|
transformHeadTailAndContents,
|
||||||
setContent
|
setContent,
|
||||||
|
setExt
|
||||||
} from '../../common/utils/polyvinyl.js';
|
} from '../../common/utils/polyvinyl.js';
|
||||||
import castToObservable from '../../common/app/utils/cast-to-observable.js';
|
import castToObservable from '../../common/app/utils/cast-to-observable.js';
|
||||||
|
|
||||||
@ -30,12 +27,12 @@ loopProtect.hit = function hit(line) {
|
|||||||
|
|
||||||
// const sourceReg =
|
// const sourceReg =
|
||||||
// /(<!-- fcc-start-source -->)([\s\S]*?)(?=<!-- fcc-end-source -->)/g;
|
// /(<!-- fcc-start-source -->)([\s\S]*?)(?=<!-- fcc-end-source -->)/g;
|
||||||
const HTML$JSReg = /html|js/;
|
|
||||||
const console$logReg = /(?:\b)console(\.log\S+)/g;
|
const console$logReg = /(?:\b)console(\.log\S+)/g;
|
||||||
const NBSPReg = new RegExp(String.fromCharCode(160), 'g');
|
const NBSPReg = new RegExp(String.fromCharCode(160), 'g');
|
||||||
|
|
||||||
const testHTMLJS = conforms({ ext: (ext) => HTML$JSReg.test(ext) });
|
const isJS = _.matchesProperty('ext', 'js');
|
||||||
const testJS = matchesProperty('ext', 'js');
|
const testHTMLJS = _.overSome(isJS, _.matchesProperty('ext', 'html'));
|
||||||
|
const testJS$JSX = _.overSome(isJS, _.matchesProperty('ext', 'jsx'));
|
||||||
|
|
||||||
// if shouldProxyConsole then we change instances of console log
|
// if shouldProxyConsole then we change instances of console log
|
||||||
// to `window.__console.log`
|
// to `window.__console.log`
|
||||||
@ -51,7 +48,7 @@ export function proxyLoggerTransformer(file) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addLoopProtect = cond([
|
export const addLoopProtect = _.cond([
|
||||||
[
|
[
|
||||||
testHTMLJS,
|
testHTMLJS,
|
||||||
function(file) {
|
function(file) {
|
||||||
@ -63,33 +60,33 @@ export const addLoopProtect = cond([
|
|||||||
return setContent(loopProtect(file.contents), file);
|
return setContent(loopProtect(file.contents), file);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[ stubTrue, identity ]
|
[ _.stubTrue, _.identity ]
|
||||||
]);
|
]);
|
||||||
export const replaceNBSP = cond([
|
export const replaceNBSP = _.cond([
|
||||||
[
|
[
|
||||||
testHTMLJS,
|
testHTMLJS,
|
||||||
function(file) {
|
function(file) {
|
||||||
return setContent(
|
return setContent(
|
||||||
file.contents.replace(NBSPReg, ' '),
|
file.contents.replace(NBSPReg, ' '),
|
||||||
file
|
file
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[ stubTrue, identity ]
|
[ _.stubTrue, _.identity ]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const babelTransformer = cond([
|
export const babelTransformer = _.cond([
|
||||||
[
|
[
|
||||||
testJS,
|
testJS$JSX,
|
||||||
function(file) {
|
function(file) {
|
||||||
const result = babel.transform(file.contents, babelOptions);
|
const result = babel.transform(file.contents, babelOptions);
|
||||||
return setContent(
|
return _.flow(
|
||||||
result.code,
|
_.partial(setContent, result.code),
|
||||||
file
|
_.partial(setExt, 'js')
|
||||||
);
|
)(file);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[ stubTrue, identity ]
|
[ _.stubTrue, _.identity ]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const _transformers = [
|
export const _transformers = [
|
||||||
|
@ -35,7 +35,7 @@ const globalRequires = [
|
|||||||
jQuery
|
jQuery
|
||||||
];
|
];
|
||||||
|
|
||||||
export function buildClassic(files, required, shouldProxyConsole) {
|
export function buildFromFiles(files, required, shouldProxyConsole) {
|
||||||
const finalRequires = [...globalRequires, ...required ];
|
const finalRequires = [...globalRequires, ...required ];
|
||||||
return createFileStream(files)
|
return createFileStream(files)
|
||||||
::pipe(throwers)
|
::pipe(throwers)
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
import React, { PureComponent } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import Panes from './Panes.jsx';
|
|
||||||
import { panesMounted } from './redux';
|
|
||||||
|
|
||||||
const mapStateToProps = null;
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
panesMounted
|
|
||||||
};
|
|
||||||
|
|
||||||
const propTypes = {
|
|
||||||
nameToComponent: PropTypes.object.isRequired,
|
|
||||||
panesMounted: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export class PanesContainer extends PureComponent {
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.panesMounted();
|
|
||||||
}
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Panes { ...this.props } />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PanesContainer.displayName = 'PanesContainer';
|
|
||||||
PanesContainer.propTypes = propTypes;
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(PanesContainer);
|
|
@ -6,6 +6,7 @@ import { createSelector } from 'reselect';
|
|||||||
import {
|
import {
|
||||||
panesSelector,
|
panesSelector,
|
||||||
panesByNameSelector,
|
panesByNameSelector,
|
||||||
|
panesMounted,
|
||||||
heightSelector,
|
heightSelector,
|
||||||
widthSelector
|
widthSelector
|
||||||
} from './redux';
|
} from './redux';
|
||||||
@ -38,23 +39,25 @@ const mapStateToProps = createSelector(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const mapDispatchToProps = null;
|
const mapDispatchToProps = { panesMounted };
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
height: PropTypes.number.isRequired,
|
height: PropTypes.number.isRequired,
|
||||||
nameToComponent: PropTypes.object.isRequired,
|
panes: PropTypes.array,
|
||||||
panes: PropTypes.array
|
panesMounted: PropTypes.func.isRequired,
|
||||||
|
render: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Panes extends PureComponent {
|
export class Panes extends PureComponent {
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.panesMounted();
|
||||||
|
}
|
||||||
renderPanes() {
|
renderPanes() {
|
||||||
const {
|
const {
|
||||||
nameToComponent,
|
render,
|
||||||
panes
|
panes
|
||||||
} = this.props;
|
} = this.props;
|
||||||
return panes.map(({ name, left, right, dividerLeft }) => {
|
return panes.map(({ name, left, right, dividerLeft }) => {
|
||||||
const { Component } = nameToComponent[name] || {};
|
|
||||||
const FinalComponent = Component ? Component : 'span';
|
|
||||||
const divider = dividerLeft ?
|
const divider = dividerLeft ?
|
||||||
(
|
(
|
||||||
<Divider
|
<Divider
|
||||||
@ -71,7 +74,7 @@ export class Panes extends PureComponent {
|
|||||||
left={ left }
|
left={ left }
|
||||||
right={ right }
|
right={ right }
|
||||||
>
|
>
|
||||||
<FinalComponent />
|
{ render(name) }
|
||||||
</Pane>,
|
</Pane>,
|
||||||
divider
|
divider
|
||||||
];
|
];
|
||||||
|
@ -1 +1 @@
|
|||||||
export default from './Panes-Container.jsx';
|
export default from './Panes.jsx';
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { isLocationAction } from 'redux-first-router';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import invariant from 'invariant';
|
||||||
import {
|
import {
|
||||||
composeReducers,
|
composeReducers,
|
||||||
createAction,
|
createAction,
|
||||||
@ -11,17 +11,14 @@ import ns from '../ns.json';
|
|||||||
|
|
||||||
import windowEpic from './window-epic.js';
|
import windowEpic from './window-epic.js';
|
||||||
import dividerEpic from './divider-epic.js';
|
import dividerEpic from './divider-epic.js';
|
||||||
import { challengeMetaSelector } from '../../routes/Challenges/redux';
|
|
||||||
import { types as app } from '../../redux';
|
|
||||||
|
|
||||||
const isDev = process.env.NODE_ENV !== 'production';
|
|
||||||
export const epics = [
|
export const epics = [
|
||||||
windowEpic,
|
windowEpic,
|
||||||
dividerEpic
|
dividerEpic
|
||||||
];
|
];
|
||||||
|
|
||||||
export const types = createTypes([
|
export const types = createTypes([
|
||||||
'panesUpdatedThroughFetch',
|
'panesMapUpdated',
|
||||||
'panesMounted',
|
'panesMounted',
|
||||||
'panesUpdated',
|
'panesUpdated',
|
||||||
'panesWillMount',
|
'panesWillMount',
|
||||||
@ -38,10 +35,10 @@ export const types = createTypes([
|
|||||||
'hidePane'
|
'hidePane'
|
||||||
], ns);
|
], ns);
|
||||||
|
|
||||||
export const panesUpdatedThroughFetch = createAction(
|
export const panesMapUpdated = createAction(
|
||||||
types.panesUpdatedThroughFetch,
|
types.panesMapUpdated,
|
||||||
null,
|
null,
|
||||||
panesView => ({ panesView })
|
(type, panesMap) => ({ trigger: type, panesMap })
|
||||||
);
|
);
|
||||||
export const panesMounted = createAction(types.panesMounted);
|
export const panesMounted = createAction(types.panesMounted);
|
||||||
export const panesUpdated = createAction(types.panesUpdated);
|
export const panesUpdated = createAction(types.panesUpdated);
|
||||||
@ -57,14 +54,14 @@ export const windowResized = createAction(types.windowResized);
|
|||||||
export const updateNavHeight = createAction(types.updateNavHeight);
|
export const updateNavHeight = createAction(types.updateNavHeight);
|
||||||
export const hidePane = createAction(types.hidePane);
|
export const hidePane = createAction(types.hidePane);
|
||||||
|
|
||||||
const initialState = {
|
const defaultState = {
|
||||||
height: 600,
|
height: 600,
|
||||||
width: 800,
|
width: 800,
|
||||||
navHeight: 50,
|
navHeight: 50,
|
||||||
panes: [],
|
panes: [],
|
||||||
panesByName: {},
|
panesByName: {},
|
||||||
pressedDivider: null,
|
pressedDivider: null,
|
||||||
nameToType: {}
|
panesMap: {}
|
||||||
};
|
};
|
||||||
export const getNS = state => state[ns];
|
export const getNS = state => state[ns];
|
||||||
export const heightSelector = state => {
|
export const heightSelector = state => {
|
||||||
@ -77,10 +74,10 @@ export const panesByNameSelector = state => getNS(state).panesByName;
|
|||||||
export const pressedDividerSelector =
|
export const pressedDividerSelector =
|
||||||
state => getNS(state).pressedDivider;
|
state => getNS(state).pressedDivider;
|
||||||
export const widthSelector = state => getNS(state).width;
|
export const widthSelector = state => getNS(state).width;
|
||||||
export const nameToTypeSelector = state => getNS(state).nameToType;
|
export const panesMapSelector = state => getNS(state).panesMap;
|
||||||
|
|
||||||
function isPanesAction({ type } = {}, typeToName) {
|
function isPanesAction({ type } = {}, panesMap) {
|
||||||
return !!typeToName[type];
|
return !!panesMap[type];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDividerLeft(numOfPanes, index) {
|
function getDividerLeft(numOfPanes, index) {
|
||||||
@ -91,98 +88,58 @@ function getDividerLeft(numOfPanes, index) {
|
|||||||
return dividerLeft;
|
return dividerLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
function forEachConfig(config, cb) {
|
function checkForTypeKeys(panesMap) {
|
||||||
return _.forEach(config, (val, key) => {
|
_.forEach(panesMap, (_, actionType) => {
|
||||||
// val is a sub config
|
invariant(
|
||||||
if (_.isObject(val) && !val.name) {
|
actionType !== 'undefined',
|
||||||
return forEachConfig(val, cb);
|
`action type for ${panesMap[actionType]} is undefined`
|
||||||
}
|
);
|
||||||
return cb(config, key);
|
|
||||||
});
|
});
|
||||||
}
|
return panesMap;
|
||||||
|
|
||||||
function reduceConfig(config, cb, acc = {}) {
|
|
||||||
return _.reduce(config, (acc, val, key) => {
|
|
||||||
if (_.isObject(val) && !val.name) {
|
|
||||||
return reduceConfig(val, cb, acc);
|
|
||||||
}
|
|
||||||
return cb(acc, val, key);
|
|
||||||
}, acc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getPaneName = (panes, index) => (panes[index] || {}).name || '';
|
const getPaneName = (panes, index) => (panes[index] || {}).name || '';
|
||||||
|
|
||||||
export const createPaneMap = (ns, getPanesMap) => {
|
function normalizePanesMapCreator(createPanesMap) {
|
||||||
const panesMap = _.reduce(getPanesMap(), (map, val, key) => {
|
invariant(
|
||||||
let paneConfig = val;
|
_.isFunction(createPanesMap),
|
||||||
if (typeof val === 'string') {
|
'createPanesMap should be a function but got %s',
|
||||||
paneConfig = {
|
createPanesMap
|
||||||
name: val
|
);
|
||||||
};
|
const panesMap = createPanesMap({}, { type: '@@panes/test' });
|
||||||
}
|
if (typeof panesMap === 'function') {
|
||||||
map[key] = paneConfig;
|
return normalizePanesMapCreator(panesMap);
|
||||||
return map;
|
|
||||||
}, {});
|
|
||||||
return Object.defineProperty(panesMap, 'toString', { value: () => ns });
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export default function createPanesAspects(config) {
|
|
||||||
if (isDev) {
|
|
||||||
forEachConfig(config, (typeToName, actionType) => {
|
|
||||||
if (actionType === 'undefined') {
|
|
||||||
throw new Error(
|
|
||||||
`action type for ${typeToName[actionType]} is undefined`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
const typeToName = reduceConfig(config, (acc, val, type) => {
|
invariant(
|
||||||
const name = _.isObject(val) ? val.name : val;
|
!panesMap,
|
||||||
acc[type] = name;
|
'panesMap test should return undefined or null on test action but got %s',
|
||||||
return acc;
|
panesMap
|
||||||
});
|
);
|
||||||
|
return createPanesMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function createPanesAspects({ createPanesMap }) {
|
||||||
|
createPanesMap = normalizePanesMapCreator(createPanesMap);
|
||||||
|
|
||||||
function middleware({ getState }) {
|
function middleware({ getState }) {
|
||||||
const filterPanes = panesMap => _.reduce(panesMap, (panes, pane, type) => {
|
|
||||||
if (typeof pane.filter !== 'function' || pane.filter(getState())) {
|
|
||||||
panes[type] = pane;
|
|
||||||
}
|
|
||||||
return panes;
|
|
||||||
}, {});
|
|
||||||
// we cache the previous map so that we can attach it to the fetchChallenge
|
|
||||||
let previousMap;
|
|
||||||
// show panes on challenge route
|
|
||||||
// select panes map on viewType (this is state dependent)
|
|
||||||
// filter panes out on state
|
|
||||||
return next => action => {
|
return next => action => {
|
||||||
let finalAction = action;
|
let finalAction = action;
|
||||||
if (isPanesAction(action, typeToName)) {
|
const panesMap = panesMapSelector(getState());
|
||||||
|
if (isPanesAction(action, panesMap)) {
|
||||||
finalAction = {
|
finalAction = {
|
||||||
...action,
|
...action,
|
||||||
meta: {
|
meta: {
|
||||||
...action.meta,
|
...action.meta,
|
||||||
isPaneAction: true,
|
isPaneAction: true,
|
||||||
paneName: typeToName[action.type]
|
paneName: panesMap[action.type]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const result = next(finalAction);
|
const result = next(finalAction);
|
||||||
if (isLocationAction(action)) {
|
const nextPanesMap = createPanesMap(getState(), action);
|
||||||
// location matches a panes route
|
if (nextPanesMap) {
|
||||||
if (config[action.type]) {
|
checkForTypeKeys(nextPanesMap);
|
||||||
const paneMap = previousMap = config[action.type];
|
next(panesMapUpdated(action.type, nextPanesMap));
|
||||||
const meta = challengeMetaSelector(getState());
|
|
||||||
const viewMap = paneMap[meta.viewType] || {};
|
|
||||||
next(panesUpdatedThroughFetch(filterPanes(viewMap)));
|
|
||||||
} else {
|
|
||||||
next(panesUpdatedThroughFetch({}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (action.type === app.fetchChallenge.complete) {
|
|
||||||
const meta = challengeMetaSelector(getState());
|
|
||||||
const viewMap = previousMap[meta.viewType] || {};
|
|
||||||
next(panesUpdatedThroughFetch(filterPanes(viewMap)));
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
@ -242,15 +199,16 @@ export default function createPanesAspects(config) {
|
|||||||
navHeight
|
navHeight
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
initialState,
|
defaultState,
|
||||||
),
|
),
|
||||||
function metaReducer(state = initialState, action) {
|
function metaReducer(state = defaultState, action) {
|
||||||
if (action.meta && action.meta.panesView) {
|
if (action.meta && action.meta.panesMap) {
|
||||||
const panesView = action.meta.panesView;
|
const panesMap = action.meta.panesMap;
|
||||||
const panes = _.map(panesView, ({ name }, type) => ({ name, type }));
|
const panes = _.map(panesMap, (name, type) => ({ name, type }));
|
||||||
const numOfPanes = Object.keys(panes).length;
|
const numOfPanes = Object.keys(panes).length;
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
panesMap,
|
||||||
panes,
|
panes,
|
||||||
panesByName: panes.reduce((panes, { name }, index) => {
|
panesByName: panes.reduce((panes, { name }, index) => {
|
||||||
const dividerLeft = getDividerLeft(numOfPanes, index);
|
const dividerLeft = getDividerLeft(numOfPanes, index);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { selectLocationState } from 'redux-first-router';
|
import { selectLocationState } from 'redux-first-router';
|
||||||
|
|
||||||
export const paramsSelector = state => selectLocationState(state).payload || {};
|
export const paramsSelector = state => selectLocationState(state).payload || {};
|
||||||
|
export const locationTypeSelector =
|
||||||
|
state => selectLocationState(state).type || '';
|
||||||
export const langSelector = state => paramsSelector(state).lang || 'en';
|
export const langSelector = state => paramsSelector(state).lang || 'en';
|
||||||
export const routesMapSelector = state => paramsSelector(state).routesMap || {};
|
export const routesMapSelector = state => paramsSelector(state).routesMap || {};
|
||||||
|
@ -55,7 +55,7 @@ export default function createApp({
|
|||||||
const {
|
const {
|
||||||
reducer: panesReducer,
|
reducer: panesReducer,
|
||||||
middleware: panesMiddleware
|
middleware: panesMiddleware
|
||||||
} = createPanesAspects(createPanesMap());
|
} = createPanesAspects({ createPanesMap });
|
||||||
|
|
||||||
const enhancer = compose(
|
const enhancer = compose(
|
||||||
addLangToRoutesEnhancer(routesMap),
|
addLangToRoutesEnhancer(routesMap),
|
||||||
|
@ -1,7 +1 @@
|
|||||||
import { createPanesMap as routesPanes } from './routes/';
|
export { createPanesMap as default } from './routes/';
|
||||||
|
|
||||||
export default function createPanesMap() {
|
|
||||||
return {
|
|
||||||
...routesPanes()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
import {
|
import {
|
||||||
combineActions,
|
combineActions,
|
||||||
createAction,
|
createAction,
|
||||||
@ -27,6 +28,10 @@ export const savedCodeFound = createAction(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const filesSelector = state => state[ns];
|
export const filesSelector = state => state[ns];
|
||||||
|
export const createFileSelector = keySelector => (state, props) => {
|
||||||
|
const files = filesSelector(state);
|
||||||
|
return files[keySelector(state, props)] || {};
|
||||||
|
};
|
||||||
|
|
||||||
export default handleActions(
|
export default handleActions(
|
||||||
() => ({
|
() => ({
|
||||||
@ -42,9 +47,12 @@ export default handleActions(
|
|||||||
}, { ...state });
|
}, { ...state });
|
||||||
},
|
},
|
||||||
[types.savedCodeFound]: (state, { payload: { files, challenge } }) => {
|
[types.savedCodeFound]: (state, { payload: { files, challenge } }) => {
|
||||||
if (challenge.type === 'mod') {
|
if (challenge.type === 'modern') {
|
||||||
// this may need to change to update head/tail
|
// this may need to change to update head/tail
|
||||||
return challenge.files;
|
return _.reduce(files, (files, file) => {
|
||||||
|
files[file.key] = createPoly(file);
|
||||||
|
return files;
|
||||||
|
}, {});
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
challenge.challengeType !== html &&
|
challenge.challengeType !== html &&
|
||||||
@ -70,8 +78,11 @@ export default handleActions(
|
|||||||
app.fetchChallenge.complete
|
app.fetchChallenge.complete
|
||||||
)
|
)
|
||||||
]: (state, { payload: { challenge } }) => {
|
]: (state, { payload: { challenge } }) => {
|
||||||
if (challenge.type === 'mod') {
|
if (challenge.type === 'modern') {
|
||||||
return challenge.files;
|
return _.reduce(challenge.files, (files, file) => {
|
||||||
|
files[file.key] = createPoly(file);
|
||||||
|
return files;
|
||||||
|
}, {});
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
challenge.challengeType !== html &&
|
challenge.challengeType !== html &&
|
||||||
|
@ -11,6 +11,7 @@ import Step from './views/step';
|
|||||||
import Project from './views/project';
|
import Project from './views/project';
|
||||||
import BackEnd from './views/backend';
|
import BackEnd from './views/backend';
|
||||||
import Quiz from './views/quiz';
|
import Quiz from './views/quiz';
|
||||||
|
import Modern from './views/Modern';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
fetchChallenge,
|
fetchChallenge,
|
||||||
@ -23,10 +24,11 @@ import { paramsSelector } from '../../Router/redux';
|
|||||||
const views = {
|
const views = {
|
||||||
backend: BackEnd,
|
backend: BackEnd,
|
||||||
classic: Classic,
|
classic: Classic,
|
||||||
|
modern: Modern,
|
||||||
project: Project,
|
project: Project,
|
||||||
|
quiz: Quiz,
|
||||||
simple: Project,
|
simple: Project,
|
||||||
step: Step,
|
step: Step
|
||||||
quiz: Quiz
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
|
@ -7,12 +7,12 @@ import PureComponent from 'react-pure-render/component';
|
|||||||
|
|
||||||
import ns from './ns.json';
|
import ns from './ns.json';
|
||||||
|
|
||||||
import BugModal from '../../Bug-Modal.jsx';
|
import BugModal from './Bug-Modal.jsx';
|
||||||
import ToolPanel from './Tool-Panel.jsx';
|
import ToolPanel from './Tool-Panel.jsx';
|
||||||
import ChallengeTitle from '../../Challenge-Title.jsx';
|
import ChallengeTitle from './Challenge-Title.jsx';
|
||||||
import ChallengeDescription from '../../Challenge-Description.jsx';
|
import ChallengeDescription from './Challenge-Description.jsx';
|
||||||
import TestSuite from '../../Test-Suite.jsx';
|
import TestSuite from './Test-Suite.jsx';
|
||||||
import Output from '../../Output.jsx';
|
import Output from './Output.jsx';
|
||||||
import {
|
import {
|
||||||
openBugModal,
|
openBugModal,
|
||||||
updateHint,
|
updateHint,
|
||||||
@ -25,11 +25,11 @@ import {
|
|||||||
hintIndexSelector,
|
hintIndexSelector,
|
||||||
codeLockedSelector,
|
codeLockedSelector,
|
||||||
chatRoomSelector
|
chatRoomSelector
|
||||||
} from '../../redux';
|
} from './redux';
|
||||||
|
|
||||||
import { descriptionRegex } from '../../utils';
|
import { descriptionRegex } from './utils';
|
||||||
import { challengeSelector } from '../../../../redux';
|
import { challengeSelector } from '../../redux';
|
||||||
import { makeToast } from '../../../../Toasts/redux';
|
import { makeToast } from '../../Toasts/redux';
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
makeToast,
|
makeToast,
|
@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Button, ButtonGroup, Tooltip, OverlayTrigger } from 'react-bootstrap';
|
import { Button, ButtonGroup, Tooltip, OverlayTrigger } from 'react-bootstrap';
|
||||||
import PureComponent from 'react-pure-render/component';
|
|
||||||
|
|
||||||
const unlockWarning = (
|
const unlockWarning = (
|
||||||
<Tooltip id='tooltip'>
|
<Tooltip id='tooltip'>
|
@ -1,11 +1,15 @@
|
|||||||
import { redirect } from 'redux-first-router';
|
import _ from 'lodash';
|
||||||
|
import { isLocationAction, redirect } from 'redux-first-router';
|
||||||
|
|
||||||
import { types } from './redux';
|
import { types, challengeMetaSelector } from './redux';
|
||||||
import { panesMap as backendPanesMap } from './views/backend';
|
import { mapStateToPanes as backendPanesMap } from './views/backend';
|
||||||
import { panesMap as classicPanesMap } from './views/classic';
|
import { mapStateToPanes as classicPanesMap } from './views/classic';
|
||||||
import { panesMap as stepPanesMap } from './views/step';
|
import { mapStateToPanes as stepPanesMap } from './views/step';
|
||||||
import { panesMap as projectPanesMap } from './views/project';
|
import { mapStateToPanes as projectPanesMap } from './views/project';
|
||||||
import { panesMap as quizPanesMap } from './views/quiz';
|
import { mapStateToPanes as quizPanesMap } from './views/quiz';
|
||||||
|
import { mapStateToPanes as modernPanesMap } from './views/Modern';
|
||||||
|
import { types as app } from '../../redux';
|
||||||
|
import { locationTypeSelector } from '../../Router/redux';
|
||||||
|
|
||||||
export const routes = {
|
export const routes = {
|
||||||
[types.onRouteChallengeRoot]: {
|
[types.onRouteChallengeRoot]: {
|
||||||
@ -18,15 +22,43 @@ export const routes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function createPanesMap() {
|
export function createPanesMap() {
|
||||||
return {
|
const viewMap = {
|
||||||
// the route to use this panes map on
|
[backendPanesMap]: backendPanesMap,
|
||||||
[types.onRouteChallenges]: {
|
[classicPanesMap]: classicPanesMap,
|
||||||
[backendPanesMap]: backendPanesMap,
|
[stepPanesMap]: stepPanesMap,
|
||||||
[classicPanesMap]: classicPanesMap,
|
[projectPanesMap]: projectPanesMap,
|
||||||
[stepPanesMap]: stepPanesMap,
|
[quizPanesMap]: quizPanesMap,
|
||||||
[projectPanesMap]: projectPanesMap,
|
[modernPanesMap]: modernPanesMap
|
||||||
[quizPanesMap]: quizPanesMap
|
};
|
||||||
|
return (state, action) => {
|
||||||
|
// if a location action has dispatched then we must update the panesmap
|
||||||
|
if (isLocationAction(action)) {
|
||||||
|
let finalPanesMap = {};
|
||||||
|
// if we are on this route,
|
||||||
|
// then we must figure out the currect view we are on
|
||||||
|
// this depends on the type of challenge
|
||||||
|
if (action.type === types.onRouteChallenges) {
|
||||||
|
// location matches a panes route
|
||||||
|
const meta = challengeMetaSelector(state);
|
||||||
|
// if challenge data has not been fetched yet (as in the case of SSR)
|
||||||
|
// then we will get a pojo factory
|
||||||
|
const mapStateToPanes = viewMap[meta.viewType] || _.stubObject;
|
||||||
|
finalPanesMap = mapStateToPanes(state);
|
||||||
|
}
|
||||||
|
return finalPanesMap;
|
||||||
}
|
}
|
||||||
|
// This should normally happen during SSR
|
||||||
|
// here we are ensured that the challenge data has been fetched
|
||||||
|
// now we can select the appropriate panes map factory
|
||||||
|
if (
|
||||||
|
action.type === app.fetchChallenge.complete &&
|
||||||
|
locationTypeSelector(state) === types.onRouteChallenges
|
||||||
|
) {
|
||||||
|
const meta = challengeMetaSelector(state);
|
||||||
|
const mapStateToPanes = viewMap[meta.viewType] || _.stubObject;
|
||||||
|
return mapStateToPanes(state);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ofType } from 'redux-epic';
|
import { ofType, combineEpics } from 'redux-epic';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
types,
|
types,
|
||||||
@ -7,7 +7,7 @@ import {
|
|||||||
|
|
||||||
import { updateFile } from '../../../files';
|
import { updateFile } from '../../../files';
|
||||||
|
|
||||||
export default function editorEpic(actions, { getState }) {
|
export function classicEditorEpic(actions, { getState }) {
|
||||||
return actions::ofType(types.classicEditorUpdated)
|
return actions::ofType(types.classicEditorUpdated)
|
||||||
.pluck('payload')
|
.pluck('payload')
|
||||||
.map(content => updateFile({
|
.map(content => updateFile({
|
||||||
@ -15,3 +15,11 @@ export default function editorEpic(actions, { getState }) {
|
|||||||
key: keySelector(getState())
|
key: keySelector(getState())
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function modernEditorEpic(actions) {
|
||||||
|
return actions::ofType(types.modernEditorUpdated)
|
||||||
|
.pluck('payload')
|
||||||
|
.map(updateFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default combineEpics(classicEditorEpic, modernEditorEpic);
|
||||||
|
@ -57,6 +57,8 @@ export const types = createTypes([
|
|||||||
'unlockUntrustedCode',
|
'unlockUntrustedCode',
|
||||||
'closeChallengeModal',
|
'closeChallengeModal',
|
||||||
'updateSuccessMessage',
|
'updateSuccessMessage',
|
||||||
|
// |- modern
|
||||||
|
'modernEditorUpdated',
|
||||||
|
|
||||||
// rechallenge
|
// rechallenge
|
||||||
'executeChallenge',
|
'executeChallenge',
|
||||||
@ -83,7 +85,8 @@ export const types = createTypes([
|
|||||||
'toggleMap',
|
'toggleMap',
|
||||||
'togglePreview',
|
'togglePreview',
|
||||||
'toggleSidePanel',
|
'toggleSidePanel',
|
||||||
'toggleStep'
|
'toggleStep',
|
||||||
|
'toggleModernEditor'
|
||||||
], ns);
|
], ns);
|
||||||
|
|
||||||
// routes
|
// routes
|
||||||
@ -93,6 +96,11 @@ export const onRouteCurrentChallenge =
|
|||||||
|
|
||||||
// classic
|
// classic
|
||||||
export const classicEditorUpdated = createAction(types.classicEditorUpdated);
|
export const classicEditorUpdated = createAction(types.classicEditorUpdated);
|
||||||
|
// modern
|
||||||
|
export const modernEditorUpdated = createAction(
|
||||||
|
types.modernEditorUpdated,
|
||||||
|
(key, content) => ({ key, content })
|
||||||
|
);
|
||||||
// challenges
|
// challenges
|
||||||
export const closeChallengeModal = createAction(types.closeChallengeModal);
|
export const closeChallengeModal = createAction(types.closeChallengeModal);
|
||||||
export const updateHint = createAction(types.updateHint);
|
export const updateHint = createAction(types.updateHint);
|
||||||
@ -204,6 +212,9 @@ export const challengeMetaSelector = createSelector(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const showPreviewSelector = state =>
|
||||||
|
!!challengeMetaSelector(state).showPreview;
|
||||||
|
|
||||||
export default combineReducers(
|
export default combineReducers(
|
||||||
handleActions(
|
handleActions(
|
||||||
() => ({
|
() => ({
|
||||||
|
@ -13,7 +13,8 @@ export const viewTypes = {
|
|||||||
[ challengeTypes.video ]: 'video',
|
[ challengeTypes.video ]: 'video',
|
||||||
[ challengeTypes.step ]: 'step',
|
[ challengeTypes.step ]: 'step',
|
||||||
[ challengeTypes.quiz ]: 'quiz',
|
[ challengeTypes.quiz ]: 'quiz',
|
||||||
backend: 'backend'
|
backend: 'backend',
|
||||||
|
modern: 'modern'
|
||||||
};
|
};
|
||||||
|
|
||||||
// determine the type of submit function to use for the challenge on completion
|
// determine the type of submit function to use for the challenge on completion
|
||||||
@ -34,7 +35,8 @@ export const submitTypes = {
|
|||||||
[ challengeTypes.video ]: 'video',
|
[ challengeTypes.video ]: 'video',
|
||||||
[ challengeTypes.step ]: 'step',
|
[ challengeTypes.step ]: 'step',
|
||||||
[ challengeTypes.quiz ]: 'quiz',
|
[ challengeTypes.quiz ]: 'quiz',
|
||||||
backend: 'backend'
|
backend: 'backend',
|
||||||
|
modern: 'tests'
|
||||||
};
|
};
|
||||||
|
|
||||||
// determines if a line in a challenge description
|
// determines if a line in a challenge description
|
||||||
|
146
common/app/routes/Challenges/views/Modern/Editor.jsx
Normal file
146
common/app/routes/Challenges/views/Modern/Editor.jsx
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
|
import Codemirror from 'react-codemirror';
|
||||||
|
import NoSSR from 'react-no-ssr';
|
||||||
|
import MouseTrap from 'mousetrap';
|
||||||
|
|
||||||
|
import ns from './ns.json';
|
||||||
|
import CodeMirrorSkeleton from '../../Code-Mirror-Skeleton.jsx';
|
||||||
|
import {
|
||||||
|
executeChallenge,
|
||||||
|
modernEditorUpdated,
|
||||||
|
challengeMetaSelector
|
||||||
|
} from '../../redux';
|
||||||
|
|
||||||
|
import { createFileSelector } from '../../../../files';
|
||||||
|
|
||||||
|
const envProps = typeof window !== 'undefined' ? Object.keys(window) : [];
|
||||||
|
const options = {
|
||||||
|
lint: {
|
||||||
|
esversion: 6,
|
||||||
|
predef: envProps
|
||||||
|
},
|
||||||
|
lineNumbers: true,
|
||||||
|
mode: 'javascript',
|
||||||
|
runnable: true,
|
||||||
|
matchBrackets: true,
|
||||||
|
autoCloseBrackets: true,
|
||||||
|
scrollbarStyle: 'null',
|
||||||
|
lineWrapping: true,
|
||||||
|
gutters: [ 'CodeMirror-lint-markers' ]
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = createSelector(
|
||||||
|
createFileSelector((_, { fileKey }) => fileKey || ''),
|
||||||
|
challengeMetaSelector,
|
||||||
|
(
|
||||||
|
file,
|
||||||
|
{ mode }
|
||||||
|
) => ({
|
||||||
|
content: file.contents || '// Happy Coding!',
|
||||||
|
file: file,
|
||||||
|
mode: file.ext || mode || 'javascript'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
executeChallenge,
|
||||||
|
modernEditorUpdated
|
||||||
|
};
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
content: PropTypes.string,
|
||||||
|
executeChallenge: PropTypes.func.isRequired,
|
||||||
|
fileKey: PropTypes.string,
|
||||||
|
mode: PropTypes.string,
|
||||||
|
modernEditorUpdated: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Editor extends PureComponent {
|
||||||
|
createOptions = createSelector(
|
||||||
|
state => state.executeChallenge,
|
||||||
|
state => state.mode,
|
||||||
|
(executeChallenge, mode) => ({
|
||||||
|
...options,
|
||||||
|
mode,
|
||||||
|
// JSHint only works with javascript
|
||||||
|
// we will need to switch to eslint to make this work with jsx
|
||||||
|
lint: mode === 'javascript' ? options.lint : false,
|
||||||
|
extraKeys: {
|
||||||
|
Esc() {
|
||||||
|
document.activeElement.blur();
|
||||||
|
},
|
||||||
|
Tab(cm) {
|
||||||
|
if (cm.somethingSelected()) {
|
||||||
|
return cm.indentSelection('add');
|
||||||
|
}
|
||||||
|
const spaces = Array(cm.getOption('indentUnit') + 1).join(' ');
|
||||||
|
return cm.replaceSelection(spaces);
|
||||||
|
},
|
||||||
|
'Shift-Tab': function(cm) {
|
||||||
|
return cm.indentSelection('subtract');
|
||||||
|
},
|
||||||
|
'Ctrl-Enter': function() {
|
||||||
|
executeChallenge();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
'Cmd-Enter': function() {
|
||||||
|
executeChallenge();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
'Ctrl-/': function(cm) {
|
||||||
|
cm.toggleComment();
|
||||||
|
},
|
||||||
|
'Cmd-/': function(cm) {
|
||||||
|
cm.toggleComment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
MouseTrap.bind('e', () => {
|
||||||
|
this.refs.editor.focus();
|
||||||
|
}, 'keyup');
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
MouseTrap.unbind('e', 'keyup');
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
content,
|
||||||
|
modernEditorUpdated,
|
||||||
|
executeChallenge,
|
||||||
|
fileKey,
|
||||||
|
mode
|
||||||
|
} = this.props;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={ `${ns}-editor` }
|
||||||
|
role='main'
|
||||||
|
>
|
||||||
|
<NoSSR onSSR={ <CodeMirrorSkeleton content={ content } /> }>
|
||||||
|
<Codemirror
|
||||||
|
onChange={ content => modernEditorUpdated(fileKey, content) }
|
||||||
|
options={ this.createOptions({ executeChallenge, mode }) }
|
||||||
|
ref='editor'
|
||||||
|
value={ content }
|
||||||
|
/>
|
||||||
|
</NoSSR>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Editor.displayName = 'Editor';
|
||||||
|
Editor.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(Editor);
|
93
common/app/routes/Challenges/views/Modern/Show.jsx
Normal file
93
common/app/routes/Challenges/views/Modern/Show.jsx
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { addNS } from 'berkeleys-redux-utils';
|
||||||
|
|
||||||
|
import ns from './ns.json';
|
||||||
|
import Editor from './Editor.jsx';
|
||||||
|
import { showPreviewSelector, types } from '../../redux';
|
||||||
|
import SidePanel from '../../Side-Panel.jsx';
|
||||||
|
import Panes from '../../../../Panes';
|
||||||
|
import _Map from '../../../../Map';
|
||||||
|
import ChildContainer from '../../../../Child-Container.jsx';
|
||||||
|
import { filesSelector } from '../../../../files';
|
||||||
|
|
||||||
|
const createModernEditorToggleType = fileKey =>
|
||||||
|
types.toggleModernEditor + `(${fileKey})`;
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
nameToFileKey: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = createSelector(
|
||||||
|
filesSelector,
|
||||||
|
files => ({
|
||||||
|
nameToFileKey: _.reduce(files, (map, file) => {
|
||||||
|
map[file.name] = file.key;
|
||||||
|
return map;
|
||||||
|
}, {})
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const mapDispatchToProps = null;
|
||||||
|
|
||||||
|
export const mapStateToPanes = addNS(
|
||||||
|
ns,
|
||||||
|
createSelector(
|
||||||
|
filesSelector,
|
||||||
|
showPreviewSelector,
|
||||||
|
(files, showPreview)=> {
|
||||||
|
// create panes map here
|
||||||
|
// must include map
|
||||||
|
// side panel
|
||||||
|
// editors are created based on state
|
||||||
|
// so pane component can have multiple panes based on state
|
||||||
|
const panesMap = _.reduce(files, (map, file) => {
|
||||||
|
map[createModernEditorToggleType(file.fileKey)] = file.name;
|
||||||
|
return map;
|
||||||
|
}, {
|
||||||
|
[types.toggleMap]: 'Map',
|
||||||
|
[types.toggleSidePanel]: 'Side Panel'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (showPreview) {
|
||||||
|
panesMap[types.togglePreview] = 'Preview';
|
||||||
|
}
|
||||||
|
return panesMap;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const nameToComponent = {
|
||||||
|
Map: _Map,
|
||||||
|
'Side Panel': SidePanel
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ShowModern({ nameToFileKey }) {
|
||||||
|
return (
|
||||||
|
<ChildContainer isFullWidth={ true }>
|
||||||
|
<Panes
|
||||||
|
render={ name => {
|
||||||
|
const Comp = nameToComponent[name];
|
||||||
|
if (Comp) {
|
||||||
|
return <Comp />;
|
||||||
|
}
|
||||||
|
if (nameToFileKey[name]) {
|
||||||
|
return <Editor fileKey={ nameToFileKey[name] } />;
|
||||||
|
}
|
||||||
|
return <span>Could not find Component for { name }</span>;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ChildContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShowModern.displayName = 'ShowModern';
|
||||||
|
ShowModern.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(ShowModern);
|
1
common/app/routes/Challenges/views/Modern/index.js
Normal file
1
common/app/routes/Challenges/views/Modern/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default, mapStateToPanes } from './Show.jsx';
|
1
common/app/routes/Challenges/views/Modern/ns.json
Normal file
1
common/app/routes/Challenges/views/Modern/ns.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
"modern"
|
@ -1,15 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { addNS } from 'berkeleys-redux-utils';
|
||||||
|
|
||||||
import BackEnd from './Back-End.jsx';
|
import BackEnd from './Back-End.jsx';
|
||||||
import { types } from '../../redux';
|
import { types } from '../../redux';
|
||||||
import Panes from '../../../../Panes';
|
import Panes from '../../../../Panes';
|
||||||
import { createPaneMap } from '../../../../Panes/redux';
|
|
||||||
import _Map from '../../../../Map';
|
import _Map from '../../../../Map';
|
||||||
import ChildContainer from '../../../../Child-Container.jsx';
|
import ChildContainer from '../../../../Child-Container.jsx';
|
||||||
|
|
||||||
const propTypes = {};
|
const propTypes = {};
|
||||||
|
|
||||||
export const panesMap = createPaneMap(
|
export const mapStateToPanes = addNS(
|
||||||
'backend',
|
'backend',
|
||||||
() => ({
|
() => ({
|
||||||
[types.toggleMap]: 'Map',
|
[types.toggleMap]: 'Map',
|
||||||
@ -17,21 +17,20 @@ export const panesMap = createPaneMap(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const nameToComponentDef = {
|
const nameToComponent = {
|
||||||
Map: {
|
Map: _Map,
|
||||||
Component: _Map,
|
Main: BackEnd
|
||||||
defaultSize: 25
|
};
|
||||||
},
|
|
||||||
Main: {
|
const renderPane = name => {
|
||||||
Component: BackEnd,
|
const Comp = nameToComponent[name];
|
||||||
defaultSize: 50
|
return Comp ? <Comp /> : <span>Pane { name } not found</span>;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ShowBackEnd() {
|
export default function ShowBackEnd() {
|
||||||
return (
|
return (
|
||||||
<ChildContainer isFullWidth={ true }>
|
<ChildContainer isFullWidth={ true }>
|
||||||
<Panes nameToComponent={ nameToComponentDef } />
|
<Panes render={ renderPane } />
|
||||||
</ChildContainer>
|
</ChildContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
export { default, panesMap } from './Show.jsx';
|
export { default, mapStateToPanes } from './Show.jsx';
|
||||||
|
@ -1,48 +1,48 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { addNS } from 'berkeleys-redux-utils';
|
||||||
|
|
||||||
import SidePanel from './Side-Panel.jsx';
|
|
||||||
import Editor from './Editor.jsx';
|
import Editor from './Editor.jsx';
|
||||||
import Preview from './Preview.jsx';
|
import Preview from './Preview.jsx';
|
||||||
import { types, challengeMetaSelector } from '../../redux';
|
import { types, showPreviewSelector } from '../../redux';
|
||||||
|
import SidePanel from '../../Side-Panel.jsx';
|
||||||
import Panes from '../../../../Panes';
|
import Panes from '../../../../Panes';
|
||||||
import { createPaneMap } from '../../../../Panes/redux';
|
|
||||||
import _Map from '../../../../Map';
|
import _Map from '../../../../Map';
|
||||||
import ChildContainer from '../../../../Child-Container.jsx';
|
import ChildContainer from '../../../../Child-Container.jsx';
|
||||||
|
|
||||||
const propTypes = {};
|
const propTypes = {};
|
||||||
|
|
||||||
export const panesMap = createPaneMap(
|
export const mapStateToPanes = addNS(
|
||||||
'classic',
|
'classic',
|
||||||
() => ({
|
state => {
|
||||||
[types.toggleMap]: 'Map',
|
const panesMap = {
|
||||||
[types.toggleSidePanel]: 'Side Panel',
|
[types.toggleMap]: 'Map',
|
||||||
[types.toggleClassicEditor]: 'Editor',
|
[types.toggleSidePanel]: 'Side Panel',
|
||||||
[types.togglePreview]: {
|
[types.toggleClassicEditor]: 'Editor'
|
||||||
name: 'Preview',
|
};
|
||||||
filter: state => !!challengeMetaSelector(state).showPreview
|
|
||||||
|
if (showPreviewSelector(state)) {
|
||||||
|
panesMap[types.togglePreview] = 'Preview';
|
||||||
}
|
}
|
||||||
})
|
return panesMap;
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const nameToComponent = {
|
const nameToComponent = {
|
||||||
Map: {
|
Map: _Map,
|
||||||
Component: _Map
|
'Side Panel': SidePanel,
|
||||||
},
|
Editor: Editor,
|
||||||
'Side Panel': {
|
Preview: Preview
|
||||||
Component: SidePanel
|
};
|
||||||
},
|
|
||||||
Editor: {
|
const renderPane = name => {
|
||||||
Component: Editor
|
const Comp = nameToComponent[name];
|
||||||
},
|
return Comp ? <Comp /> : <span>Pane for { name } not found</span>;
|
||||||
Preview: {
|
|
||||||
Component: Preview
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ShowClassic() {
|
export default function ShowClassic() {
|
||||||
return (
|
return (
|
||||||
<ChildContainer isFullWidth={ true }>
|
<ChildContainer isFullWidth={ true }>
|
||||||
<Panes nameToComponent={ nameToComponent }/>
|
<Panes render={ renderPane }/>
|
||||||
</ChildContainer>
|
</ChildContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
export { default, panesMap } from './Show.jsx';
|
export { default, mapStateToPanes } from './Show.jsx';
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { addNS } from 'berkeleys-redux-utils';
|
||||||
|
|
||||||
import ns from './ns.json';
|
import ns from './ns.json';
|
||||||
import Main from './Project.jsx';
|
import Main from './Project.jsx';
|
||||||
import { types } from '../../redux';
|
import { types } from '../../redux';
|
||||||
import Panes from '../../../../Panes';
|
import Panes from '../../../../Panes';
|
||||||
import { createPaneMap } from '../../../../Panes/redux';
|
|
||||||
import _Map from '../../../../Map';
|
import _Map from '../../../../Map';
|
||||||
import ChildContainer from '../../../../Child-Container.jsx';
|
import ChildContainer from '../../../../Child-Container.jsx';
|
||||||
|
|
||||||
const propTypes = {};
|
const propTypes = {};
|
||||||
export const panesMap = createPaneMap(
|
export const mapStateToPanes = addNS(
|
||||||
ns,
|
ns,
|
||||||
() => ({
|
() => ({
|
||||||
[types.toggleMap]: 'Map',
|
[types.toggleMap]: 'Map',
|
||||||
@ -18,18 +18,19 @@ export const panesMap = createPaneMap(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const nameToComponent = {
|
const nameToComponent = {
|
||||||
Map: {
|
Map: _Map,
|
||||||
Component: _Map
|
Main: Main
|
||||||
},
|
};
|
||||||
Main: {
|
|
||||||
Component: Main
|
const renderPane = name => {
|
||||||
}
|
const Comp = nameToComponent[name];
|
||||||
|
return Comp ? <Comp /> : <span>Pane { name } not found</span>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ShowProject() {
|
export default function ShowProject() {
|
||||||
return (
|
return (
|
||||||
<ChildContainer isFullWidth={ true }>
|
<ChildContainer isFullWidth={ true }>
|
||||||
<Panes nameToComponent={ nameToComponent }/>
|
<Panes render={ renderPane }/>
|
||||||
</ChildContainer>
|
</ChildContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
export { default, panesMap } from './Show.jsx';
|
export { default, mapStateToPanes } from './Show.jsx';
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { addNS } from 'berkeleys-redux-utils';
|
||||||
|
|
||||||
import ns from './ns.json';
|
import ns from './ns.json';
|
||||||
import Main from './Quiz.jsx';
|
import Main from './Quiz.jsx';
|
||||||
import { types } from '../../redux';
|
import { types } from '../../redux';
|
||||||
import Panes from '../../../../Panes';
|
import Panes from '../../../../Panes';
|
||||||
import { createPaneMap } from '../../../../Panes/redux';
|
|
||||||
import _Map from '../../../../Map';
|
import _Map from '../../../../Map';
|
||||||
import ChildContainer from '../../../../Child-Container.jsx';
|
import ChildContainer from '../../../../Child-Container.jsx';
|
||||||
|
|
||||||
const propTypes = {};
|
const propTypes = {};
|
||||||
export const panesMap = createPaneMap(
|
export const mapStateToPanes = addNS(
|
||||||
ns,
|
ns,
|
||||||
() => ({
|
() => ({
|
||||||
[types.toggleMap]: 'Map',
|
[types.toggleMap]: 'Map',
|
||||||
@ -18,18 +18,19 @@ export const panesMap = createPaneMap(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const nameToComponent = {
|
const nameToComponent = {
|
||||||
Map: {
|
Map: _Map,
|
||||||
Component: _Map
|
Main: Main
|
||||||
},
|
};
|
||||||
Main: {
|
|
||||||
Component: Main
|
const renderPane = name => {
|
||||||
}
|
const Comp = nameToComponent[name];
|
||||||
|
return Comp ? <Comp /> : <span>Pane { name } not found</span>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ShowQuiz() {
|
export default function ShowQuiz() {
|
||||||
return (
|
return (
|
||||||
<ChildContainer isFullWidth={ true }>
|
<ChildContainer isFullWidth={ true }>
|
||||||
<Panes nameToComponent={ nameToComponent }/>
|
<Panes render={ renderPane }/>
|
||||||
</ChildContainer>
|
</ChildContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
export { default, panesMap } from './Show.jsx';
|
export { default, mapStateToPanes } from './Show.jsx';
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { addNS } from 'berkeleys-redux-utils';
|
||||||
|
|
||||||
import ns from './ns.json';
|
import ns from './ns.json';
|
||||||
import Step from './Step.jsx';
|
import Step from './Step.jsx';
|
||||||
import { types } from '../../redux';
|
import { types } from '../../redux';
|
||||||
import Panes from '../../../../Panes';
|
import Panes from '../../../../Panes';
|
||||||
import { createPaneMap } from '../../../../Panes/redux';
|
|
||||||
import _Map from '../../../../Map';
|
import _Map from '../../../../Map';
|
||||||
import ChildContainer from '../../../../Child-Container.jsx';
|
import ChildContainer from '../../../../Child-Container.jsx';
|
||||||
|
|
||||||
const propTypes = {};
|
const propTypes = {};
|
||||||
export const panesMap = createPaneMap(
|
export const mapStateToPanes = addNS(
|
||||||
ns,
|
ns,
|
||||||
() => ({
|
() => ({
|
||||||
[types.toggleMap]: 'Map',
|
[types.toggleMap]: 'Map',
|
||||||
@ -18,18 +18,19 @@ export const panesMap = createPaneMap(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const nameToComponent = {
|
const nameToComponent = {
|
||||||
Map: {
|
Map: _Map,
|
||||||
Component: _Map
|
Step: Step
|
||||||
},
|
};
|
||||||
Step: {
|
|
||||||
Component: Step
|
const renderPane = name => {
|
||||||
}
|
const Comp = nameToComponent[name];
|
||||||
|
return Comp ? <Comp /> : <span>Pane { name } not found</span>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ShowStep() {
|
export default function ShowStep() {
|
||||||
return (
|
return (
|
||||||
<ChildContainer isFullWidth={ true }>
|
<ChildContainer isFullWidth={ true }>
|
||||||
<Panes nameToComponent={ nameToComponent }/>
|
<Panes render={ renderPane }/>
|
||||||
</ChildContainer>
|
</ChildContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
export { default, panesMap } from './Show.jsx';
|
export { default, mapStateToPanes } from './Show.jsx';
|
||||||
|
@ -108,6 +108,7 @@ const paths = {
|
|||||||
resolve('codemirror', 'lib/codemirror.js', 'addon/lint/lint.js'),
|
resolve('codemirror', 'lib/codemirror.js', 'addon/lint/lint.js'),
|
||||||
resolve('codemirror', 'lib/codemirror.js', 'addon/lint/javascript-lint.js'),
|
resolve('codemirror', 'lib/codemirror.js', 'addon/lint/javascript-lint.js'),
|
||||||
resolve('codemirror', 'lib/codemirror.js', 'mode/javascript/javascript.js'),
|
resolve('codemirror', 'lib/codemirror.js', 'mode/javascript/javascript.js'),
|
||||||
|
resolve('codemirror', 'lib/codemirror.js', 'mode/jsx/jsx.js'),
|
||||||
resolve('codemirror', 'lib/codemirror.js', 'mode/xml/xml.js'),
|
resolve('codemirror', 'lib/codemirror.js', 'mode/xml/xml.js'),
|
||||||
resolve('codemirror', 'lib/codemirror.js', 'mode/css/css.js'),
|
resolve('codemirror', 'lib/codemirror.js', 'mode/css/css.js'),
|
||||||
resolve('codemirror', 'lib/codemirror.js', 'mode/htmlmixed/htmlmixed.js'),
|
resolve('codemirror', 'lib/codemirror.js', 'mode/htmlmixed/htmlmixed.js'),
|
||||||
|
272
package-lock.json
generated
272
package-lock.json
generated
@ -1839,9 +1839,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"berkeleys-redux-utils": {
|
"berkeleys-redux-utils": {
|
||||||
"version": "3.2.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/berkeleys-redux-utils/-/berkeleys-redux-utils-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/berkeleys-redux-utils/-/berkeleys-redux-utils-4.0.0.tgz",
|
||||||
"integrity": "sha512-w6MZvum7TuVLxHoBqidlXtwdp325hEfTUx128tytV1sitHt75LnitR9evgWecV6O9IcKItx6lDNQiQkr177dBQ==",
|
"integrity": "sha512-2ZwevXuCFPgtg5mWmuWDClP/xAGH0+2Ijm5TL42nSEqY5JGwReis4W9Ltr5q3G7G7hfLt6eS+/7igPN3rHDe9g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"babel-runtime": "6.26.0",
|
"babel-runtime": "6.26.0",
|
||||||
"invariant": "2.2.2"
|
"invariant": "2.2.2"
|
||||||
@ -1904,6 +1904,11 @@
|
|||||||
"type-is": "1.6.15"
|
"type-is": "1.6.15"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"boolbase": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
|
||||||
|
},
|
||||||
"boom": {
|
"boom": {
|
||||||
"version": "4.3.1",
|
"version": "4.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz",
|
||||||
@ -2434,6 +2439,39 @@
|
|||||||
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
|
||||||
"integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc="
|
"integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc="
|
||||||
},
|
},
|
||||||
|
"cheerio": {
|
||||||
|
"version": "1.0.0-rc.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz",
|
||||||
|
"integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=",
|
||||||
|
"requires": {
|
||||||
|
"css-select": "1.2.0",
|
||||||
|
"dom-serializer": "0.1.0",
|
||||||
|
"entities": "1.1.1",
|
||||||
|
"htmlparser2": "3.9.2",
|
||||||
|
"lodash": "4.17.4",
|
||||||
|
"parse5": "3.0.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"entities": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz",
|
||||||
|
"integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA="
|
||||||
|
},
|
||||||
|
"htmlparser2": {
|
||||||
|
"version": "3.9.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz",
|
||||||
|
"integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=",
|
||||||
|
"requires": {
|
||||||
|
"domelementtype": "1.3.0",
|
||||||
|
"domhandler": "2.3.0",
|
||||||
|
"domutils": "1.5.1",
|
||||||
|
"entities": "1.1.1",
|
||||||
|
"inherits": "2.0.3",
|
||||||
|
"readable-stream": "2.2.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"chokidar": {
|
"chokidar": {
|
||||||
"version": "1.7.0",
|
"version": "1.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz",
|
||||||
@ -3429,11 +3467,27 @@
|
|||||||
"resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.0.4.tgz",
|
||||||
"integrity": "sha1-OLBQP7+dqfVOnB29pg4UXHcRe90="
|
"integrity": "sha1-OLBQP7+dqfVOnB29pg4UXHcRe90="
|
||||||
},
|
},
|
||||||
|
"css-select": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
|
||||||
|
"requires": {
|
||||||
|
"boolbase": "1.0.0",
|
||||||
|
"css-what": "2.1.0",
|
||||||
|
"domutils": "1.5.1",
|
||||||
|
"nth-check": "1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"css-stringify": {
|
"css-stringify": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/css-stringify/-/css-stringify-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/css-stringify/-/css-stringify-1.0.5.tgz",
|
||||||
"integrity": "sha1-sNBClG2ylTu50pKQCmy19tASIDE="
|
"integrity": "sha1-sNBClG2ylTu50pKQCmy19tASIDE="
|
||||||
},
|
},
|
||||||
|
"css-what": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz",
|
||||||
|
"integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0="
|
||||||
|
},
|
||||||
"csurf": {
|
"csurf": {
|
||||||
"version": "1.9.0",
|
"version": "1.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/csurf/-/csurf-1.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/csurf/-/csurf-1.9.0.tgz",
|
||||||
@ -3733,6 +3787,11 @@
|
|||||||
"integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=",
|
"integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"discontinuous-range": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo="
|
||||||
|
},
|
||||||
"dns-prefetch-control": {
|
"dns-prefetch-control": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz",
|
||||||
@ -4112,6 +4171,46 @@
|
|||||||
"resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz",
|
||||||
"integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY="
|
"integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY="
|
||||||
},
|
},
|
||||||
|
"enzyme": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/enzyme/-/enzyme-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-l0HcjycivXjB4IXkwuRc1K5z8hzWIVZB2b/Y/H2bao9eFTpBz4ACOwAQf44SgG5Nu3d1jF41LasxDgFWZeeysA==",
|
||||||
|
"requires": {
|
||||||
|
"cheerio": "1.0.0-rc.2",
|
||||||
|
"function.prototype.name": "1.0.3",
|
||||||
|
"has": "1.0.1",
|
||||||
|
"is-subset": "0.1.1",
|
||||||
|
"lodash": "4.17.4",
|
||||||
|
"object-is": "1.0.1",
|
||||||
|
"object.assign": "4.0.4",
|
||||||
|
"object.entries": "1.0.4",
|
||||||
|
"object.values": "1.0.4",
|
||||||
|
"raf": "3.4.0",
|
||||||
|
"rst-selector-parser": "2.2.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enzyme-adapter-react-15": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/enzyme-adapter-react-15/-/enzyme-adapter-react-15-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-GxQ+ZYbo6YFwwpaLc9LLyAwsx+F1au628/+hwTx3XV2OiuvHGyWgC/r1AAK1HlDRjujzfwwMNZTc/JxkjIuYVg==",
|
||||||
|
"requires": {
|
||||||
|
"enzyme-adapter-utils": "1.2.0",
|
||||||
|
"lodash": "4.17.4",
|
||||||
|
"object.assign": "4.0.4",
|
||||||
|
"object.values": "1.0.4",
|
||||||
|
"prop-types": "15.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enzyme-adapter-utils": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/enzyme-adapter-utils/-/enzyme-adapter-utils-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-6CeIrmymLWoQgvH5m/ixJLaCsa6pSoWU2nlMeO0nHCZR8LQ+tKzP/jPh4qceTPlB4oFfyMRFeqr0+IryY4gAxg==",
|
||||||
|
"requires": {
|
||||||
|
"lodash": "4.17.4",
|
||||||
|
"object.assign": "4.0.4",
|
||||||
|
"prop-types": "15.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"errno": {
|
"errno": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.4.tgz",
|
||||||
@ -4147,7 +4246,6 @@
|
|||||||
"version": "1.9.0",
|
"version": "1.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.9.0.tgz",
|
||||||
"integrity": "sha512-kk3IJoKo7A3pWJc0OV8yZ/VEX2oSUytfekrJiqoxBlKJMFAJVJVpGdHClCCTdv+Fn2zHfpDHHIelMFhZVfef3Q==",
|
"integrity": "sha512-kk3IJoKo7A3pWJc0OV8yZ/VEX2oSUytfekrJiqoxBlKJMFAJVJVpGdHClCCTdv+Fn2zHfpDHHIelMFhZVfef3Q==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"es-to-primitive": "1.1.1",
|
"es-to-primitive": "1.1.1",
|
||||||
"function-bind": "1.1.1",
|
"function-bind": "1.1.1",
|
||||||
@ -4160,7 +4258,6 @@
|
|||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz",
|
||||||
"integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=",
|
"integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-callable": "1.1.3",
|
"is-callable": "1.1.3",
|
||||||
"is-date-object": "1.0.1",
|
"is-date-object": "1.0.1",
|
||||||
@ -6435,6 +6532,16 @@
|
|||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||||
},
|
},
|
||||||
|
"function.prototype.name": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-5EblxZUdioXi2JiMZ9FUbwYj40eQ9MFHyzFLBSPdlRl3SO8l7SLWuAnQ/at/1Wi4hjJwME/C5WpF2ZfAc8nGNw==",
|
||||||
|
"requires": {
|
||||||
|
"define-properties": "1.1.2",
|
||||||
|
"function-bind": "1.1.1",
|
||||||
|
"is-callable": "1.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"functional-red-black-tree": {
|
"functional-red-black-tree": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
|
||||||
@ -7534,7 +7641,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz",
|
||||||
"integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=",
|
"integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"function-bind": "1.1.1"
|
"function-bind": "1.1.1"
|
||||||
}
|
}
|
||||||
@ -8053,8 +8159,7 @@
|
|||||||
"is-callable": {
|
"is-callable": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz",
|
||||||
"integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=",
|
"integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"is-ci": {
|
"is-ci": {
|
||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
@ -8068,8 +8173,7 @@
|
|||||||
"is-date-object": {
|
"is-date-object": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
|
||||||
"integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
|
"integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"is-dotfile": {
|
"is-dotfile": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
@ -8274,7 +8378,6 @@
|
|||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
|
||||||
"integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
|
"integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"has": "1.0.1"
|
"has": "1.0.1"
|
||||||
}
|
}
|
||||||
@ -8312,11 +8415,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
||||||
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
|
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
|
||||||
},
|
},
|
||||||
|
"is-subset": {
|
||||||
|
"version": "0.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz",
|
||||||
|
"integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY="
|
||||||
|
},
|
||||||
"is-symbol": {
|
"is-symbol": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz",
|
||||||
"integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=",
|
"integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"is-typedarray": {
|
"is-typedarray": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@ -9462,6 +9569,11 @@
|
|||||||
"integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=",
|
"integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"lodash.flattendeep": {
|
||||||
|
"version": "4.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
|
||||||
|
"integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI="
|
||||||
|
},
|
||||||
"lodash.isarguments": {
|
"lodash.isarguments": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
|
||||||
@ -10854,6 +10966,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nearley": {
|
||||||
|
"version": "2.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/nearley/-/nearley-2.11.0.tgz",
|
||||||
|
"integrity": "sha512-clqqhEuP0ZCJQ85Xv2I/4o2Gs/fvSR6fCg5ZHVE2c8evWyNk2G++ih4JOO3lMb/k/09x6ihQ2nzKUlB/APCWjg==",
|
||||||
|
"requires": {
|
||||||
|
"nomnom": "1.6.2",
|
||||||
|
"railroad-diagrams": "1.0.0",
|
||||||
|
"randexp": "0.4.6"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"colors": {
|
||||||
|
"version": "0.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz",
|
||||||
|
"integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q="
|
||||||
|
},
|
||||||
|
"nomnom": {
|
||||||
|
"version": "1.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.6.2.tgz",
|
||||||
|
"integrity": "sha1-hKZqJgF0QI/Ft3oY+IjszET7aXE=",
|
||||||
|
"requires": {
|
||||||
|
"colors": "0.5.1",
|
||||||
|
"underscore": "1.4.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"underscore": {
|
||||||
|
"version": "1.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz",
|
||||||
|
"integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"needle": {
|
"needle": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/needle/-/needle-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/needle/-/needle-2.0.1.tgz",
|
||||||
@ -11398,6 +11541,14 @@
|
|||||||
"path-key": "2.0.1"
|
"path-key": "2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"nth-check": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=",
|
||||||
|
"requires": {
|
||||||
|
"boolbase": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"number-is-nan": {
|
"number-is-nan": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
|
||||||
@ -11430,6 +11581,11 @@
|
|||||||
"integrity": "sha512-OHHnLgLNXpM++GnJRyyhbr2bwl3pPVm4YvaraHrRvDt/N3r+s/gDVHciA7EJBTkijKXj61ssgSAikq1fb0IBRg==",
|
"integrity": "sha512-OHHnLgLNXpM++GnJRyyhbr2bwl3pPVm4YvaraHrRvDt/N3r+s/gDVHciA7EJBTkijKXj61ssgSAikq1fb0IBRg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"object-is": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY="
|
||||||
|
},
|
||||||
"object-keys": {
|
"object-keys": {
|
||||||
"version": "1.0.11",
|
"version": "1.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz",
|
||||||
@ -11463,6 +11619,17 @@
|
|||||||
"isobject": "3.0.1"
|
"isobject": "3.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"object.entries": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.0.4.tgz",
|
||||||
|
"integrity": "sha1-G/mk3SKI9bM/Opk9JXZh8F0WGl8=",
|
||||||
|
"requires": {
|
||||||
|
"define-properties": "1.1.2",
|
||||||
|
"es-abstract": "1.9.0",
|
||||||
|
"function-bind": "1.1.1",
|
||||||
|
"has": "1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"object.omit": {
|
"object.omit": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz",
|
||||||
@ -11493,6 +11660,17 @@
|
|||||||
"isobject": "3.0.1"
|
"isobject": "3.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"object.values": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/object.values/-/object.values-1.0.4.tgz",
|
||||||
|
"integrity": "sha1-5STaCbT2b/Bd9FdUbscqyZ8TBpo=",
|
||||||
|
"requires": {
|
||||||
|
"define-properties": "1.1.2",
|
||||||
|
"es-abstract": "1.9.0",
|
||||||
|
"function-bind": "1.1.1",
|
||||||
|
"has": "1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"omni-fetch": {
|
"omni-fetch": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/omni-fetch/-/omni-fetch-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/omni-fetch/-/omni-fetch-0.1.0.tgz",
|
||||||
@ -11854,6 +12032,14 @@
|
|||||||
"integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=",
|
"integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"parse5": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==",
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "8.0.47"
|
||||||
|
}
|
||||||
|
},
|
||||||
"parsejson": {
|
"parsejson": {
|
||||||
"version": "0.0.3",
|
"version": "0.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.3.tgz",
|
||||||
@ -12320,6 +12506,20 @@
|
|||||||
"performance-now": "2.1.0"
|
"performance-now": "2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"railroad-diagrams": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-635iZ1SN3t+4mcG5Dlc3RVnN234="
|
||||||
|
},
|
||||||
|
"randexp": {
|
||||||
|
"version": "0.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz",
|
||||||
|
"integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==",
|
||||||
|
"requires": {
|
||||||
|
"discontinuous-range": "1.0.0",
|
||||||
|
"ret": "0.1.15"
|
||||||
|
}
|
||||||
|
},
|
||||||
"random-bytes": {
|
"random-bytes": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
|
||||||
@ -12412,13 +12612,15 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"react": {
|
"react": {
|
||||||
"version": "15.4.2",
|
"version": "15.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-15.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-15.6.2.tgz",
|
||||||
"integrity": "sha1-QfeZGyYYU5K6m66WyIiefgGDl+8=",
|
"integrity": "sha1-26BDSrQ5z+gvEI8PURZjkIF5qnI=",
|
||||||
"requires": {
|
"requires": {
|
||||||
|
"create-react-class": "15.6.2",
|
||||||
"fbjs": "0.8.16",
|
"fbjs": "0.8.16",
|
||||||
"loose-envify": "1.3.1",
|
"loose-envify": "1.3.1",
|
||||||
"object-assign": "4.1.1"
|
"object-assign": "4.1.1",
|
||||||
|
"prop-types": "15.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-addons-css-transition-group": {
|
"react-addons-css-transition-group": {
|
||||||
@ -12474,13 +12676,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-dom": {
|
"react-dom": {
|
||||||
"version": "15.4.2",
|
"version": "15.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.2.tgz",
|
||||||
"integrity": "sha1-AVNj8FsKH9Uq6e/dOgBg2QaVII8=",
|
"integrity": "sha1-Qc+t9pO3V/rycIRDodH9WgK+9zA=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"fbjs": "0.8.16",
|
"fbjs": "0.8.16",
|
||||||
"loose-envify": "1.3.1",
|
"loose-envify": "1.3.1",
|
||||||
"object-assign": "4.1.1"
|
"object-assign": "4.1.1",
|
||||||
|
"prop-types": "15.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"react-fontawesome": {
|
"react-fontawesome": {
|
||||||
@ -12605,6 +12808,15 @@
|
|||||||
"prop-types": "15.6.0"
|
"prop-types": "15.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-test-renderer": {
|
||||||
|
"version": "15.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-15.6.2.tgz",
|
||||||
|
"integrity": "sha1-0DM0NPwsQ4CSaWyncNpe1IA376g=",
|
||||||
|
"requires": {
|
||||||
|
"fbjs": "0.8.16",
|
||||||
|
"object-assign": "4.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-transition-group": {
|
"react-transition-group": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-1.2.1.tgz",
|
||||||
@ -12795,9 +13007,9 @@
|
|||||||
"requires": {
|
"requires": {
|
||||||
"debug": "2.6.9",
|
"debug": "2.6.9",
|
||||||
"invariant": "2.2.2",
|
"invariant": "2.2.2",
|
||||||
"react": "15.4.2",
|
"react": "15.6.2",
|
||||||
"react-addons-shallow-compare": "15.4.2",
|
"react-addons-shallow-compare": "15.4.2",
|
||||||
"react-dom": "15.4.2",
|
"react-dom": "15.6.2",
|
||||||
"rx": "4.1.0",
|
"rx": "4.1.0",
|
||||||
"warning": "3.0.0"
|
"warning": "3.0.0"
|
||||||
},
|
},
|
||||||
@ -13141,6 +13353,11 @@
|
|||||||
"through": "2.3.8"
|
"through": "2.3.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ret": {
|
||||||
|
"version": "0.1.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
|
||||||
|
"integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="
|
||||||
|
},
|
||||||
"rev-del": {
|
"rev-del": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/rev-del/-/rev-del-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/rev-del/-/rev-del-1.0.5.tgz",
|
||||||
@ -13298,6 +13515,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz",
|
||||||
"integrity": "sha1-8z/pz7Urv9UgqhgyO8ZdsRCht2w="
|
"integrity": "sha1-8z/pz7Urv9UgqhgyO8ZdsRCht2w="
|
||||||
},
|
},
|
||||||
|
"rst-selector-parser": {
|
||||||
|
"version": "2.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz",
|
||||||
|
"integrity": "sha1-gbIw6i/MYGbInjRy3nlChdmwPZE=",
|
||||||
|
"requires": {
|
||||||
|
"lodash.flattendeep": "4.4.0",
|
||||||
|
"nearley": "2.11.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"run-async": {
|
"run-async": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
"babel-preset-es2015": "^6.3.13",
|
"babel-preset-es2015": "^6.3.13",
|
||||||
"babel-preset-react": "^6.3.13",
|
"babel-preset-react": "^6.3.13",
|
||||||
"babel-register": "^6.3.0",
|
"babel-register": "^6.3.0",
|
||||||
"berkeleys-redux-utils": "^3.2.0",
|
"berkeleys-redux-utils": "^4.0.0",
|
||||||
"body-parser": "^1.13.2",
|
"body-parser": "^1.13.2",
|
||||||
"bootstrap": "~3.3.7",
|
"bootstrap": "~3.3.7",
|
||||||
"cal-heatmap": "~3.5.2",
|
"cal-heatmap": "~3.5.2",
|
||||||
@ -54,6 +54,8 @@
|
|||||||
"dedent": "~0.7.0",
|
"dedent": "~0.7.0",
|
||||||
"dotenv": "^4.0.0",
|
"dotenv": "^4.0.0",
|
||||||
"emmet-codemirror": "^1.2.5",
|
"emmet-codemirror": "^1.2.5",
|
||||||
|
"enzyme": "^3.2.0",
|
||||||
|
"enzyme-adapter-react-15": "^1.0.5",
|
||||||
"errorhandler": "^1.4.2",
|
"errorhandler": "^1.4.2",
|
||||||
"es6-map": "~0.1.1",
|
"es6-map": "~0.1.1",
|
||||||
"express": "^4.13.3",
|
"express": "^4.13.3",
|
||||||
@ -102,12 +104,12 @@
|
|||||||
"passport-twitter": "^1.0.3",
|
"passport-twitter": "^1.0.3",
|
||||||
"pmx": "~0.6.2",
|
"pmx": "~0.6.2",
|
||||||
"prop-types": "^15.5.10",
|
"prop-types": "^15.5.10",
|
||||||
"react": "~15.4.2",
|
"react": "^15.6.2",
|
||||||
"react-addons-css-transition-group": "~15.4.2",
|
"react-addons-css-transition-group": "~15.4.2",
|
||||||
"react-addons-shallow-compare": "~15.4.2",
|
"react-addons-shallow-compare": "~15.4.2",
|
||||||
"react-bootstrap": "~0.31.2",
|
"react-bootstrap": "~0.31.2",
|
||||||
"react-codemirror": "^0.3.0",
|
"react-codemirror": "^0.3.0",
|
||||||
"react-dom": "~15.4.2",
|
"react-dom": "^15.6.2",
|
||||||
"react-fontawesome": "^1.2.0",
|
"react-fontawesome": "^1.2.0",
|
||||||
"react-images": "^0.5.1",
|
"react-images": "^0.5.1",
|
||||||
"react-motion": "~0.4.2",
|
"react-motion": "~0.4.2",
|
||||||
@ -115,6 +117,7 @@
|
|||||||
"react-notification": "git+https://github.com/BerkeleyTrue/react-notification.git#freecodecamp",
|
"react-notification": "git+https://github.com/BerkeleyTrue/react-notification.git#freecodecamp",
|
||||||
"react-pure-render": "^1.0.2",
|
"react-pure-render": "^1.0.2",
|
||||||
"react-redux": "^4.0.6",
|
"react-redux": "^4.0.6",
|
||||||
|
"react-test-renderer": "^15.6.2",
|
||||||
"react-youtube": "^7.0.0",
|
"react-youtube": "^7.0.0",
|
||||||
"redux": "^3.0.5",
|
"redux": "^3.0.5",
|
||||||
"redux-actions": "^2.0.3",
|
"redux-actions": "^2.0.3",
|
||||||
|
@ -3,6 +3,11 @@
|
|||||||
"order": 5,
|
"order": 5,
|
||||||
"time": "5 hours",
|
"time": "5 hours",
|
||||||
"helpRoom": "Help",
|
"helpRoom": "Help",
|
||||||
|
"required": [
|
||||||
|
{
|
||||||
|
"src": "https://cdnjs.cloudflare.com/ajax/libs/react/16.1.1/umd/react.development.js"
|
||||||
|
}
|
||||||
|
],
|
||||||
"challenges": [
|
"challenges": [
|
||||||
{
|
{
|
||||||
"id": "587d7dbc367417b2b2512bb1",
|
"id": "587d7dbc367417b2b2512bb1",
|
||||||
@ -17,7 +22,7 @@
|
|||||||
],
|
],
|
||||||
"files": {
|
"files": {
|
||||||
"indexjsx": {
|
"indexjsx": {
|
||||||
"key": "indexjxs",
|
"key": "indexjsx",
|
||||||
"ext": "jsx",
|
"ext": "jsx",
|
||||||
"name": "index",
|
"name": "index",
|
||||||
"contents": [
|
"contents": [
|
||||||
@ -28,11 +33,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tests": [
|
"tests": [
|
||||||
"assert(Enzyme.shallow(jsx).type === 'h1', 'message: The constant JSX should return an <code>h1</code> element.');",
|
"assert(Enzyme.shallow(jsx).type() === 'h1', 'message: The constant JSX should return an <code>h1</code> element.');",
|
||||||
"assert(Enzyme.shallow(jsx).children() === 'Hello JSX!', 'message: The <code>h1</code> tag should include the text <code>Hello JSX!</code>');"
|
"assert(Enzyme.shallow(jsx).contains( 'Hello JSX!'), 'message: The <code>h1</code> tag should include the text <code>Hello JSX!</code>');"
|
||||||
],
|
],
|
||||||
"type": "modern",
|
"type": "modern",
|
||||||
"isRequired": true,
|
"isRequired": false,
|
||||||
"translations": {}
|
"translations": {}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -23,6 +23,8 @@ var createChallenges =
|
|||||||
var Block = app.models.Block;
|
var Block = app.models.Block;
|
||||||
var destroyBlocks = Observable.fromNodeCallback(Block.destroyAll, Block);
|
var destroyBlocks = Observable.fromNodeCallback(Block.destroyAll, Block);
|
||||||
var createBlocks = Observable.fromNodeCallback(Block.create, Block);
|
var createBlocks = Observable.fromNodeCallback(Block.create, Block);
|
||||||
|
const arrToString = arr =>
|
||||||
|
Array.isArray(arr) ? arr.join('\n') : _.toString(arr);
|
||||||
|
|
||||||
Observable.combineLatest(
|
Observable.combineLatest(
|
||||||
destroyChallenges(),
|
destroyChallenges(),
|
||||||
@ -82,6 +84,17 @@ Observable.combineLatest(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (challenge.files) {
|
||||||
|
challenge.files = _.reduce(challenge.files, (map, file) => {
|
||||||
|
map[file.key] = {
|
||||||
|
...file,
|
||||||
|
head: arrToString(file.head),
|
||||||
|
contents: arrToString(file.contents),
|
||||||
|
tail: arrToString(file.tail)
|
||||||
|
};
|
||||||
|
return map;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
challenge.fileName = fileName;
|
challenge.fileName = fileName;
|
||||||
challenge.helpRoom = helpRoom;
|
challenge.helpRoom = helpRoom;
|
||||||
challenge.order = order;
|
challenge.order = order;
|
||||||
|
Reference in New Issue
Block a user