Files
freeCodeCamp/client/src/templates/Challenges/redux/index.js
Tom 580a51f7a7 fix: simplify mobile layout tabs (#44431)
* fix: simplify mobile layout tabs

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>


Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
2021-12-15 13:52:44 +00:00

365 lines
12 KiB
JavaScript

import { isEmpty } from 'lodash-es';
import { createAction, handleActions } from 'redux-actions';
import { getLines } from '../../../../../utils/get-lines';
import { createPoly } from '../../../../../utils/polyvinyl';
import { challengeTypes } from '../../../../utils/challenge-types';
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 };
const initialState = {
canFocusEditor: true,
visibleEditors: {},
challengeFiles: [],
challengeMeta: {
superBlock: '',
block: '',
id: '',
nextChallengePath: '/',
prevChallengePath: '/',
challengeType: -1
},
challengeTests: [],
consoleOut: [],
hasCompletedBlock: false,
isCodeLocked: false,
isBuildEnabled: true,
isResetting: false,
logsOut: [],
modal: {
completion: false,
help: false,
video: false,
reset: false,
projectPreview: false
},
projectFormValues: {},
successMessage: 'Happy Coding!'
};
export const epics = [
codeLockEpic,
completionEpic,
createQuestionEpic,
codeStorageEpic
];
export const sagas = [
...createExecuteChallengeSaga(actionTypes),
...createCurrentChallengeSaga(actionTypes)
];
// TODO: can createPoly handle editable region, rather than separating it?
export const createFiles = createAction(
actionTypes.createFiles,
challengeFiles =>
challengeFiles.map(challengeFile => ({
...createPoly(challengeFile),
seed: challengeFile.contents.slice(),
editableContents: getLines(
challengeFile.contents,
challengeFile.editableRegionBoundaries
),
seedEditableRegionBoundaries:
challengeFile.editableRegionBoundaries.slice()
}))
);
export const createQuestion = createAction(actionTypes.createQuestion);
export const initTests = createAction(actionTypes.initTests);
export const updateTests = createAction(actionTypes.updateTests);
export const cancelTests = createAction(actionTypes.cancelTests);
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
);
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);
export const closeModal = createAction(actionTypes.closeModal);
export const openModal = createAction(actionTypes.openModal);
export const previewMounted = createAction(actionTypes.previewMounted);
export const projectPreviewMounted = createAction(
actionTypes.projectPreviewMounted
);
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 stopResetting = createAction(actionTypes.stopResetting);
export const submitChallenge = createAction(actionTypes.submitChallenge);
export const setEditorFocusability = createAction(
actionTypes.setEditorFocusability
);
export const toggleVisibleEditor = createAction(
actionTypes.toggleVisibleEditor
);
export const currentTabSelector = state => state[ns].currentTab;
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;
export const isCompletionModalOpenSelector = state =>
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;
export const isProjectPreviewModalOpenSelector = state =>
state[ns].modal.projectPreview;
export const isResettingSelector = state => state[ns].isResetting;
export const isBuildEnabledSelector = state => state[ns].isBuildEnabled;
export const successMessageSelector = state => state[ns].successMessage;
export const projectFormValuesSelector = state =>
state[ns].projectFormValues || {};
export const challengeDataSelector = state => {
const { challengeType } = challengeMetaSelector(state);
let challengeData = { challengeType };
if (
challengeType === challengeTypes.js ||
challengeType === challengeTypes.bonfire
) {
challengeData = {
...challengeData,
challengeFiles: challengeFilesSelector(state)
};
} else if (challengeType === challengeTypes.backend) {
const { solution: url = {} } = projectFormValuesSelector(state);
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) {
challengeData = {
...challengeData,
...projectFormValuesSelector(state)
};
} else if (
challengeType === challengeTypes.html ||
challengeType === challengeTypes.modern
) {
const { required = [], template = '' } = challengeMetaSelector(state);
challengeData = {
...challengeData,
challengeFiles: challengeFilesSelector(state),
required,
template
};
}
return challengeData;
};
export const canFocusEditorSelector = state => state[ns].canFocusEditor;
export const visibleEditorsSelector = state => state[ns].visibleEditors;
export const reducer = handleActions(
{
[actionTypes.createFiles]: (state, { payload }) => ({
...state,
challengeFiles: payload,
visibleEditors: { [getTargetEditor(payload)]: true }
}),
[actionTypes.updateFile]: (
state,
{ payload: { fileKey, editorValue, editableRegionBoundaries } }
) => {
const updates = {};
// if a given part of the payload is null, we leave that part of the state
// unchanged
if (editableRegionBoundaries !== null)
updates.editableRegionBoundaries = editableRegionBoundaries;
if (editorValue !== null) updates.contents = editorValue;
if (editableRegionBoundaries !== null && editorValue !== null)
updates.editableContents = getLines(
editorValue,
editableRegionBoundaries
);
return {
...state,
challengeFiles: [
...state.challengeFiles.filter(x => x.fileKey !== fileKey),
{
...state.challengeFiles.find(x => x.fileKey === fileKey),
...updates
}
]
};
},
[actionTypes.storedCodeFound]: (state, { payload }) => ({
...state,
challengeFiles: payload
}),
[actionTypes.initTests]: (state, { payload }) => ({
...state,
challengeTests: payload
}),
[actionTypes.updateTests]: (state, { payload }) => ({
...state,
challengeTests: payload
}),
[actionTypes.initConsole]: (state, { payload }) => ({
...state,
consoleOut: payload ? [payload] : []
}),
[actionTypes.updateConsole]: (state, { payload }) => ({
...state,
consoleOut: state.consoleOut.concat(payload)
}),
[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 => {
const challengeFilesReset = state.challengeFiles.map(challengeFile => ({
...challengeFile,
contents: challengeFile.seed.slice(),
editableContents: getLines(
challengeFile.seed,
challengeFile.seedEditableRegionBoundaries
),
editableRegionBoundaries:
challengeFile.seedEditableRegionBoundaries.slice()
}));
return {
...state,
currentTab: 2,
challengeFiles: challengeFilesReset,
challengeTests: state.challengeTests.map(({ text, testString }) => ({
text,
testString
})),
consoleOut: [],
isResetting: true
};
},
[actionTypes.stopResetting]: state => ({
...state,
isResetting: false
}),
[actionTypes.updateSolutionFormValues]: (state, { payload }) => ({
...state,
projectFormValues: payload
}),
[actionTypes.lockCode]: state => ({
...state,
isCodeLocked: true
}),
[actionTypes.unlockCode]: state => ({
...state,
isBuildEnabled: true,
isCodeLocked: false
}),
[actionTypes.disableBuildOnError]: state => ({
...state,
isBuildEnabled: false
}),
[actionTypes.updateSuccessMessage]: (state, { payload }) => ({
...state,
successMessage: payload
}),
[actionTypes.closeModal]: (state, { payload }) => ({
...state,
modal: {
...state.modal,
[payload]: false
}
}),
[actionTypes.openModal]: (state, { payload }) => ({
...state,
modal: {
...state.modal,
[payload]: true
}
}),
[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]
}
};
}
},
initialState
);