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

366 lines
10 KiB
JavaScript
Raw Normal View History

2018-04-06 14:51:52 +01:00
import { createAction, handleActions } from 'redux-actions';
2018-09-21 17:30:16 +03:00
2018-09-27 14:19:03 +03:00
import { createTypes } from '../../../../utils/stateManagement';
2018-04-06 14:51:52 +01:00
import { createPoly } from '../utils/polyvinyl';
import challengeModalEpic from './challenge-modal-epic';
import completionEpic from './completion-epic';
import codeLockEpic from './code-lock-epic';
import createQuestionEpic from './create-question-epic';
import codeStorageEpic from './code-storage-epic';
2018-04-06 14:51:52 +01:00
2018-11-26 02:17:38 +03:00
import { createExecuteChallengeSaga } from './execute-challenge-saga';
import { createCurrentChallengeSaga } from './current-challenge-saga';
2019-02-08 17:33:05 +03:00
import { challengeTypes } from '../../../../utils/challengeTypes';
import { completedChallengesSelector } from '../../../redux';
2018-11-29 12:12:15 +00:00
export const ns = 'challenge';
export const backendNS = 'backendChallenge';
2018-04-06 14:51:52 +01:00
const initialState = {
canFocusEditor: true,
2018-04-06 14:51:52 +01:00
challengeFiles: {},
challengeMeta: {
id: '',
2019-02-08 17:33:05 +03:00
nextChallengePath: '/',
prevChallengePath: '/',
2019-02-08 17:33:05 +03:00
introPath: '',
challengeType: -1
2018-04-06 14:51:52 +01:00
},
challengeTests: [],
consoleOut: '',
isCodeLocked: false,
2019-02-08 17:33:05 +03:00
isBuildEnabled: true,
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 types = createTypes(
[
'createFiles',
'createQuestion',
2018-04-06 14:51:52 +01:00
'initTests',
'initConsole',
'initLogs',
'updateBackendFormValues',
2018-04-06 14:51:52 +01:00
'updateConsole',
'updateChallengeMeta',
'updateFile',
'updateJSEnabled',
2018-05-24 19:45:38 +01:00
'updateProjectFormValues',
2018-04-06 14:51:52 +01:00
'updateSuccessMessage',
'updateTests',
'updateLogs',
'logsToConsole',
'lockCode',
'unlockCode',
2019-02-08 17:33:05 +03:00
'disableBuildOnError',
'storedCodeFound',
'noStoredCodeFound',
2018-04-06 14:51:52 +01:00
'closeModal',
'openModal',
'previewMounted',
'challengeMounted',
2018-04-06 14:51:52 +01:00
'checkChallenge',
'executeChallenge',
'resetChallenge',
'submitChallenge',
'moveToTab',
'setEditorFocusability'
2018-04-06 14:51:52 +01:00
],
ns
);
export const epics = [
challengeModalEpic,
codeLockEpic,
completionEpic,
createQuestionEpic,
codeStorageEpic
];
2018-11-26 02:17:38 +03:00
export const sagas = [
...createExecuteChallengeSaga(types),
...createCurrentChallengeSaga(types)
2018-11-26 02:17:38 +03:00
];
2018-04-06 14:51:52 +01:00
export const createFiles = createAction(types.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(0)
}
}),
{}
)
);
export const createQuestion = createAction(types.createQuestion);
2018-04-06 14:51:52 +01:00
export const initTests = createAction(types.initTests);
export const updateTests = createAction(types.updateTests);
export const initConsole = createAction(types.initConsole);
export const initLogs = createAction(types.initLogs);
export const updateBackendFormValues = createAction(
types.updateBackendFormValues
);
2018-04-06 14:51:52 +01:00
export const updateChallengeMeta = createAction(types.updateChallengeMeta);
export const updateFile = createAction(types.updateFile);
export const updateConsole = createAction(types.updateConsole);
export const updateLogs = createAction(types.updateLogs);
2018-04-06 14:51:52 +01:00
export const updateJSEnabled = createAction(types.updateJSEnabled);
2018-05-24 19:45:38 +01:00
export const updateProjectFormValues = createAction(
types.updateProjectFormValues
);
2018-04-06 14:51:52 +01:00
export const updateSuccessMessage = createAction(types.updateSuccessMessage);
export const logsToConsole = createAction(types.logsToConsole);
export const lockCode = createAction(types.lockCode);
export const unlockCode = createAction(types.unlockCode);
2019-02-08 17:33:05 +03:00
export const disableBuildOnError = createAction(types.disableBuildOnError);
export const storedCodeFound = createAction(types.storedCodeFound);
export const noStoredCodeFound = createAction(types.noStoredCodeFound);
2018-04-06 14:51:52 +01:00
export const closeModal = createAction(types.closeModal);
export const openModal = createAction(types.openModal);
export const previewMounted = createAction(types.previewMounted);
export const challengeMounted = createAction(types.challengeMounted);
2018-04-06 14:51:52 +01:00
export const checkChallenge = createAction(types.checkChallenge);
export const executeChallenge = createAction(types.executeChallenge);
export const resetChallenge = createAction(types.resetChallenge);
2018-04-06 14:51:52 +01:00
export const submitChallenge = createAction(types.submitChallenge);
export const moveToTab = createAction(types.moveToTab);
export const setEditorFocusability = createAction(types.setEditorFocusability);
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 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;
export const backendFormValuesSelector = state =>
state[ns].backendFormValues || {};
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 = {} } = backendFormValuesSelector(state);
2019-02-08 17:33:05 +03:00
challengeData = {
...challengeData,
url
};
} else if (challengeType === challengeTypes.backEndProject) {
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;
const MAX_LOGS_SIZE = 64 * 1024;
2018-04-06 14:51:52 +01:00
export const reducer = handleActions(
{
[types.createFiles]: (state, { payload }) => ({
...state,
challengeFiles: payload
}),
[types.updateFile]: (state, { payload: { key, editorValue } }) => ({
...state,
challengeFiles: {
...state.challengeFiles,
[key]: {
...state.challengeFiles[key],
contents: editorValue
}
}
}),
[types.storedCodeFound]: (state, { payload }) => ({
...state,
challengeFiles: payload
}),
2018-04-06 14:51:52 +01:00
[types.initTests]: (state, { payload }) => ({
...state,
challengeTests: payload
}),
[types.updateTests]: (state, { payload }) => ({
...state,
challengeTests: payload
}),
2018-04-06 14:51:52 +01:00
[types.initConsole]: (state, { payload }) => ({
...state,
consoleOut: payload
}),
[types.updateConsole]: (state, { payload }) => ({
...state,
consoleOut: state.consoleOut + '\n' + payload
}),
[types.initLogs]: state => ({
...state,
logsOut: ''
}),
[types.updateLogs]: (state, { payload }) => ({
...state,
logsOut: (state.logsOut + '\n' + payload).slice(-MAX_LOGS_SIZE)
}),
[types.logsToConsole]: (state, { payload }) => ({
...state,
consoleOut:
state.consoleOut +
(state.logsOut ? '\n' + payload + '\n' + state.logsOut : '')
}),
[types.updateChallengeMeta]: (state, { payload }) => ({
...state,
challengeMeta: { ...payload }
}),
[types.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()
}
}),
{}
)
},
challengeTests: state.challengeTests.map(({ text, testString }) => ({
text,
testString
})),
consoleOut: ''
2018-04-06 14:51:52 +01:00
}),
[types.updateBackendFormValues]: (state, { payload }) => ({
...state,
backendFormValues: payload
}),
2018-05-24 19:45:38 +01:00
[types.updateProjectFormValues]: (state, { payload }) => ({
...state,
2018-09-27 14:19:03 +03:00
projectFormValues: payload
2018-05-24 19:45:38 +01:00
}),
[types.lockCode]: state => ({
...state,
isCodeLocked: true
}),
[types.unlockCode]: state => ({
...state,
2019-02-08 17:33:05 +03:00
isBuildEnabled: true,
isCodeLocked: false
}),
2019-02-08 17:33:05 +03:00
[types.disableBuildOnError]: (state, { payload }) => ({
2018-04-06 14:51:52 +01:00
...state,
consoleOut: state.consoleOut + ' \n' + payload,
2019-02-08 17:33:05 +03:00
isBuildEnabled: false
2018-04-06 14:51:52 +01:00
}),
2018-04-06 14:51:52 +01:00
[types.updateSuccessMessage]: (state, { payload }) => ({
...state,
successMessage: payload
}),
[types.closeModal]: (state, { payload }) => ({
...state,
modal: {
...state.modal,
[payload]: false
}
}),
[types.openModal]: (state, { payload }) => ({
...state,
modal: {
...state.modal,
[payload]: true
}
}),
[types.moveToTab]: (state, { payload }) => ({
...state,
currentTab: payload
}),
[types.executeChallenge]: state => ({
...state,
currentTab: 3
}),
[types.setEditorFocusability]: (state, { payload }) => ({
...state,
canFocusEditor: payload
2018-04-06 14:51:52 +01:00
})
},
initialState
);