Feat(Challenges): no js preview (#16149)
* fix(files): Decouple files from challenges * feat(server/react): Remove action logger use redux remote devtools instead! * feat(Challenges): Disable js on edit, enable on execute * feat(Challenge/Preview): Show message when js is disabled * refactor(frameEpic): Reduce code by using lodash * feat(frameEpic): Disable js in preview by state * feat(frameEpic): Colocate epic in Challenges/redux * refactor(ExecuteChallengeEpic): CoLocated with Challenges * refactor(executeChallengesEpic): Separate tests from main logic * feat(Challenge/Preview): Update main on edit * feat(frameEpuc): Replace frame on edit/execute This allows for sandbox to work properly * fix(Challenges/Utils): Require utisl * revert(frameEpic): Hoist function to mount code in frame * fix(frameEpic): Ensure new frame is given classname * feat(executeChallenge): Update main on code unlocked * fix(frameEpic): Filter out empty test message * fix(Challenge/Preview): Remove unnessary quote in classname * feat(codeStorageEpic): Separate localstorage from solutions loading * fix(fetchUser): Merge user actions into one prefer many effects from one action over one action to one effect * fix(themes): Centralize theme utils and defs * fix(entities.user): Fix user reducer namespacing * feat(frame): Refactor frameEpic to util * feat(Challenges.redux): Should not attempt to update main from storage * fix(loadPreviousChallengeEpic): Refactor for RFR * fix(Challenges.Modern): Show preview plane
This commit is contained in:
committed by
Quincy Larson
parent
9051faee79
commit
2e410330f1
@@ -1,3 +1,4 @@
|
||||
import _ from 'lodash';
|
||||
import { Observable } from 'rx';
|
||||
import {
|
||||
combineActions,
|
||||
@@ -7,18 +8,21 @@ import {
|
||||
handleActions
|
||||
} from 'berkeleys-redux-utils';
|
||||
import { createSelector } from 'reselect';
|
||||
import noop from 'lodash/noop';
|
||||
import identity from 'lodash/identity';
|
||||
|
||||
import { entitiesSelector } from '../entities';
|
||||
import fetchUserEpic from './fetch-user-epic.js';
|
||||
import updateMyCurrentChallengeEpic from './update-my-challenge-epic.js';
|
||||
import fetchChallengesEpic from './fetch-challenges-epic.js';
|
||||
import navSizeEpic from './nav-size-epic.js';
|
||||
|
||||
import { createFilesMetaCreator } from '../files';
|
||||
import { updateThemeMetacreator, entitiesSelector } from '../entities';
|
||||
import { types as challenges } from '../routes/Challenges/redux';
|
||||
import { challengeToFiles } from '../routes/Challenges/utils';
|
||||
|
||||
import ns from '../ns.json';
|
||||
|
||||
import { themes, invertTheme } from '../../utils/themes.js';
|
||||
|
||||
export const epics = [
|
||||
fetchUserEpic,
|
||||
fetchChallengesEpic,
|
||||
@@ -36,9 +40,7 @@ export const types = createTypes([
|
||||
createAsyncTypes('fetchChallenge'),
|
||||
createAsyncTypes('fetchChallenges'),
|
||||
|
||||
'fetchUser',
|
||||
'addUser',
|
||||
'updateThisUser',
|
||||
createAsyncTypes('fetchUser'),
|
||||
'showSignIn',
|
||||
|
||||
'handleError',
|
||||
@@ -48,8 +50,7 @@ export const types = createTypes([
|
||||
|
||||
// night mode
|
||||
'toggleNightMode',
|
||||
'updateTheme',
|
||||
'addThemeToBody'
|
||||
'postThemeComplete'
|
||||
], ns);
|
||||
|
||||
const throwIfUndefined = () => {
|
||||
@@ -95,7 +96,10 @@ export const fetchChallenge = createAction(
|
||||
export const fetchChallengeCompleted = createAction(
|
||||
types.fetchChallenge.complete,
|
||||
null,
|
||||
identity
|
||||
meta => ({
|
||||
...meta,
|
||||
..._.flow(challengeToFiles, createFilesMetaCreator)(meta.challenge)
|
||||
})
|
||||
);
|
||||
export const fetchChallenges = createAction('' + types.fetchChallenges);
|
||||
export const fetchChallengesCompleted = createAction(
|
||||
@@ -110,16 +114,12 @@ export const updateTitle = createAction(types.updateTitle);
|
||||
// fetchUser() => Action
|
||||
// used in combination with fetch-user-epic
|
||||
export const fetchUser = createAction(types.fetchUser);
|
||||
|
||||
// addUser(
|
||||
// entities: { [userId]: User }
|
||||
// ) => Action
|
||||
export const addUser = createAction(
|
||||
types.addUser,
|
||||
noop,
|
||||
entities => ({ entities })
|
||||
export const fetchUserComplete = createAction(
|
||||
types.fetchUser.complete,
|
||||
({ result }) => result,
|
||||
_.identity
|
||||
);
|
||||
export const updateThisUser = createAction(types.updateThisUser);
|
||||
|
||||
export const showSignIn = createAction(types.showSignIn);
|
||||
|
||||
// used when server needs client to redirect
|
||||
@@ -145,21 +145,20 @@ export const doActionOnError = actionCreator => error => Observable.of(
|
||||
|
||||
export const toggleNightMode = createAction(
|
||||
types.toggleNightMode,
|
||||
// we use this function to avoid hanging onto the eventObject
|
||||
// so that react can recycle it
|
||||
() => null
|
||||
null,
|
||||
(username, theme) => updateThemeMetacreator(username, invertTheme(theme))
|
||||
);
|
||||
export const postThemeComplete = createAction(
|
||||
types.postThemeComplete,
|
||||
null,
|
||||
updateThemeMetacreator
|
||||
);
|
||||
// updateTheme(theme: /night|default/) => Action
|
||||
export const updateTheme = createAction(types.updateTheme);
|
||||
// addThemeToBody(theme: /night|default/) => Action
|
||||
export const addThemeToBody = createAction(types.addThemeToBody);
|
||||
|
||||
const initialState = {
|
||||
const defaultState = {
|
||||
title: 'Learn To Code | freeCodeCamp',
|
||||
isSignInAttempted: false,
|
||||
user: '',
|
||||
csrfToken: '',
|
||||
theme: 'default',
|
||||
// eventually this should be only in the user object
|
||||
currentChallenge: '',
|
||||
superBlocks: []
|
||||
@@ -167,28 +166,37 @@ const initialState = {
|
||||
|
||||
export const getNS = state => state[ns];
|
||||
export const csrfSelector = state => getNS(state).csrfToken;
|
||||
export const themeSelector = state => getNS(state).theme;
|
||||
export const titleSelector = state => getNS(state).title;
|
||||
|
||||
export const currentChallengeSelector = state => getNS(state).currentChallenge;
|
||||
export const superBlocksSelector = state => getNS(state).superBlocks;
|
||||
export const signInLoadingSelector = state => !getNS(state).isSignInAttempted;
|
||||
|
||||
export const usernameSelector = state => getNS(state).user || '';
|
||||
export const userSelector = createSelector(
|
||||
state => getNS(state).user,
|
||||
state => entitiesSelector(state).user,
|
||||
(username, userMap) => userMap[username] || {}
|
||||
);
|
||||
|
||||
export const themeSelector = _.flow(
|
||||
userSelector,
|
||||
user => user.theme || themes.default
|
||||
);
|
||||
|
||||
export const isSignedInSelector = state => !!userSelector(state).username;
|
||||
|
||||
export const challengeSelector = createSelector(
|
||||
currentChallengeSelector,
|
||||
state => entitiesSelector(state).challenge,
|
||||
(challengeName, challengeMap = {}) => {
|
||||
return challengeMap[challengeName] || {};
|
||||
}
|
||||
);
|
||||
export const challengeSelector = state => {
|
||||
const challengeName = currentChallengeSelector(state);
|
||||
const challengeMap = entitiesSelector(state).challenge || {};
|
||||
return challengeMap[challengeName] || {};
|
||||
};
|
||||
|
||||
export const previousSolutionSelector = state => {
|
||||
const { id } = challengeSelector(state);
|
||||
const { challengeMap = {} } = userSelector(state);
|
||||
return challengeMap[id];
|
||||
};
|
||||
|
||||
export const firstChallengeSelector = createSelector(
|
||||
entitiesSelector,
|
||||
@@ -231,7 +239,7 @@ export default handleActions(
|
||||
title: payload + ' | freeCodeCamp'
|
||||
}),
|
||||
|
||||
[types.updateThisUser]: (state, { payload: user }) => ({
|
||||
[types.fetchUser.complete]: (state, { payload: user }) => ({
|
||||
...state,
|
||||
user
|
||||
}),
|
||||
@@ -246,11 +254,9 @@ export default handleActions(
|
||||
...state,
|
||||
currentChallenge: dashedName
|
||||
}),
|
||||
[types.updateTheme]: (state, { payload = 'default' }) => ({
|
||||
...state,
|
||||
theme: payload
|
||||
}),
|
||||
[combineActions(types.showSignIn, types.updateThisUser)]: state => ({
|
||||
[
|
||||
combineActions(types.showSignIn, types.fetchUser.complete)
|
||||
]: state => ({
|
||||
...state,
|
||||
isSignInAttempted: true
|
||||
}),
|
||||
@@ -264,6 +270,6 @@ export default handleActions(
|
||||
delayedRedirect: payload
|
||||
})
|
||||
}),
|
||||
initialState,
|
||||
defaultState,
|
||||
ns
|
||||
);
|
||||
|
Reference in New Issue
Block a user