This commit applies changes from 78e86f514f
.
It introduces Help dialog which guides the user to review Read-Search-Ask
methodology, ask the question on the forums or in gitter chatroom.
This dialog replaces existing Help button which was leading to Gitter Chatroom.
347 lines
9.4 KiB
JavaScript
347 lines
9.4 KiB
JavaScript
import _ from 'lodash';
|
|
import {
|
|
combineActions,
|
|
combineReducers,
|
|
createAction,
|
|
createAsyncTypes,
|
|
createTypes,
|
|
handleActions
|
|
} from 'berkeleys-redux-utils';
|
|
import { createSelector } from 'reselect';
|
|
import noop from 'lodash/noop';
|
|
|
|
import modalEpic from './modal-epic';
|
|
import completionEpic from './completion-epic.js';
|
|
import challengeEpic from './challenge-epic.js';
|
|
import executeChallengeEpic from './execute-challenge-epic.js';
|
|
import codeStorageEpic from './code-storage-epic.js';
|
|
|
|
import ns from '../ns.json';
|
|
import stepReducer, { epics as stepEpics } from '../views/step/redux';
|
|
import quizReducer from '../views/quiz/redux';
|
|
import projectReducer from '../views/project/redux';
|
|
|
|
import {
|
|
createTests,
|
|
loggerToStr,
|
|
submitTypes,
|
|
viewTypes,
|
|
getFileKey,
|
|
challengeToFiles
|
|
} from '../utils';
|
|
import {
|
|
types as app,
|
|
challengeSelector
|
|
} from '../../../redux';
|
|
import { html } from '../../../utils/challengeTypes.js';
|
|
import blockNameify from '../../../utils/blockNameify.js';
|
|
import { updateFileMetaCreator, createFilesMetaCreator } from '../../../files';
|
|
|
|
// this is not great but is ok until we move to a different form type
|
|
export projectNormalizer from '../views/project/redux';
|
|
|
|
const challengeToFilesMetaCreator =
|
|
_.flow(challengeToFiles, createFilesMetaCreator);
|
|
|
|
export const epics = [
|
|
modalEpic,
|
|
challengeEpic,
|
|
codeStorageEpic,
|
|
completionEpic,
|
|
executeChallengeEpic,
|
|
...stepEpics
|
|
];
|
|
|
|
export const types = createTypes([
|
|
'onRouteChallengeRoot',
|
|
'onRouteChallenges',
|
|
'onRouteCurrentChallenge',
|
|
// challenges
|
|
// |- classic
|
|
'classicEditorUpdated',
|
|
'challengeUpdated',
|
|
'clickOnReset',
|
|
'updateHint',
|
|
'unlockUntrustedCode',
|
|
'closeChallengeModal',
|
|
'updateSuccessMessage',
|
|
// |- modern
|
|
'modernEditorUpdated',
|
|
|
|
// rechallenge
|
|
'executeChallenge',
|
|
'updateOutput',
|
|
'initOutput',
|
|
'updateTests',
|
|
'checkChallenge',
|
|
createAsyncTypes('submitChallenge'),
|
|
'moveToNextChallenge',
|
|
|
|
// bug
|
|
'openBugModal',
|
|
'closeBugModal',
|
|
'openIssueSearch',
|
|
'createIssue',
|
|
|
|
// help
|
|
'openHelpModal',
|
|
'closeHelpModal',
|
|
'createQuestion',
|
|
'openHelpChatRoom',
|
|
|
|
// panes
|
|
'toggleClassicEditor',
|
|
'toggleMain',
|
|
'toggleMap',
|
|
'togglePreview',
|
|
'toggleSidePanel',
|
|
'toggleStep',
|
|
'toggleModernEditor',
|
|
|
|
// code storage
|
|
'storedCodeFound',
|
|
'noStoredCodeFound',
|
|
'previousSolutionFound'
|
|
], ns);
|
|
|
|
// routes
|
|
export const onRouteChallenges = createAction(types.onRouteChallenges);
|
|
export const onRouteCurrentChallenge =
|
|
createAction(types.onRouteCurrentChallenge);
|
|
|
|
// classic
|
|
export const classicEditorUpdated = createAction(
|
|
types.classicEditorUpdated,
|
|
null,
|
|
updateFileMetaCreator
|
|
);
|
|
// modern
|
|
export const modernEditorUpdated = createAction(
|
|
types.modernEditorUpdated,
|
|
null,
|
|
updateFileMetaCreator
|
|
);
|
|
// challenges
|
|
export const closeChallengeModal = createAction(types.closeChallengeModal);
|
|
export const updateHint = createAction(types.updateHint);
|
|
export const unlockUntrustedCode = createAction(
|
|
types.unlockUntrustedCode,
|
|
_.noop
|
|
);
|
|
export const updateSuccessMessage = createAction(types.updateSuccessMessage);
|
|
export const challengeUpdated = createAction(
|
|
types.challengeUpdated,
|
|
challenge => ({ challenge }),
|
|
challengeToFilesMetaCreator
|
|
);
|
|
export const clickOnReset = createAction(types.clickOnReset);
|
|
|
|
// rechallenge
|
|
export const executeChallenge = createAction(
|
|
types.executeChallenge,
|
|
noop,
|
|
);
|
|
|
|
export const updateTests = createAction(types.updateTests);
|
|
|
|
export const initOutput = createAction(types.initOutput, loggerToStr);
|
|
export const updateOutput = createAction(types.updateOutput, loggerToStr);
|
|
|
|
export const checkChallenge = createAction(types.checkChallenge);
|
|
|
|
export const submitChallenge = createAction(types.submitChallenge);
|
|
export const submitChallengeComplete = createAction(
|
|
types.submitChallenge.complete,
|
|
(username, points, challengeInfo) => ({ username, points, challengeInfo })
|
|
);
|
|
|
|
export const moveToNextChallenge = createAction(types.moveToNextChallenge);
|
|
|
|
// bug
|
|
export const openBugModal = createAction(types.openBugModal);
|
|
export const closeBugModal = createAction(types.closeBugModal);
|
|
export const openIssueSearch = createAction(types.openIssueSearch);
|
|
export const createIssue = createAction(types.createIssue);
|
|
|
|
// help
|
|
export const openHelpModal = createAction(types.openHelpModal);
|
|
export const closeHelpModal = createAction(types.closeHelpModal);
|
|
export const createQuestion = createAction(types.createQuestion);
|
|
export const openHelpChatRoom = createAction(types.openHelpChatRoom);
|
|
|
|
// code storage
|
|
export const storedCodeFound = createAction(
|
|
types.storedCodeFound,
|
|
null,
|
|
challengeToFilesMetaCreator,
|
|
);
|
|
export const noStoredCodeFound = createAction(types.noStoredCodeFound);
|
|
export const previousSolutionFound = createAction(
|
|
types.previousSolutionFound,
|
|
null,
|
|
challengeToFilesMetaCreator
|
|
);
|
|
|
|
const initialUiState = {
|
|
output: null,
|
|
isChallengeModalOpen: false,
|
|
isBugOpen: false,
|
|
isHelpOpen: false,
|
|
successMessage: 'Happy Coding!'
|
|
};
|
|
|
|
const initialState = {
|
|
isCodeLocked: false,
|
|
isJSEnabled: true,
|
|
id: '',
|
|
challenge: '',
|
|
helpChatRoom: 'Help',
|
|
// old code storage key
|
|
legacyKey: '',
|
|
// map
|
|
superBlocks: [],
|
|
// misc
|
|
...initialUiState
|
|
};
|
|
|
|
export const getNS = state => state[ns];
|
|
export const keySelector = state => getNS(state).key;
|
|
export const testsSelector = state => getNS(state).tests;
|
|
|
|
export const outputSelector = state => getNS(state).output;
|
|
export const successMessageSelector = state => getNS(state).successMessage;
|
|
export const hintIndexSelector = state => getNS(state).hintIndex;
|
|
export const codeLockedSelector = state => getNS(state).isCodeLocked;
|
|
export const isCodeLockedSelector = state => getNS(state).isCodeLocked;
|
|
export const isJSEnabledSelector = state => getNS(state).isJSEnabled;
|
|
export const chatRoomSelector = state => getNS(state).helpChatRoom;
|
|
export const challengeModalSelector =
|
|
state => getNS(state).isChallengeModalOpen;
|
|
|
|
export const bugModalSelector = state => getNS(state).isBugOpen;
|
|
export const helpModalSelector = state => getNS(state).isHelpOpen;
|
|
|
|
export const challengeRequiredSelector = state =>
|
|
challengeSelector(state).required || [];
|
|
export const challengeMetaSelector = createSelector(
|
|
// use closure to get around circular deps
|
|
(...args) => challengeSelector(...args),
|
|
challenge => {
|
|
if (!challenge.id) {
|
|
return {};
|
|
}
|
|
const challengeType = challenge && challenge.challengeType;
|
|
const type = challenge && challenge.type;
|
|
const viewType = viewTypes[type] || viewTypes[challengeType] || 'classic';
|
|
const blockName = blockNameify(challenge.block);
|
|
const title = blockName && challenge.title ?
|
|
`${blockName}: ${challenge.title}` :
|
|
challenge.title;
|
|
|
|
return {
|
|
type,
|
|
title,
|
|
viewType,
|
|
submitType:
|
|
submitTypes[challengeType] ||
|
|
submitTypes[challenge && challenge.type] ||
|
|
'tests',
|
|
showPreview: (
|
|
challengeType === html ||
|
|
type === 'modern'
|
|
),
|
|
mode: challenge && challengeType === html ?
|
|
'text/html' :
|
|
'javascript'
|
|
};
|
|
}
|
|
);
|
|
|
|
export const showPreviewSelector = state =>
|
|
!!challengeMetaSelector(state).showPreview;
|
|
export const challengeTypeSelector = state =>
|
|
challengeMetaSelector(state).type || '';
|
|
export const challengeTemplateSelector = state =>
|
|
challengeSelector(state).template || null;
|
|
|
|
export default combineReducers(
|
|
handleActions(
|
|
() => ({
|
|
[
|
|
combineActions(
|
|
types.challengeUpdated,
|
|
app.fetchChallenge.complete
|
|
)
|
|
]: (state, { payload: { challenge } }) => {
|
|
return {
|
|
...state,
|
|
...initialUiState,
|
|
id: challenge.id,
|
|
challenge: challenge.dashedName,
|
|
key: getFileKey(challenge),
|
|
tests: createTests(challenge),
|
|
helpChatRoom: challenge.helpRoom || 'Help'
|
|
};
|
|
},
|
|
[types.updateTests]: (state, { payload: tests }) => ({
|
|
...state,
|
|
tests,
|
|
isChallengeModalOpen: (
|
|
tests.length > 0 &&
|
|
tests.every(test => test.pass && !test.err)
|
|
)
|
|
}),
|
|
[types.closeChallengeModal]: state => ({
|
|
...state,
|
|
isChallengeModalOpen: false
|
|
}),
|
|
[types.updateSuccessMessage]: (state, { payload }) => ({
|
|
...state,
|
|
successMessage: payload
|
|
}),
|
|
[types.storedCodeFound]: state => ({
|
|
...state,
|
|
isJSEnabled: false,
|
|
isCodeLocked: true
|
|
}),
|
|
[types.unlockUntrustedCode]: state => ({
|
|
...state,
|
|
isCodeLocked: false
|
|
}),
|
|
[types.executeChallenge]: state => ({
|
|
...state,
|
|
isJSEnabled: true,
|
|
tests: state.tests.map(test => ({ ...test, err: false, pass: false }))
|
|
}),
|
|
[
|
|
combineActions(
|
|
types.classicEditorUpdated,
|
|
types.modernEditorUpdated
|
|
)
|
|
]: state => ({
|
|
...state,
|
|
isJSEnabled: false
|
|
}),
|
|
|
|
// classic/modern
|
|
[types.initOutput]: (state, { payload: output }) => ({
|
|
...state,
|
|
output
|
|
}),
|
|
[types.updateOutput]: (state, { payload: output }) => ({
|
|
...state,
|
|
output: (state.output || '') + output
|
|
}),
|
|
[types.openBugModal]: state => ({ ...state, isBugOpen: true }),
|
|
[types.closeBugModal]: state => ({ ...state, isBugOpen: false }),
|
|
[types.openHelpModal]: state => ({ ...state, isHelpOpen: true }),
|
|
[types.closeHelpModal]: state => ({ ...state, isHelpOpen: false })
|
|
}),
|
|
initialState,
|
|
ns
|
|
),
|
|
stepReducer,
|
|
quizReducer,
|
|
projectReducer
|
|
);
|