freeCodeCamp/client/sagas/execute-challenge-saga.js

116 lines
3.5 KiB
JavaScript
Raw Normal View History

import { Scheduler, Observable } from 'rx';
2016-05-20 19:42:26 +00:00
import { ajax$ } from '../../common/utils/ajax-stream';
2016-05-24 19:01:50 +00:00
import throwers from '../rechallenge/throwers';
import transformers from '../rechallenge/transformers';
2016-05-20 19:42:26 +00:00
import types from '../../common/app/routes/challenges/redux/types';
import {
frameMain,
frameTests,
frameOutput
2016-05-20 19:42:26 +00:00
} from '../../common/app/routes/challenges/redux/actions';
import { setExt, updateContents } from '../../common/utils/polyvinyl';
// createFileStream(files: Dictionary[Path, PolyVinyl]) =>
// Observable[...Observable[...PolyVinyl]]
function createFileStream(files = {}) {
return Observable.just(
Observable.from(Object.keys(files)).map(key => files[key])
);
}
const jQuery = {
src: '/bower_components/jquery/dist/jquery.js',
script: true,
type: 'global'
};
const scriptCache = new Map();
function cacheScript({ src } = {}) {
if (!src) {
return Observable.throw(new Error('No source provided for script'));
}
if (scriptCache.has(src)) {
return scriptCache.get(src);
}
const script$ = ajax$(src)
.doOnNext(res => {
if (res.status !== 200) {
throw new Error('Request errror: ' + res.status);
}
})
.map(({ response }) => response)
.map(script => `<script>${script}</script>`)
.catch(e => (console.error(e), Observable.just('')))
.shareReplay();
scriptCache.set(src, script$);
return script$;
}
const frameRunner$ = cacheScript({ src: '/js/frame-runner.js' });
const htmlCatch = '\n<!-- -->';
const jsCatch = '\n;/* */';
export default function executeChallengeSaga(action$, getState) {
return action$
.filter(({ type }) => (
type === types.executeChallenge ||
type === types.updateMain
))
2016-05-20 19:42:26 +00:00
.debounce(750)
.flatMapLatest(({ type }) => {
2016-05-20 19:42:26 +00:00
const { files, required = [ jQuery ] } = getState().challengesApp;
return createFileStream(files)
::throwers()
::transformers()
// createbuild
.flatMap(file$ => file$.reduce((build, file) => {
let finalFile;
if (file.ext === 'js') {
finalFile = setExt('html', updateContents(
`<script>${file.contents}${jsCatch}</script>`,
file
));
} else if (file.ext === 'css') {
finalFile = setExt('html', updateContents(
`<style>${file.contents}</style>`,
file
));
} else {
finalFile = file;
}
return build + finalFile.contents + htmlCatch;
}, ''))
// add required scripts and links here
.flatMap(source => {
const head$ = Observable.from(required)
2016-05-20 19:42:26 +00:00
.flatMap(required => {
if (required.script) {
return cacheScript(required);
}
return Observable.just('');
})
.reduce((head, required) => head + required, '')
.map(head => `<head>${head}</head>`);
return Observable.combineLatest(head$, frameRunner$)
.map(([ head, frameRunner ]) => {
return head + `<body>${source}</body>` + frameRunner;
})
.map(build => ({ source, build }));
2016-05-20 19:42:26 +00:00
})
.flatMap(payload => {
const actions = [];
actions.push(frameMain(payload));
if (type !== types.updateMain) {
actions.push(frameTests(payload));
actions.push(frameOutput(payload));
}
return Observable.from(actions, null, null, Scheduler.default);
});
2016-05-20 19:42:26 +00:00
});
}