diff --git a/client/src/templates/Challenges/backend/Show.js b/client/src/templates/Challenges/backend/Show.js index 8fc2cdd5b4..3a741ae2e8 100644 --- a/client/src/templates/Challenges/backend/Show.js +++ b/client/src/templates/Challenges/backend/Show.js @@ -7,6 +7,7 @@ import { graphql } from 'gatsby'; import { executeChallenge, + challengeMounted, challengeTestsSelector, consoleOutputSelector, initTests, @@ -42,12 +43,15 @@ const reduxFormPropTypes = { }; const propTypes = { + challengeMounted: PropTypes.func.isRequired, description: PropTypes.string, executeChallenge: PropTypes.func.isRequired, id: PropTypes.string, + initTests: PropTypes.func.isRequired, output: PropTypes.string, tests: PropTypes.array, title: PropTypes.string, + updateChallengeMeta: PropTypes.func.isRequired, ...reduxFormPropTypes }; @@ -67,6 +71,7 @@ const mapStateToProps = createSelector( ); const mapDispatchToActions = { + challengeMounted, executeChallenge, initTests, updateChallengeMeta @@ -89,6 +94,7 @@ export class BackEnd extends Component { componentDidMount() { const { + challengeMounted, initTests, updateChallengeMeta, data: { @@ -101,6 +107,7 @@ export class BackEnd extends Component { } = this.props; initTests(tests); updateChallengeMeta({ ...challengeMeta, challengeType }); + challengeMounted(challengeMeta.id); window.addEventListener('resize', this.updateDimensions); } @@ -119,6 +126,7 @@ export class BackEnd extends Component { } } = prevProps; const { + challengeMounted, initTests, updateChallengeMeta, data: { @@ -133,6 +141,7 @@ export class BackEnd extends Component { if (prevTitle !== currentTitle) { initTests(tests); updateChallengeMeta({ ...challengeMeta, challengeType }); + challengeMounted(challengeMeta.id); } } diff --git a/client/src/templates/Challenges/classic/Show.js b/client/src/templates/Challenges/classic/Show.js index 63eb3f87b9..cfbaccb8d5 100644 --- a/client/src/templates/Challenges/classic/Show.js +++ b/client/src/templates/Challenges/classic/Show.js @@ -166,6 +166,11 @@ class ShowClassic extends Component { } } + componentWillUnmount() { + const { createFiles } = this.props; + createFiles({}); + } + getChallenge = () => this.props.data.challengeNode; getBlockNameTitle() { diff --git a/client/src/templates/Challenges/project/Show.js b/client/src/templates/Challenges/project/Show.js index 102719b405..922b03c616 100644 --- a/client/src/templates/Challenges/project/Show.js +++ b/client/src/templates/Challenges/project/Show.js @@ -8,8 +8,8 @@ import Helmet from 'react-helmet'; import { randomCompliment } from '../utils/get-words'; import { ChallengeNode } from '../../../redux/propTypes'; import { + challengeMounted, updateChallengeMeta, - createFiles, updateSuccessMessage, openModal, updateProjectFormValues @@ -32,7 +32,7 @@ const mapDispatchToProps = dispatch => bindActionCreators( { updateChallengeMeta, - createFiles, + challengeMounted, updateProjectFormValues, updateSuccessMessage, openCompletionModal: () => openModal('completion') @@ -41,7 +41,7 @@ const mapDispatchToProps = dispatch => ); const propTypes = { - createFiles: PropTypes.func.isRequired, + challengeMounted: PropTypes.func.isRequired, data: PropTypes.shape({ challengeNode: ChallengeNode }), @@ -57,34 +57,42 @@ const propTypes = { export class Project extends Component { componentDidMount() { const { - createFiles, - data: { challengeNode: { title, challengeType } }, + challengeMounted, + data: { + challengeNode: { title, challengeType } + }, pageContext: { challengeMeta }, updateChallengeMeta, updateSuccessMessage } = this.props; - createFiles({}); updateSuccessMessage(randomCompliment()); - return updateChallengeMeta({ ...challengeMeta, title, challengeType }); + updateChallengeMeta({ ...challengeMeta, title, challengeType }); + challengeMounted(challengeMeta.id); } componentDidUpdate(prevProps) { - const { data: { challengeNode: { title: prevTitle } } } = prevProps; const { - createFiles, - data: { challengeNode: { title: currentTitle, challengeType } }, + data: { + challengeNode: { title: prevTitle } + } + } = prevProps; + const { + challengeMounted, + data: { + challengeNode: { title: currentTitle, challengeType } + }, pageContext: { challengeMeta }, updateChallengeMeta, updateSuccessMessage } = this.props; updateSuccessMessage(randomCompliment()); if (prevTitle !== currentTitle) { - createFiles({}); updateChallengeMeta({ ...challengeMeta, title: currentTitle, challengeType }); + challengeMounted(challengeMeta.id); } } @@ -133,7 +141,10 @@ export class Project extends Component { Project.displayName = 'Project'; Project.propTypes = propTypes; -export default connect(mapStateToProps, mapDispatchToProps)(Project); +export default connect( + mapStateToProps, + mapDispatchToProps +)(Project); export const query = graphql` query ProjectChallenge($slug: String!) { diff --git a/client/src/templates/Challenges/redux/code-storage-epic.js b/client/src/templates/Challenges/redux/code-storage-epic.js index e757713b6c..fecec0ef1b 100644 --- a/client/src/templates/Challenges/redux/code-storage-epic.js +++ b/client/src/templates/Challenges/redux/code-storage-epic.js @@ -89,6 +89,10 @@ function saveCodeEpic(action$, state$) { function loadCodeEpic(action$, state$) { return action$.pipe( ofType(types.challengeMounted), + filter(() => { + const files = challengeFilesSelector(state$.value); + return Object.keys(files).length > 0; + }), switchMap(({ payload: id }) => { let finalFiles; const state = state$.value; diff --git a/client/src/templates/Challenges/redux/current-challenge-epic.js b/client/src/templates/Challenges/redux/current-challenge-epic.js deleted file mode 100644 index 32f425a891..0000000000 --- a/client/src/templates/Challenges/redux/current-challenge-epic.js +++ /dev/null @@ -1,36 +0,0 @@ -import { of } from 'rxjs'; -import { ofType } from 'redux-observable'; - -import { types } from './'; -import { filter, switchMap, catchError, mapTo } from 'rxjs/operators'; -import { - isSignedInSelector, - currentChallengeIdSelector, - updateComplete, - updateFailed -} from '../../../redux'; -import postUpdate$ from '../utils/postUpdate$'; - -function currentChallengeEpic(action$, state$) { - return action$.pipe( - ofType(types.challengeMounted), - filter(() => isSignedInSelector(state$.value)), - filter( - ({ payload }) => payload !== currentChallengeIdSelector(state$.value) - ), - switchMap(({ payload }) => { - const update = { - endpoint: '/update-my-current-challenge', - payload: { - currentChallengeId: payload - } - }; - return postUpdate$(update).pipe( - mapTo(updateComplete()), - catchError(() => of(updateFailed(update))) - ); - }) - ); -} - -export default currentChallengeEpic; diff --git a/client/src/templates/Challenges/redux/current-challenge-saga.js b/client/src/templates/Challenges/redux/current-challenge-saga.js new file mode 100644 index 0000000000..c6196f52f1 --- /dev/null +++ b/client/src/templates/Challenges/redux/current-challenge-saga.js @@ -0,0 +1,35 @@ +import { put, select, call, takeEvery } from 'redux-saga/effects'; + +import { + isSignedInSelector, + currentChallengeIdSelector, + updateComplete, + updateFailed +} from '../../../redux'; + +import { post } from '../../../utils/ajax'; + +function* currentChallengeSaga({ payload }) { + const isSignedIn = yield select(isSignedInSelector); + const currentChallengeId = yield select(currentChallengeIdSelector); + if (isSignedIn && payload !== currentChallengeId) { + const update = { + endpoint: '/update-my-current-challenge', + payload: { + currentChallengeId: payload + } + }; + try { + yield call(post, update.endpoint, update.payload); + yield put(updateComplete()); + } catch { + yield put(updateFailed(update)); + } + } +} + +export function createCurrentChallengeSaga(types) { + return [ + takeEvery(types.challengeMounted, currentChallengeSaga) + ]; +} diff --git a/client/src/templates/Challenges/redux/index.js b/client/src/templates/Challenges/redux/index.js index 2009a93944..30ab22112a 100644 --- a/client/src/templates/Challenges/redux/index.js +++ b/client/src/templates/Challenges/redux/index.js @@ -10,10 +10,10 @@ import completionEpic from './completion-epic'; import codeLockEpic from './code-lock-epic'; import createQuestionEpic from './create-question-epic'; import codeStorageEpic from './code-storage-epic'; -import currentChallengeEpic from './current-challenge-epic'; import { createIdToNameMapSaga } from './id-to-name-map-saga'; import { createExecuteChallengeSaga } from './execute-challenge-saga'; +import { createCurrentChallengeSaga } from './current-challenge-saga'; export const ns = 'challenge'; export const backendNS = 'backendChallenge'; @@ -85,13 +85,13 @@ export const epics = [ codeLockEpic, completionEpic, createQuestionEpic, - codeStorageEpic, - currentChallengeEpic + codeStorageEpic ]; export const sagas = [ ...createIdToNameMapSaga(types), - ...createExecuteChallengeSaga(types) + ...createExecuteChallengeSaga(types), + ...createCurrentChallengeSaga(types) ]; export const createFiles = createAction(types.createFiles, challengeFiles => @@ -300,7 +300,7 @@ export const reducer = handleActions( ...state, currentTab: payload }), - [types.executeChallenge]: (state, { payload }) => ({ + [types.executeChallenge]: state => ({ ...state, currentTab: 3 })