feat: control editor focus (#43882)
* refactor: MultifileEditor to functional component. * fix: make editor acquire focus once on mount Now the editors can leave the dom (e.g. if a tab is clicked), but an editor will only call for focus if the MultifileEditor itself remounts
This commit is contained in:
committed by
GitHub
parent
2bddbbff42
commit
6c20301204
@ -1,5 +1,5 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { useRef } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { ReflexContainer, ReflexElement, ReflexSplitter } from 'react-reflex';
|
import { ReflexContainer, ReflexElement, ReflexSplitter } from 'react-reflex';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
@ -73,154 +73,153 @@ const mapDispatchToProps = {
|
|||||||
updateFile
|
updateFile
|
||||||
};
|
};
|
||||||
|
|
||||||
class MultifileEditor extends Component {
|
const MultifileEditor = props => {
|
||||||
focusOnHotkeys() {
|
const {
|
||||||
if (this.props.containerRef.current) {
|
challengeFiles,
|
||||||
this.props.containerRef.current.focus();
|
containerRef,
|
||||||
|
description,
|
||||||
|
editorRef,
|
||||||
|
initialTests,
|
||||||
|
theme,
|
||||||
|
resizeProps,
|
||||||
|
title,
|
||||||
|
visibleEditors: { indexcss, indexhtml, indexjs, indexjsx },
|
||||||
|
usesMultifileEditor
|
||||||
|
} = props;
|
||||||
|
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)
|
||||||
|
|
||||||
|
// TODO: the splitters should appear between editors, so logically this
|
||||||
|
// would be best as
|
||||||
|
// editors.map(props => <EditorWrapper ...props>).join(<ReflexSplitter>)
|
||||||
|
// ...probably! As long as we can put keys in the right places.
|
||||||
|
const reflexProps = {
|
||||||
|
propagateDimensions: true
|
||||||
|
};
|
||||||
|
|
||||||
|
let splitterJSXRight, splitterHTMLRight, splitterCSSRight;
|
||||||
|
if (indexjsx) {
|
||||||
|
if (indexhtml || indexcss || indexjs) {
|
||||||
|
splitterJSXRight = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (indexhtml) {
|
||||||
|
if (indexcss || indexjs) {
|
||||||
|
splitterHTMLRight = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (indexcss) {
|
||||||
|
if (indexjs) {
|
||||||
|
splitterCSSRight = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
// TODO: tabs should be dynamically created from the challengeFiles
|
||||||
const {
|
// TODO: the tabs mess up the rendering (scroll doesn't work properly and
|
||||||
challengeFiles,
|
// the in-editor description)
|
||||||
containerRef,
|
const targetEditor = getTargetEditor(challengeFiles);
|
||||||
description,
|
|
||||||
editorRef,
|
|
||||||
initialTests,
|
|
||||||
theme,
|
|
||||||
resizeProps,
|
|
||||||
title,
|
|
||||||
visibleEditors: { indexcss, indexhtml, indexjs, indexjsx },
|
|
||||||
usesMultifileEditor
|
|
||||||
} = this.props;
|
|
||||||
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)
|
|
||||||
|
|
||||||
// TODO: the splitters should appear between editors, so logically this
|
// Only one editor should be focused and that should happen once, after it has
|
||||||
// would be best as
|
// been mounted. This ref allows the editors to co-ordinate, without having to
|
||||||
// editors.map(props => <EditorWrapper ...props>).join(<ReflexSplitter>)
|
// resort to redux.
|
||||||
// ...probably! As long as we can put keys in the right places.
|
const canFocusOnMountRef = useRef(true);
|
||||||
const reflexProps = {
|
return (
|
||||||
propagateDimensions: true
|
<ReflexContainer
|
||||||
};
|
orientation='horizontal'
|
||||||
|
{...reflexProps}
|
||||||
|
{...resizeProps}
|
||||||
|
className='editor-container'
|
||||||
|
>
|
||||||
|
<ReflexElement flex={10} {...reflexProps} {...resizeProps}>
|
||||||
|
<ReflexContainer orientation='vertical'>
|
||||||
|
{indexjsx && (
|
||||||
|
<ReflexElement {...reflexProps} {...resizeProps}>
|
||||||
|
<Editor
|
||||||
|
canFocusOnMountRef={canFocusOnMountRef}
|
||||||
|
challengeFiles={challengeFiles}
|
||||||
|
containerRef={containerRef}
|
||||||
|
description={targetEditor === 'indexjsx' ? description : null}
|
||||||
|
editorRef={editorRef}
|
||||||
|
fileKey='indexjsx'
|
||||||
|
initialTests={initialTests}
|
||||||
|
key='indexjsx'
|
||||||
|
resizeProps={resizeProps}
|
||||||
|
theme={editorTheme}
|
||||||
|
title={title}
|
||||||
|
usesMultifileEditor={usesMultifileEditor}
|
||||||
|
/>
|
||||||
|
</ReflexElement>
|
||||||
|
)}
|
||||||
|
{splitterJSXRight && (
|
||||||
|
<ReflexSplitter propagate={true} {...resizeProps} />
|
||||||
|
)}
|
||||||
|
{indexhtml && (
|
||||||
|
<ReflexElement {...reflexProps} {...resizeProps}>
|
||||||
|
<Editor
|
||||||
|
canFocusOnMountRef={canFocusOnMountRef}
|
||||||
|
challengeFiles={challengeFiles}
|
||||||
|
containerRef={containerRef}
|
||||||
|
description={targetEditor === 'indexhtml' ? description : null}
|
||||||
|
editorRef={editorRef}
|
||||||
|
fileKey='indexhtml'
|
||||||
|
initialTests={initialTests}
|
||||||
|
key='indexhtml'
|
||||||
|
resizeProps={resizeProps}
|
||||||
|
theme={editorTheme}
|
||||||
|
title={title}
|
||||||
|
usesMultifileEditor={usesMultifileEditor}
|
||||||
|
/>
|
||||||
|
</ReflexElement>
|
||||||
|
)}
|
||||||
|
{splitterHTMLRight && (
|
||||||
|
<ReflexSplitter propagate={true} {...resizeProps} />
|
||||||
|
)}
|
||||||
|
{indexcss && (
|
||||||
|
<ReflexElement {...reflexProps} {...resizeProps}>
|
||||||
|
<Editor
|
||||||
|
canFocusOnMountRef={canFocusOnMountRef}
|
||||||
|
challengeFiles={challengeFiles}
|
||||||
|
containerRef={containerRef}
|
||||||
|
description={targetEditor === 'indexcss' ? description : null}
|
||||||
|
editorRef={editorRef}
|
||||||
|
fileKey='indexcss'
|
||||||
|
initialTests={initialTests}
|
||||||
|
key='indexcss'
|
||||||
|
resizeProps={resizeProps}
|
||||||
|
theme={editorTheme}
|
||||||
|
title={title}
|
||||||
|
usesMultifileEditor={usesMultifileEditor}
|
||||||
|
/>
|
||||||
|
</ReflexElement>
|
||||||
|
)}
|
||||||
|
{splitterCSSRight && (
|
||||||
|
<ReflexSplitter propagate={true} {...resizeProps} />
|
||||||
|
)}
|
||||||
|
|
||||||
let splitterJSXRight, splitterHTMLRight, splitterCSSRight;
|
{indexjs && (
|
||||||
if (indexjsx) {
|
<ReflexElement {...reflexProps} {...resizeProps}>
|
||||||
if (indexhtml || indexcss || indexjs) {
|
<Editor
|
||||||
splitterJSXRight = true;
|
canFocusOnMountRef={canFocusOnMountRef}
|
||||||
}
|
challengeFiles={challengeFiles}
|
||||||
}
|
containerRef={containerRef}
|
||||||
if (indexhtml) {
|
description={targetEditor === 'indexjs' ? description : null}
|
||||||
if (indexcss || indexjs) {
|
editorRef={editorRef}
|
||||||
splitterHTMLRight = true;
|
fileKey='indexjs'
|
||||||
}
|
initialTests={initialTests}
|
||||||
}
|
key='indexjs'
|
||||||
if (indexcss) {
|
resizeProps={resizeProps}
|
||||||
if (indexjs) {
|
theme={editorTheme}
|
||||||
splitterCSSRight = true;
|
title={title}
|
||||||
}
|
usesMultifileEditor={usesMultifileEditor}
|
||||||
}
|
/>
|
||||||
|
</ReflexElement>
|
||||||
// TODO: tabs should be dynamically created from the challengeFiles
|
)}
|
||||||
// TODO: the tabs mess up the rendering (scroll doesn't work properly and
|
</ReflexContainer>
|
||||||
// the in-editor description)
|
</ReflexElement>
|
||||||
const targetEditor = getTargetEditor(challengeFiles);
|
</ReflexContainer>
|
||||||
return (
|
);
|
||||||
<ReflexContainer
|
};
|
||||||
orientation='horizontal'
|
|
||||||
{...reflexProps}
|
|
||||||
{...resizeProps}
|
|
||||||
className='editor-container'
|
|
||||||
>
|
|
||||||
<ReflexElement flex={10} {...reflexProps} {...resizeProps}>
|
|
||||||
<ReflexContainer orientation='vertical'>
|
|
||||||
{indexjsx && (
|
|
||||||
<ReflexElement {...reflexProps} {...resizeProps}>
|
|
||||||
<Editor
|
|
||||||
challengeFiles={challengeFiles}
|
|
||||||
containerRef={containerRef}
|
|
||||||
description={targetEditor === 'indexjsx' ? description : null}
|
|
||||||
editorRef={editorRef}
|
|
||||||
fileKey='indexjsx'
|
|
||||||
initialTests={initialTests}
|
|
||||||
key='indexjsx'
|
|
||||||
resizeProps={resizeProps}
|
|
||||||
theme={editorTheme}
|
|
||||||
title={title}
|
|
||||||
usesMultifileEditor={usesMultifileEditor}
|
|
||||||
/>
|
|
||||||
</ReflexElement>
|
|
||||||
)}
|
|
||||||
{splitterJSXRight && (
|
|
||||||
<ReflexSplitter propagate={true} {...resizeProps} />
|
|
||||||
)}
|
|
||||||
{indexhtml && (
|
|
||||||
<ReflexElement {...reflexProps} {...resizeProps}>
|
|
||||||
<Editor
|
|
||||||
challengeFiles={challengeFiles}
|
|
||||||
containerRef={containerRef}
|
|
||||||
description={
|
|
||||||
targetEditor === 'indexhtml' ? description : null
|
|
||||||
}
|
|
||||||
editorRef={editorRef}
|
|
||||||
fileKey='indexhtml'
|
|
||||||
initialTests={initialTests}
|
|
||||||
key='indexhtml'
|
|
||||||
resizeProps={resizeProps}
|
|
||||||
theme={editorTheme}
|
|
||||||
title={title}
|
|
||||||
usesMultifileEditor={usesMultifileEditor}
|
|
||||||
/>
|
|
||||||
</ReflexElement>
|
|
||||||
)}
|
|
||||||
{splitterHTMLRight && (
|
|
||||||
<ReflexSplitter propagate={true} {...resizeProps} />
|
|
||||||
)}
|
|
||||||
{indexcss && (
|
|
||||||
<ReflexElement {...reflexProps} {...resizeProps}>
|
|
||||||
<Editor
|
|
||||||
challengeFiles={challengeFiles}
|
|
||||||
containerRef={containerRef}
|
|
||||||
description={targetEditor === 'indexcss' ? description : null}
|
|
||||||
editorRef={editorRef}
|
|
||||||
fileKey='indexcss'
|
|
||||||
initialTests={initialTests}
|
|
||||||
key='indexcss'
|
|
||||||
resizeProps={resizeProps}
|
|
||||||
theme={editorTheme}
|
|
||||||
title={title}
|
|
||||||
usesMultifileEditor={usesMultifileEditor}
|
|
||||||
/>
|
|
||||||
</ReflexElement>
|
|
||||||
)}
|
|
||||||
{splitterCSSRight && (
|
|
||||||
<ReflexSplitter propagate={true} {...resizeProps} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{indexjs && (
|
|
||||||
<ReflexElement {...reflexProps} {...resizeProps}>
|
|
||||||
<Editor
|
|
||||||
challengeFiles={challengeFiles}
|
|
||||||
containerRef={containerRef}
|
|
||||||
description={targetEditor === 'indexjs' ? description : null}
|
|
||||||
editorRef={editorRef}
|
|
||||||
fileKey='indexjs'
|
|
||||||
initialTests={initialTests}
|
|
||||||
key='indexjs'
|
|
||||||
resizeProps={resizeProps}
|
|
||||||
theme={editorTheme}
|
|
||||||
title={title}
|
|
||||||
usesMultifileEditor={usesMultifileEditor}
|
|
||||||
/>
|
|
||||||
</ReflexElement>
|
|
||||||
)}
|
|
||||||
</ReflexContainer>
|
|
||||||
</ReflexElement>
|
|
||||||
</ReflexContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MultifileEditor.displayName = 'MultifileEditor';
|
MultifileEditor.displayName = 'MultifileEditor';
|
||||||
MultifileEditor.propTypes = propTypes;
|
MultifileEditor.propTypes = propTypes;
|
||||||
|
@ -57,6 +57,7 @@ interface EditorProps {
|
|||||||
executeChallenge: (options?: { showCompletionModal: boolean }) => void;
|
executeChallenge: (options?: { showCompletionModal: boolean }) => void;
|
||||||
ext: ExtTypes;
|
ext: ExtTypes;
|
||||||
fileKey: FileKeyTypes;
|
fileKey: FileKeyTypes;
|
||||||
|
canFocusOnMountRef: MutableRefObject<boolean>;
|
||||||
initialEditorContent: string;
|
initialEditorContent: string;
|
||||||
initialExt: string;
|
initialExt: string;
|
||||||
initTests: (tests: Test[]) => void;
|
initTests: (tests: Test[]) => void;
|
||||||
@ -631,12 +632,15 @@ const Editor = (props: EditorProps): JSX.Element => {
|
|||||||
|
|
||||||
function focusIfTargetEditor() {
|
function focusIfTargetEditor() {
|
||||||
const { editor } = dataRef.current;
|
const { editor } = dataRef.current;
|
||||||
if (!editor) return;
|
const { canFocusOnMountRef } = props;
|
||||||
|
if (!editor || !canFocusOnMountRef.current) return;
|
||||||
if (!props.usesMultifileEditor) {
|
if (!props.usesMultifileEditor) {
|
||||||
// Only one editor? Focus it.
|
// Only one editor? Focus it.
|
||||||
editor.focus();
|
editor.focus();
|
||||||
|
canFocusOnMountRef.current = false;
|
||||||
} else if (hasEditableRegion()) {
|
} else if (hasEditableRegion()) {
|
||||||
editor.focus();
|
editor.focus();
|
||||||
|
canFocusOnMountRef.current = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user