feat: add action row for challenges with ERMs on desktop (#39377)
This commit is contained in:
committed by
Mrugesh Mohapatra
parent
69e3e138f6
commit
a1a051bd3a
13
client/src/templates/Challenges/classic/ActionRow.js
Normal file
13
client/src/templates/Challenges/classic/ActionRow.js
Normal file
@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
|
||||
import EditorTabs from './EditorTabs';
|
||||
|
||||
const ActionRow = () => (
|
||||
<div className='action-row'>
|
||||
<EditorTabs />
|
||||
</div>
|
||||
);
|
||||
|
||||
ActionRow.displayName = 'ActionRow';
|
||||
|
||||
export default ActionRow;
|
@ -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 (
|
||||
<ReflexContainer className='desktop-layout' orientation='vertical'>
|
||||
<ReflexElement flex={1} {...resizeProps}>
|
||||
{instructions}
|
||||
</ReflexElement>
|
||||
<ReflexSplitter propagate={true} {...resizeProps} />
|
||||
<ReflexElement flex={1} {...resizeProps}>
|
||||
{challengeFile && (
|
||||
<ReflexContainer key={challengeFile.key} orientation='horizontal'>
|
||||
<ReflexElement flex={1} {...reflexProps} {...resizeProps}>
|
||||
{editor}
|
||||
</ReflexElement>
|
||||
<ReflexSplitter propagate={true} {...resizeProps} />
|
||||
<ReflexElement flex={0.25} {...reflexProps} {...resizeProps}>
|
||||
{testOutput}
|
||||
</ReflexElement>
|
||||
</ReflexContainer>
|
||||
)}
|
||||
</ReflexElement>
|
||||
{hasPreview && <ReflexSplitter propagate={true} {...resizeProps} />}
|
||||
{hasPreview && (
|
||||
<ReflexElement flex={0.7} {...resizeProps}>
|
||||
{preview}
|
||||
<Fragment>
|
||||
{hasEditableBoundries && <ActionRow />}
|
||||
<ReflexContainer className='desktop-layout' orientation='vertical'>
|
||||
<ReflexElement flex={1} {...resizeProps}>
|
||||
{instructions}
|
||||
</ReflexElement>
|
||||
)}
|
||||
</ReflexContainer>
|
||||
<ReflexSplitter propagate={true} {...resizeProps} />
|
||||
<ReflexElement flex={1} {...resizeProps}>
|
||||
{challengeFile && (
|
||||
<ReflexContainer key={challengeFile.key} orientation='horizontal'>
|
||||
<ReflexElement flex={1} {...reflexProps} {...resizeProps}>
|
||||
{
|
||||
<Fragment>
|
||||
{!hasEditableBoundries && <EditorTabs />}
|
||||
{editor}
|
||||
</Fragment>
|
||||
}
|
||||
</ReflexElement>
|
||||
<ReflexSplitter propagate={true} {...resizeProps} />
|
||||
<ReflexElement flex={0.25} {...reflexProps} {...resizeProps}>
|
||||
{testOutput}
|
||||
</ReflexElement>
|
||||
</ReflexContainer>
|
||||
)}
|
||||
</ReflexElement>
|
||||
{hasPreview && <ReflexSplitter propagate={true} {...resizeProps} />}
|
||||
{hasPreview && (
|
||||
<ReflexElement flex={0.7} {...resizeProps}>
|
||||
{preview}
|
||||
</ReflexElement>
|
||||
)}
|
||||
</ReflexContainer>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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 }) => (
|
||||
<div className='monaco-editor-tabs'>
|
||||
{challengeFiles['indexjsx'] && (
|
||||
<button
|
||||
aria-selected={visibleEditors.indexjsx}
|
||||
className='monaco-editor-tab'
|
||||
onClick={() => toggleTab('indexjsx')}
|
||||
role='tab'
|
||||
>
|
||||
script.jsx
|
||||
</button>
|
||||
)}
|
||||
{challengeFiles['indexhtml'] && (
|
||||
<button
|
||||
aria-selected={visibleEditors.indexhtml}
|
||||
className='monaco-editor-tab'
|
||||
onClick={() => toggleTab('indexhtml')}
|
||||
role='tab'
|
||||
>
|
||||
index.html
|
||||
</button>
|
||||
)}
|
||||
{challengeFiles['indexcss'] && (
|
||||
<button
|
||||
aria-selected={visibleEditors.indexcss}
|
||||
className='monaco-editor-tab'
|
||||
onClick={() => toggleTab('indexcss')}
|
||||
role='tab'
|
||||
>
|
||||
styles.css
|
||||
</button>
|
||||
)}
|
||||
{challengeFiles['indexjs'] && (
|
||||
<button
|
||||
aria-selected={visibleEditors.indexjs}
|
||||
className='monaco-editor-tab'
|
||||
onClick={() => toggleTab('indexjs')}
|
||||
role='tab'
|
||||
>
|
||||
script.js
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
const mapStateToProps = createSelector(
|
||||
visibleEditorsSelector,
|
||||
challengeFilesSelector,
|
||||
(visibleEditors, challengeFiles) => ({
|
||||
visibleEditors,
|
||||
challengeFiles
|
||||
})
|
||||
);
|
||||
|
||||
const mapDispatchToProps = {
|
||||
toggleVisibleEditor
|
||||
};
|
||||
|
||||
class EditorTabs extends Component {
|
||||
render() {
|
||||
const { challengeFiles, toggleVisibleEditor, visibleEditors } = this.props;
|
||||
return (
|
||||
<div className='monaco-editor-tabs'>
|
||||
{challengeFiles['indexjsx'] && (
|
||||
<button
|
||||
aria-selected={visibleEditors.indexjsx}
|
||||
className='monaco-editor-tab'
|
||||
onClick={() => toggleVisibleEditor('indexjsx')}
|
||||
role='tab'
|
||||
>
|
||||
script.jsx
|
||||
</button>
|
||||
)}
|
||||
{challengeFiles['indexhtml'] && (
|
||||
<button
|
||||
aria-selected={visibleEditors.indexhtml}
|
||||
className='monaco-editor-tab'
|
||||
onClick={() => toggleVisibleEditor('indexhtml')}
|
||||
role='tab'
|
||||
>
|
||||
index.html
|
||||
</button>
|
||||
)}
|
||||
{challengeFiles['indexcss'] && (
|
||||
<button
|
||||
aria-selected={visibleEditors.indexcss}
|
||||
className='monaco-editor-tab'
|
||||
onClick={() => toggleVisibleEditor('indexcss')}
|
||||
role='tab'
|
||||
>
|
||||
styles.css
|
||||
</button>
|
||||
)}
|
||||
{challengeFiles['indexjs'] && (
|
||||
<button
|
||||
aria-selected={visibleEditors.indexjs}
|
||||
className='monaco-editor-tab'
|
||||
onClick={() => toggleVisibleEditor('indexjs')}
|
||||
role='tab'
|
||||
>
|
||||
script.js
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
EditorTabs.displayName = 'EditorTabs';
|
||||
EditorTabs.propTypes = propTypes;
|
||||
|
||||
export default EditorTabs;
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(EditorTabs);
|
||||
|
@ -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}
|
||||
</TabPane>
|
||||
<TabPane eventKey={2} title='Code' {...editorTabPaneProps}>
|
||||
<EditorTabs />
|
||||
{editor}
|
||||
</TabPane>
|
||||
<TabPane eventKey={3} title='Tests' {...editorTabPaneProps}>
|
||||
|
@ -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 (
|
||||
<ReflexContainer
|
||||
orientation='horizontal'
|
||||
{...reflexProps}
|
||||
{...resizeProps}
|
||||
>
|
||||
<ReflexElement flex={0.1}>
|
||||
<EditorTabs
|
||||
challengeFiles={challengeFiles}
|
||||
toggleTab={this.toggleTab.bind(this)}
|
||||
visibleEditors={visibleEditors}
|
||||
/>
|
||||
</ReflexElement>
|
||||
<ReflexElement flex={0.9} {...reflexProps} {...resizeProps}>
|
||||
<ReflexElement flex={10} {...reflexProps} {...resizeProps}>
|
||||
<ReflexContainer orientation='vertical'>
|
||||
{visibleEditors.indexhtml && (
|
||||
<ReflexElement {...reflexProps} {...resizeProps}>
|
||||
|
@ -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 (
|
||||
<Hotkeys
|
||||
editorRef={this.editorRef}
|
||||
@ -295,8 +301,9 @@ class ShowClassic extends Component {
|
||||
</Media>
|
||||
<Media minWidth={MAX_MOBILE_WIDTH + 1}>
|
||||
<DesktopLayout
|
||||
challengeFile={this.getChallengeFile()}
|
||||
challengeFiles={files}
|
||||
editor={this.renderEditor()}
|
||||
hasEditableBoundries={this.hasEditableBoundries()}
|
||||
hasPreview={this.hasPreview()}
|
||||
instructions={this.renderInstructionsPanel({
|
||||
showToolPanel: true
|
||||
|
@ -57,3 +57,54 @@
|
||||
.monaco-menu .monaco-action-bar.vertical .action-label.separator {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.monaco-editor-tabs {
|
||||
display: flex;
|
||||
padding: 0px 10px;
|
||||
background-color: var(--primary-background);
|
||||
border-bottom: 2px solid var(--primary-color);
|
||||
}
|
||||
|
||||
.monaco-editor-tab {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
padding: 4px 16px;
|
||||
border: 2px solid var(--primary-color);
|
||||
border-left: none;
|
||||
background-color: var(--secondary-background);
|
||||
}
|
||||
|
||||
button.monaco-editor-tab:hover {
|
||||
color: var(--quaternary-color);
|
||||
background-color: var(--quaternary-background);
|
||||
}
|
||||
|
||||
.monaco-editor-tab:first-child {
|
||||
border-left: 2px solid var(--primary-color);
|
||||
}
|
||||
|
||||
.monaco-editor-tab-selected {
|
||||
background-color: var(--primary-background);
|
||||
border-bottom: 2px solid var(--primary-background);
|
||||
}
|
||||
|
||||
.monaco-editor-tab[role='tab'][aria-selected='true'] {
|
||||
border-color: var(--secondary-color);
|
||||
background-color: var(--secondary-color);
|
||||
color: var(--secondary-background);
|
||||
}
|
||||
|
||||
.action-row {
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid var(--quaternary-background);
|
||||
}
|
||||
|
||||
.action-row .monaco-editor-tabs {
|
||||
padding: 0;
|
||||
border-bottom: none;
|
||||
background-color: var(--secondary-background);
|
||||
}
|
||||
|
||||
.action-row .monaco-editor-tabs .monaco-editor-tab {
|
||||
top: 0;
|
||||
}
|
||||
|
@ -1,53 +1,15 @@
|
||||
.monaco-editor-tabs {
|
||||
display: flex;
|
||||
padding: 0px 10px;
|
||||
background-color: var(--primary-background);
|
||||
border-bottom: 2px solid var(--primary-color);
|
||||
#description {
|
||||
background: var(--secondary-background);
|
||||
}
|
||||
.monaco-editor .margin-view-overlays .line-numbers,
|
||||
.monaco-editor .margin-view-overlays .myLineDecoration + .line-numbers {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.monaco-editor-tab {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
padding: 4px 16px;
|
||||
border: 2px solid var(--primary-color);
|
||||
border-left: none;
|
||||
background-color: var(--secondary-background);
|
||||
[widgetid='my.overlay.widget'] {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
button.monaco-editor-tab:hover {
|
||||
color: var(--quaternary-color);
|
||||
background-color: var(--quaternary-background);
|
||||
}
|
||||
|
||||
.monaco-editor-tab:first-child {
|
||||
border-left: 2px solid var(--primary-color);
|
||||
}
|
||||
|
||||
.monaco-editor-tab-selected {
|
||||
background-color: var(--primary-background);
|
||||
border-bottom: 2px solid var(--primary-background);
|
||||
}
|
||||
|
||||
.monaco-editor-tab[role='tab'][aria-selected='true'] {
|
||||
border-color: var(--secondary-color);
|
||||
background-color: var(--secondary-color);
|
||||
color: var(--secondary-background);
|
||||
}
|
||||
|
||||
.myInlineDecoration {
|
||||
color: lightgray !important;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
font-weight: bold;
|
||||
font-style: oblique;
|
||||
}
|
||||
|
||||
.myLineDecoration {
|
||||
background: lightblue;
|
||||
width: 5px !important;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.do-not-edit {
|
||||
background: grey;
|
||||
.vs .monaco-scrollable-element > .scrollbar > .slider {
|
||||
z-index: 11;
|
||||
}
|
||||
|
@ -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
|
||||
|
14
client/src/templates/Challenges/utils/getTargetEditor.js
Normal file
14
client/src/templates/Challenges/utils/getTargetEditor.js
Normal file
@ -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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user