diff --git a/client/src/templates/Challenges/classic/ActionRow.js b/client/src/templates/Challenges/classic/ActionRow.js
new file mode 100644
index 0000000000..c53f5ff740
--- /dev/null
+++ b/client/src/templates/Challenges/classic/ActionRow.js
@@ -0,0 +1,13 @@
+import React from 'react';
+
+import EditorTabs from './EditorTabs';
+
+const ActionRow = () => (
+
+
+
+);
+
+ActionRow.displayName = 'ActionRow';
+
+export default ActionRow;
diff --git a/client/src/templates/Challenges/classic/DesktopLayout.js b/client/src/templates/Challenges/classic/DesktopLayout.js
index ccdeb4bc99..32dd8c8627 100644
--- a/client/src/templates/Challenges/classic/DesktopLayout.js
+++ b/client/src/templates/Challenges/classic/DesktopLayout.js
@@ -1,12 +1,14 @@
-import React, { Component } from 'react';
+import React, { Component, Fragment } from 'react';
import { ReflexContainer, ReflexSplitter, ReflexElement } from 'react-reflex';
import PropTypes from 'prop-types';
+import { first } from 'lodash';
+import EditorTabs from './EditorTabs';
+import ActionRow from './ActionRow';
const propTypes = {
- challengeFile: PropTypes.shape({
- key: PropTypes.string
- }),
+ challengeFiles: PropTypes.object,
editor: PropTypes.element,
+ hasEditableBoundries: PropTypes.bool,
hasPreview: PropTypes.bool,
instructions: PropTypes.element,
preview: PropTypes.element,
@@ -24,42 +26,57 @@ const reflexProps = {
};
class DesktopLayout extends Component {
+ getChallengeFile() {
+ const { challengeFiles } = this.props;
+ return first(Object.keys(challengeFiles).map(key => challengeFiles[key]));
+ }
+
render() {
const {
resizeProps,
instructions,
- challengeFile,
editor,
testOutput,
hasPreview,
- preview
+ preview,
+ hasEditableBoundries
} = this.props;
+
+ const challengeFile = this.getChallengeFile();
return (
-
-
- {instructions}
-
-
-
- {challengeFile && (
-
-
- {editor}
-
-
-
- {testOutput}
-
-
- )}
-
- {hasPreview && }
- {hasPreview && (
-
- {preview}
+
+ {hasEditableBoundries && }
+
+
+ {instructions}
- )}
-
+
+
+ {challengeFile && (
+
+
+ {
+
+ {!hasEditableBoundries && }
+ {editor}
+
+ }
+
+
+
+ {testOutput}
+
+
+ )}
+
+ {hasPreview && }
+ {hasPreview && (
+
+ {preview}
+
+ )}
+
+
);
}
}
diff --git a/client/src/templates/Challenges/classic/Editor.js b/client/src/templates/Challenges/classic/Editor.js
index 775e89921a..a7c9a38025 100644
--- a/client/src/templates/Challenges/classic/Editor.js
+++ b/client/src/templates/Challenges/classic/Editor.js
@@ -435,7 +435,7 @@ class Editor extends Component {
domNode.setAttribute('aria-hidden', true);
- domNode.style.background = 'yellow';
+ domNode.style.background = 'lightYellow';
domNode.style.left = this._editor.getLayoutInfo().contentLeft + 'px';
domNode.style.width = this._editor.getLayoutInfo().contentWidth + 'px';
domNode.style.top = this.getViewZoneTop();
@@ -459,8 +459,6 @@ class Editor extends Component {
outputNode.style.zIndex = '10';
outputNode.setAttribute('aria-hidden', true);
-
- outputNode.style.background = 'var(--secondary-background)';
outputNode.style.left = this._editor.getLayoutInfo().contentLeft + 'px';
outputNode.style.width = this._editor.getLayoutInfo().contentWidth + 'px';
outputNode.style.top = this.getOutputZoneTop();
diff --git a/client/src/templates/Challenges/classic/EditorTabs.js b/client/src/templates/Challenges/classic/EditorTabs.js
index 03f65cc391..73b942d242 100644
--- a/client/src/templates/Challenges/classic/EditorTabs.js
+++ b/client/src/templates/Challenges/classic/EditorTabs.js
@@ -1,9 +1,17 @@
-import React from 'react';
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
import PropTypes from 'prop-types';
+import { createSelector } from 'reselect';
+
+import {
+ toggleVisibleEditor,
+ visibleEditorsSelector,
+ challengeFilesSelector
+} from '../redux';
const propTypes = {
challengeFiles: PropTypes.object.isRequired,
- toggleTab: PropTypes.func.isRequired,
+ toggleVisibleEditor: PropTypes.func.isRequired,
visibleEditors: PropTypes.shape({
indexjs: PropTypes.bool,
indexjsx: PropTypes.bool,
@@ -12,52 +20,73 @@ const propTypes = {
})
};
-const EditorTabs = ({ challengeFiles, toggleTab, visibleEditors }) => (
-
- {challengeFiles['indexjsx'] && (
-
- )}
- {challengeFiles['indexhtml'] && (
-
- )}
- {challengeFiles['indexcss'] && (
-
- )}
- {challengeFiles['indexjs'] && (
-
- )}
-
+const mapStateToProps = createSelector(
+ visibleEditorsSelector,
+ challengeFilesSelector,
+ (visibleEditors, challengeFiles) => ({
+ visibleEditors,
+ challengeFiles
+ })
);
+const mapDispatchToProps = {
+ toggleVisibleEditor
+};
+
+class EditorTabs extends Component {
+ render() {
+ const { challengeFiles, toggleVisibleEditor, visibleEditors } = this.props;
+ return (
+
+ {challengeFiles['indexjsx'] && (
+
+ )}
+ {challengeFiles['indexhtml'] && (
+
+ )}
+ {challengeFiles['indexcss'] && (
+
+ )}
+ {challengeFiles['indexjs'] && (
+
+ )}
+
+ );
+ }
+}
+
EditorTabs.displayName = 'EditorTabs';
EditorTabs.propTypes = propTypes;
-export default EditorTabs;
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(EditorTabs);
diff --git a/client/src/templates/Challenges/classic/MobileLayout.js b/client/src/templates/Challenges/classic/MobileLayout.js
index e64bd7d5e0..363ef55d74 100644
--- a/client/src/templates/Challenges/classic/MobileLayout.js
+++ b/client/src/templates/Challenges/classic/MobileLayout.js
@@ -7,6 +7,7 @@ import ToolPanel from '../components/Tool-Panel';
import { createStructuredSelector } from 'reselect';
import { currentTabSelector, moveToTab } from '../redux';
import { bindActionCreators } from 'redux';
+import EditorTabs from './EditorTabs';
const mapStateToProps = createStructuredSelector({
currentTab: currentTabSelector
@@ -66,6 +67,7 @@ class MobileLayout extends Component {
{instructions}
+
{editor}
diff --git a/client/src/templates/Challenges/classic/MultifileEditor.js b/client/src/templates/Challenges/classic/MultifileEditor.js
index 3aab3e74ec..884429590c 100644
--- a/client/src/templates/Challenges/classic/MultifileEditor.js
+++ b/client/src/templates/Challenges/classic/MultifileEditor.js
@@ -3,7 +3,7 @@ import React, { Component, Suspense } from 'react';
import { connect } from 'react-redux';
import { ReflexContainer, ReflexElement, ReflexSplitter } from 'react-reflex';
import { createSelector } from 'reselect';
-import { isEmpty } from 'lodash';
+import { getTargetEditor } from '../utils/getTargetEditor';
import { isDonationModalOpenSelector, userSelector } from '../../../redux';
import {
canFocusEditorSelector,
@@ -13,13 +13,12 @@ import {
saveEditorContent,
setAccessibilityMode,
setEditorFocusability,
+ visibleEditorsSelector,
updateFile
} from '../redux';
import './editor.css';
import { Loader } from '../../../components/helpers';
-import EditorTabs from './EditorTabs';
import Editor from './Editor';
-import { toSortedArray } from '../../../../../utils/sort-files';
const propTypes = {
canFocus: PropTypes.bool,
@@ -45,16 +44,31 @@ const propTypes = {
setAccessibilityMode: PropTypes.func.isRequired,
setEditorFocusability: PropTypes.func,
theme: PropTypes.string,
- updateFile: PropTypes.func.isRequired
+ updateFile: PropTypes.func.isRequired,
+ visibleEditors: PropTypes.shape({
+ indexjs: PropTypes.bool,
+ indexjsx: PropTypes.bool,
+ indexcss: PropTypes.bool,
+ indexhtml: PropTypes.bool
+ })
};
const mapStateToProps = createSelector(
+ visibleEditorsSelector,
canFocusEditorSelector,
consoleOutputSelector,
inAccessibilityModeSelector,
isDonationModalOpenSelector,
userSelector,
- (canFocus, output, accessibilityMode, open, { theme = 'default' }) => ({
+ (
+ visibleEditors,
+ canFocus,
+ output,
+ accessibilityMode,
+ open,
+ { theme = 'default' }
+ ) => ({
+ visibleEditors,
canFocus: open ? false : canFocus,
output,
inAccessibilityMode: accessibilityMode,
@@ -70,15 +84,6 @@ const mapDispatchToProps = {
updateFile
};
-function getTargetEditor(challengeFiles) {
- let targetEditor = Object.values(challengeFiles).find(
- ({ editableRegionBoundaries }) => !isEmpty(editableRegionBoundaries)
- )?.key;
-
- // fallback for when there is no editable region.
- return targetEditor || toSortedArray(challengeFiles)[0].key;
-}
-
class MultifileEditor extends Component {
constructor(...props) {
super(...props);
@@ -137,13 +142,6 @@ class MultifileEditor extends Component {
}
};
- const { challengeFiles } = this.props;
- const targetEditor = getTargetEditor(challengeFiles);
-
- this.state = {
- visibleEditors: { [targetEditor]: true }
- };
-
// TODO: we might want to store the current editor here
this.focusOnEditor = this.focusOnEditor.bind(this);
}
@@ -159,15 +157,6 @@ class MultifileEditor extends Component {
// this._editor.focus();
}
- toggleTab = newFileKey => {
- this.setState(state => ({
- visibleEditors: {
- ...state.visibleEditors,
- [newFileKey]: !state.visibleEditors[newFileKey]
- }
- }));
- };
-
componentWillUnmount() {
// this.setState({ fileKey: null });
this.data = null;
@@ -180,9 +169,9 @@ class MultifileEditor extends Component {
description,
editorRef,
theme,
- resizeProps
+ resizeProps,
+ visibleEditors
} = this.props;
- const { visibleEditors } = this.state;
const editorTheme = theme === 'night' ? 'vs-dark-custom' : 'vs-custom';
// TODO: the tabs mess up the rendering (scroll doesn't work properly and
// the in-editor description)
@@ -207,21 +196,13 @@ class MultifileEditor extends Component {
// TODO: the tabs mess up the rendering (scroll doesn't work properly and
// the in-editor description)
const targetEditor = getTargetEditor(challengeFiles);
-
return (
-
-
-
-
+
{visibleEditors.indexhtml && (
diff --git a/client/src/templates/Challenges/classic/Show.js b/client/src/templates/Challenges/classic/Show.js
index 4aeb9975fa..ec8017a29f 100644
--- a/client/src/templates/Challenges/classic/Show.js
+++ b/client/src/templates/Challenges/classic/Show.js
@@ -5,7 +5,6 @@ import { createStructuredSelector } from 'reselect';
import { connect } from 'react-redux';
import Helmet from 'react-helmet';
import { graphql } from 'gatsby';
-import { first } from 'lodash';
import Media from 'react-responsive';
import LearnLayout from '../../../components/layouts/Learn';
@@ -184,11 +183,6 @@ class ShowClassic extends Component {
getVideoUrl = () => this.getChallenge().videoUrl;
- getChallengeFile() {
- const { files } = this.props;
- return first(Object.keys(files).map(key => files[key]));
- }
-
hasPreview() {
const { challengeType } = this.getChallenge();
return (
@@ -229,6 +223,7 @@ class ShowClassic extends Component {
containerRef={this.containerRef}
description={description}
editorRef={this.editorRef}
+ hasEditableBoundries={this.hasEditableBoundries()}
resizeProps={this.resizeProps}
/>
)
@@ -255,6 +250,15 @@ class ShowClassic extends Component {
);
}
+ hasEditableBoundries() {
+ const { files } = this.props;
+ return Object.values(files).some(
+ file =>
+ file.editableRegionBoundaries &&
+ file.editableRegionBoundaries.length === 2
+ );
+ }
+
render() {
const {
fields: { blockName },
@@ -265,8 +269,10 @@ class ShowClassic extends Component {
executeChallenge,
pageContext: {
challengeMeta: { introPath, nextChallengePath, prevChallengePath }
- }
+ },
+ files
} = this.props;
+
return (
.scrollbar > .slider {
+ z-index: 11;
}
diff --git a/client/src/templates/Challenges/redux/index.js b/client/src/templates/Challenges/redux/index.js
index 9fb862c262..52f88c666d 100644
--- a/client/src/templates/Challenges/redux/index.js
+++ b/client/src/templates/Challenges/redux/index.js
@@ -12,6 +12,7 @@ import codeStorageEpic from './code-storage-epic';
import { createExecuteChallengeSaga } from './execute-challenge-saga';
import { createCurrentChallengeSaga } from './current-challenge-saga';
import { challengeTypes } from '../../../../utils/challengeTypes';
+import { getTargetEditor } from '../utils/getTargetEditor';
import { completedChallengesSelector } from '../../../redux';
import { isEmpty } from 'lodash';
@@ -20,6 +21,7 @@ export const backendNS = 'backendChallenge';
const initialState = {
canFocusEditor: true,
+ visibleEditors: {},
challengeFiles: {},
challengeMeta: {
superBlock: '',
@@ -86,6 +88,7 @@ export const types = createTypes(
'moveToTab',
'setEditorFocusability',
+ 'toggleVisibleEditor',
'setAccessibilityMode',
'lastBlockChalSubmitted'
@@ -179,6 +182,7 @@ export const submitChallenge = createAction(types.submitChallenge);
export const moveToTab = createAction(types.moveToTab);
export const setEditorFocusability = createAction(types.setEditorFocusability);
+export const toggleVisibleEditor = createAction(types.toggleVisibleEditor);
export const setAccessibilityMode = createAction(types.setAccessibilityMode);
export const lastBlockChalSubmitted = createAction(
@@ -258,6 +262,8 @@ export const challengeDataSelector = state => {
};
export const canFocusEditorSelector = state => state[ns].canFocusEditor;
+export const visibleEditorsSelector = state => state[ns].visibleEditors;
+
export const inAccessibilityModeSelector = state =>
state[ns].inAccessibilityMode;
@@ -265,7 +271,8 @@ export const reducer = handleActions(
{
[types.createFiles]: (state, { payload }) => ({
...state,
- challengeFiles: payload
+ challengeFiles: payload,
+ visibleEditors: { [getTargetEditor(payload)]: true }
}),
[types.updateFile]: (
state,
@@ -399,6 +406,15 @@ export const reducer = handleActions(
...state,
canFocusEditor: payload
}),
+ [types.toggleVisibleEditor]: (state, { payload }) => {
+ return {
+ ...state,
+ visibleEditors: {
+ ...state.visibleEditors,
+ [payload]: !state.visibleEditors[payload]
+ }
+ };
+ },
[types.setAccessibilityMode]: (state, { payload }) => ({
...state,
inAccessibilityMode: payload
diff --git a/client/src/templates/Challenges/utils/getTargetEditor.js b/client/src/templates/Challenges/utils/getTargetEditor.js
new file mode 100644
index 0000000000..950ab5153f
--- /dev/null
+++ b/client/src/templates/Challenges/utils/getTargetEditor.js
@@ -0,0 +1,14 @@
+import { toSortedArray } from '../../../../../utils/sort-files';
+import { isEmpty } from 'lodash';
+
+export function getTargetEditor(challengeFiles) {
+ if (isEmpty(challengeFiles)) return null;
+ else {
+ let targetEditor = Object.values(challengeFiles).find(
+ ({ editableRegionBoundaries }) => !isEmpty(editableRegionBoundaries)
+ )?.key;
+
+ // fallback for when there is no editable region.
+ return targetEditor || toSortedArray(challengeFiles)[0].key;
+ }
+}