Feature(code-uri): Lock untrusted code from playing on first load
This commit is contained in:
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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$);
|
||||
|
@ -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 }/>
|
||||
|
@ -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'
|
||||
|
@ -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(
|
||||
|
@ -93,7 +93,7 @@ const mainReducer = handleActions(
|
||||
...state,
|
||||
isCodeLocked: true
|
||||
}),
|
||||
[types.unlockCode]: state => ({
|
||||
[types.unlockUntrustedCode]: state => ({
|
||||
...state,
|
||||
isCodeLocked: false
|
||||
}),
|
||||
|
@ -18,6 +18,7 @@ export default createTypes([
|
||||
'resetUi',
|
||||
'updateHint',
|
||||
'lockUntrustedCode',
|
||||
'unlockUntrustedCode',
|
||||
|
||||
// map
|
||||
'updateFilter',
|
||||
|
Reference in New Issue
Block a user