Feature(code-uri): Lock untrusted code from playing on first load

This commit is contained in:
Berkeley Martinez
2016-08-16 15:59:23 -07:00
parent fb9c1001b0
commit f9cf212fe7
8 changed files with 75 additions and 26 deletions

View File

@ -53,6 +53,8 @@ function legacyToFile(code, files, key) {
export function saveCodeSaga(actions, getState) {
return actions
::ofType(types.saveCode)
// do not save challenge if code is locked
.filter(() => !getState().challengesApp.isCodeLocked)
.map(() => {
const { challengesApp: { id = '', files = {} } } = getState();
store.set(id, files);

View File

@ -91,6 +91,8 @@ export default function executeChallengeSaga(action$, getState) {
type === types.executeChallenge ||
type === types.updateMain
))
// if isCodeLockedTrue do not run challenges
.filter(() => !getState().challengesApp.isCodeLocked)
.debounce(750)
.flatMapLatest(({ type }) => {
const state = getState();

View File

@ -2,6 +2,7 @@ import Rx, { Observable, Subject } from 'rx';
/* eslint-disable import/no-unresolved */
import loopProtect from 'loop-protect';
/* eslint-enable import/no-unresolved */
import { ofType } from '../../common/utils/get-actions-of-type';
import types from '../../common/app/routes/challenges/redux/types';
import {
updateOutput,
@ -89,12 +90,13 @@ export default function frameSaga(actions$, getState, { window, document }) {
const proxyLogger$ = new Subject();
const runTests$ = window.__common[testId + 'Ready$'] =
new Subject();
const result$ = actions$
.filter(({ type }) => (
type === types.frameMain ||
type === types.frameTests ||
type === types.frameOutput
))
const result$ = actions$::ofType(
types.frameMain,
types.frameTests,
types.frameOutput
)
// if isCodeLocked is true do not frame user code
.filter(() => !getState().challengesApp.isCodeLocked)
.map(action => {
if (action.type === types.frameMain) {
return frameMain(action.payload, document, proxyLogger$);

View File

@ -12,7 +12,8 @@ import { challengeSelector } from '../../redux/selectors';
import {
openBugModal,
updateHint,
executeChallenge
executeChallenge,
unlockUntrustedCode
} from '../../redux/actions';
import { makeToast } from '../../../../toasts/redux/actions';
import { toggleHelpChat } from '../../../../redux/actions';
@ -22,7 +23,8 @@ const bindableActions = {
executeChallenge,
updateHint,
toggleHelpChat,
openBugModal
openBugModal,
unlockUntrustedCode
};
const mapStateToProps = createSelector(
challengeSelector,
@ -31,20 +33,23 @@ const mapStateToProps = createSelector(
state => state.challengesApp.tests,
state => state.challengesApp.output,
state => state.challengesApp.hintIndex,
state => state.challengesApp.isCodeLocked,
(
{ challenge: { title, description, hints = [] } = {} },
windowHeight,
navHeight,
tests,
output,
hintIndex
hintIndex,
isCodeLocked
) => ({
title,
description,
height: windowHeight - navHeight - 20,
tests,
output,
hint: hints[hintIndex]
hint: hints[hintIndex],
isCodeLocked
})
);
@ -65,7 +70,8 @@ export class SidePanel extends PureComponent {
updateHint: PropTypes.func,
makeToast: PropTypes.func,
toggleHelpChat: PropTypes.func,
openBugModal: PropTypes.func
openBugModal: PropTypes.func,
unlockUntrustedCode: PropTypes.func
};
renderDescription(description = [ 'Happy Coding!' ], descriptionRegex) {
@ -106,7 +112,9 @@ export class SidePanel extends PureComponent {
updateHint,
makeToast,
toggleHelpChat,
openBugModal
openBugModal,
isCodeLocked,
unlockUntrustedCode
} = this.props;
const style = {};
if (height) {
@ -135,9 +143,11 @@ export class SidePanel extends PureComponent {
<ToolPanel
executeChallenge={ executeChallenge }
hint={ hint }
isCodeLocked={ isCodeLocked }
makeToast={ makeToast }
openBugModal={ openBugModal }
toggleHelpChat={ toggleHelpChat }
unlockUntrustedCode={ unlockUntrustedCode }
updateHint={ updateHint }
/>
<Output output={ output }/>

View File

@ -14,8 +14,10 @@ export default class ToolPanel extends PureComponent {
executeChallenge: PropTypes.func,
updateHint: PropTypes.func,
hint: PropTypes.string,
isCodeLocked: PropTypes.bool,
toggleHelpChat: PropTypes.func,
openBugModal: PropTypes.func
openBugModal: PropTypes.func,
unlockUntrustedCode: PropTypes.func.isRequired
};
makeHint() {
@ -51,24 +53,50 @@ export default class ToolPanel extends PureComponent {
);
}
render() {
const {
hint,
executeChallenge,
toggleHelpChat,
openBugModal
} = this.props;
return (
<div>
{ this.renderHint(hint, this.makeHint) }
renderExecute(isCodeLocked, executeChallenge, unlockUntrustedCode) {
if (isCodeLocked) {
return (
<Button
block={ true }
bsStyle='primary'
className='btn-big'
onClick={ executeChallenge }
onClick={ unlockUntrustedCode }
>
Run tests (ctrl + enter)
Code Locked. Unlock?
</Button>
);
}
return (
<Button
block={ true }
bsStyle='primary'
className='btn-big'
onClick={ executeChallenge }
>
Run tests (ctrl + enter)
</Button>
);
}
render() {
const {
hint,
isCodeLocked,
executeChallenge,
toggleHelpChat,
openBugModal,
unlockUntrustedCode
} = this.props;
return (
<div>
{ this.renderHint(hint, this.makeHint) }
{
this.renderExecute(
isCodeLocked,
executeChallenge,
unlockUntrustedCode
)
}
<div className='button-spacer' />
<ButtonGroup
className='input-group'

View File

@ -23,6 +23,10 @@ export const fetchChallengeCompleted = createAction(
export const resetUi = createAction(types.resetUi);
export const updateHint = createAction(types.updateHint);
export const lockUntrustedCode = createAction(types.lockUntrustedCode);
export const unlockUntrustedCode = createAction(
types.unlockUntrustedCode,
() => null
);
export const fetchChallenges = createAction(types.fetchChallenges);
export const fetchChallengesCompleted = createAction(

View File

@ -93,7 +93,7 @@ const mainReducer = handleActions(
...state,
isCodeLocked: true
}),
[types.unlockCode]: state => ({
[types.unlockUntrustedCode]: state => ({
...state,
isCodeLocked: false
}),

View File

@ -18,6 +18,7 @@ export default createTypes([
'resetUi',
'updateHint',
'lockUntrustedCode',
'unlockUntrustedCode',
// map
'updateFilter',