Files
freeCodeCamp/client/src/templates/Challenges/redux/index.js

361 lines
11 KiB
JavaScript
Raw Normal View History

import { isEmpty } from 'lodash-es';
import { createAction, handleActions } from 'redux-actions';
2018-09-21 17:30:16 +03:00
import { getLines } from '../../../../../utils/get-lines';
import { createPoly } from '../../../../../utils/polyvinyl';
import { challengeTypes } from '../../../../utils/challengeTypes';
import { completedChallengesSelector } from '../../../redux';
import { getTargetEditor } from '../utils/getTargetEditor';
import { actionTypes, ns } from './action-types';
import codeLockEpic from './code-lock-epic';
import codeStorageEpic from './code-storage-epic';
import completionEpic from './completion-epic';
import createQuestionEpic from './create-question-epic';
import { createCurrentChallengeSaga } from './current-challenge-saga';
import { createExecuteChallengeSaga } from './execute-challenge-saga';
export { ns };
2018-04-06 14:51:52 +01:00
const initialState = {
canFocusEditor: true,
visibleEditors: {},
2018-04-06 14:51:52 +01:00
challengeFiles: {},
challengeMeta: {
superBlock: '',
block: '',
2018-04-06 14:51:52 +01:00
id: '',
2019-02-08 17:33:05 +03:00
nextChallengePath: '/',
prevChallengePath: '/',
2019-02-08 17:33:05 +03:00
challengeType: -1
2018-04-06 14:51:52 +01:00
},
challengeTests: [],
consoleOut: [],
hasCompletedBlock: false,
isCodeLocked: false,
2019-02-08 17:33:05 +03:00
isBuildEnabled: true,
logsOut: [],
2018-04-06 14:51:52 +01:00
modal: {
completion: false,
help: false,
video: false,
reset: false
2018-04-06 14:51:52 +01:00
},
2018-09-27 14:19:03 +03:00
projectFormValues: {},
2018-04-06 14:51:52 +01:00
successMessage: 'Happy Coding!'
};
export const epics = [
codeLockEpic,
completionEpic,
createQuestionEpic,
codeStorageEpic
];
2018-11-26 02:17:38 +03:00
export const sagas = [
...createExecuteChallengeSaga(actionTypes),
...createCurrentChallengeSaga(actionTypes)
2018-11-26 02:17:38 +03:00
];
// TODO: can createPoly handle editable region, rather than separating it?
export const createFiles = createAction(
actionTypes.createFiles,
challengeFiles =>
Object.keys(challengeFiles)
.filter(key => challengeFiles[key])
.map(key => challengeFiles[key])
.reduce(
(challengeFiles, file) => ({
...challengeFiles,
[file.key]: {
...createPoly(file),
seed: file.contents.slice(),
editableContents: getLines(
file.contents,
file.editableRegionBoundaries
),
seedEditableRegionBoundaries: file.editableRegionBoundaries.slice()
}
}),
{}
)
2018-04-06 14:51:52 +01:00
);
export const createQuestion = createAction(actionTypes.createQuestion);
export const initTests = createAction(actionTypes.initTests);
export const updateTests = createAction(actionTypes.updateTests);
export const cancelTests = createAction(actionTypes.cancelTests);
2018-04-06 14:51:52 +01:00
export const initConsole = createAction(actionTypes.initConsole);
export const initLogs = createAction(actionTypes.initLogs);
export const updateChallengeMeta = createAction(
actionTypes.updateChallengeMeta
);
export const updateFile = createAction(actionTypes.updateFile);
export const updateConsole = createAction(actionTypes.updateConsole);
export const updateLogs = createAction(actionTypes.updateLogs);
export const updateJSEnabled = createAction(actionTypes.updateJSEnabled);
export const updateSolutionFormValues = createAction(
actionTypes.updateSolutionFormValues
);
export const updateSuccessMessage = createAction(
actionTypes.updateSuccessMessage
2018-05-24 19:45:38 +01:00
);
2018-04-06 14:51:52 +01:00
export const logsToConsole = createAction(actionTypes.logsToConsole);
export const lockCode = createAction(actionTypes.lockCode);
export const unlockCode = createAction(actionTypes.unlockCode);
export const disableBuildOnError = createAction(
actionTypes.disableBuildOnError
);
export const storedCodeFound = createAction(actionTypes.storedCodeFound);
export const noStoredCodeFound = createAction(actionTypes.noStoredCodeFound);
export const saveEditorContent = createAction(actionTypes.saveEditorContent);
2018-04-06 14:51:52 +01:00
export const closeModal = createAction(actionTypes.closeModal);
export const openModal = createAction(actionTypes.openModal);
2018-04-06 14:51:52 +01:00
export const previewMounted = createAction(actionTypes.previewMounted);
export const challengeMounted = createAction(actionTypes.challengeMounted);
export const checkChallenge = createAction(actionTypes.checkChallenge);
export const executeChallenge = createAction(actionTypes.executeChallenge);
export const resetChallenge = createAction(actionTypes.resetChallenge);
export const submitChallenge = createAction(actionTypes.submitChallenge);
2018-04-06 14:51:52 +01:00
export const moveToTab = createAction(actionTypes.moveToTab);
export const setEditorFocusability = createAction(
actionTypes.setEditorFocusability
);
export const toggleVisibleEditor = createAction(
actionTypes.toggleVisibleEditor
);
export const currentTabSelector = state => state[ns].currentTab;
2018-04-06 14:51:52 +01:00
export const challengeFilesSelector = state => state[ns].challengeFiles;
export const challengeMetaSelector = state => state[ns].challengeMeta;
export const challengeTestsSelector = state => state[ns].challengeTests;
export const consoleOutputSelector = state => state[ns].consoleOut;
export const completedChallengesIds = state =>
completedChallengesSelector(state).map(node => node.id);
export const isChallengeCompletedSelector = state => {
const completedChallenges = completedChallengesSelector(state);
const { id: currentChallengeId } = challengeMetaSelector(state);
return completedChallenges.some(({ id }) => id === currentChallengeId);
};
export const isCodeLockedSelector = state => state[ns].isCodeLocked;
2018-04-06 14:51:52 +01:00
export const isCompletionModalOpenSelector = state =>
2018-04-11 14:51:47 +01:00
state[ns].modal.completion;
export const isHelpModalOpenSelector = state => state[ns].modal.help;
export const isVideoModalOpenSelector = state => state[ns].modal.video;
export const isResetModalOpenSelector = state => state[ns].modal.reset;
2019-02-08 17:33:05 +03:00
export const isBuildEnabledSelector = state => state[ns].isBuildEnabled;
2018-04-06 14:51:52 +01:00
export const successMessageSelector = state => state[ns].successMessage;
2018-09-27 14:19:03 +03:00
export const projectFormValuesSelector = state =>
state[ns].projectFormValues || {};
2018-05-24 19:45:38 +01:00
2019-02-08 17:33:05 +03:00
export const challengeDataSelector = state => {
const { challengeType } = challengeMetaSelector(state);
let challengeData = { challengeType };
if (
challengeType === challengeTypes.js ||
challengeType === challengeTypes.bonfire
) {
challengeData = {
...challengeData,
files: challengeFilesSelector(state)
};
} else if (challengeType === challengeTypes.backend) {
const { solution: url = {} } = projectFormValuesSelector(state);
2019-02-08 17:33:05 +03:00
challengeData = {
...challengeData,
url
};
} else if (
challengeType === challengeTypes.backEndProject ||
challengeType === challengeTypes.pythonProject
) {
const values = projectFormValuesSelector(state);
const { solution: url } = values;
challengeData = {
...challengeData,
...values,
url
};
} else if (challengeType === challengeTypes.frontEndProject) {
2019-02-08 17:33:05 +03:00
challengeData = {
...challengeData,
...projectFormValuesSelector(state)
};
} else if (
challengeType === challengeTypes.html ||
challengeType === challengeTypes.modern
) {
const { required = [], template = '' } = challengeMetaSelector(state);
challengeData = {
...challengeData,
files: challengeFilesSelector(state),
required,
template
};
}
return challengeData;
};
export const canFocusEditorSelector = state => state[ns].canFocusEditor;
export const visibleEditorsSelector = state => state[ns].visibleEditors;
2018-04-06 14:51:52 +01:00
export const reducer = handleActions(
{
[actionTypes.createFiles]: (state, { payload }) => ({
2018-04-06 14:51:52 +01:00
...state,
challengeFiles: payload,
visibleEditors: { [getTargetEditor(payload)]: true }
2018-04-06 14:51:52 +01:00
}),
[actionTypes.updateFile]: (
state,
{ payload: { key, editorValue, editableRegionBoundaries } }
) => ({
...state,
challengeFiles: {
...state.challengeFiles,
[key]: {
...state.challengeFiles[key],
contents: editorValue,
editableContents: getLines(editorValue, editableRegionBoundaries),
editableRegionBoundaries
}
}
}),
[actionTypes.storedCodeFound]: (state, { payload }) => ({
...state,
challengeFiles: payload
}),
[actionTypes.initTests]: (state, { payload }) => ({
2018-04-06 14:51:52 +01:00
...state,
challengeTests: payload
}),
[actionTypes.updateTests]: (state, { payload }) => ({
2018-04-06 14:51:52 +01:00
...state,
challengeTests: payload
}),
[actionTypes.initConsole]: (state, { payload }) => ({
2018-04-06 14:51:52 +01:00
...state,
consoleOut: payload ? [payload] : []
2018-04-06 14:51:52 +01:00
}),
[actionTypes.updateConsole]: (state, { payload }) => ({
2018-04-06 14:51:52 +01:00
...state,
consoleOut: state.consoleOut.concat(payload)
2018-04-06 14:51:52 +01:00
}),
[actionTypes.initLogs]: state => ({
...state,
logsOut: []
}),
[actionTypes.updateLogs]: (state, { payload }) => ({
...state,
logsOut: state.logsOut.concat(payload)
}),
[actionTypes.logsToConsole]: (state, { payload }) => ({
...state,
consoleOut: isEmpty(state.logsOut)
? state.consoleOut
: state.consoleOut.concat(payload, state.logsOut)
}),
[actionTypes.updateChallengeMeta]: (state, { payload }) => ({
...state,
challengeMeta: { ...payload }
}),
[actionTypes.resetChallenge]: state => ({
2018-04-06 14:51:52 +01:00
...state,
currentTab: 2,
2018-04-06 14:51:52 +01:00
challengeFiles: {
...Object.keys(state.challengeFiles)
.map(key => state.challengeFiles[key])
.reduce(
(files, file) => ({
...files,
[file.key]: {
...file,
contents: file.seed.slice(),
editableContents: getLines(
file.seed,
file.seedEditableRegionBoundaries
),
editableRegionBoundaries: file.seedEditableRegionBoundaries
}
}),
{}
)
},
challengeTests: state.challengeTests.map(({ text, testString }) => ({
text,
testString
})),
consoleOut: []
2018-04-06 14:51:52 +01:00
}),
[actionTypes.updateSolutionFormValues]: (state, { payload }) => ({
2018-05-24 19:45:38 +01:00
...state,
2018-09-27 14:19:03 +03:00
projectFormValues: payload
2018-05-24 19:45:38 +01:00
}),
[actionTypes.lockCode]: state => ({
...state,
isCodeLocked: true
}),
[actionTypes.unlockCode]: state => ({
...state,
2019-02-08 17:33:05 +03:00
isBuildEnabled: true,
isCodeLocked: false
}),
[actionTypes.disableBuildOnError]: state => ({
2018-04-06 14:51:52 +01:00
...state,
2019-02-08 17:33:05 +03:00
isBuildEnabled: false
2018-04-06 14:51:52 +01:00
}),
[actionTypes.updateSuccessMessage]: (state, { payload }) => ({
2018-04-06 14:51:52 +01:00
...state,
successMessage: payload
}),
[actionTypes.closeModal]: (state, { payload }) => ({
2018-04-06 14:51:52 +01:00
...state,
modal: {
...state.modal,
[payload]: false
}
}),
[actionTypes.openModal]: (state, { payload }) => ({
2018-04-06 14:51:52 +01:00
...state,
modal: {
...state.modal,
[payload]: true
}
}),
[actionTypes.moveToTab]: (state, { payload }) => ({
...state,
currentTab: payload
}),
[actionTypes.executeChallenge]: state => ({
...state,
currentTab: 3
}),
[actionTypes.setEditorFocusability]: (state, { payload }) => ({
...state,
canFocusEditor: payload
}),
[actionTypes.toggleVisibleEditor]: (state, { payload }) => {
return {
...state,
visibleEditors: {
...state.visibleEditors,
[payload]: !state.visibleEditors[payload]
}
};
}
2018-04-06 14:51:52 +01:00
},
initialState
);