fix(client): align multifileEditor with hotkey types (#49830)

Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>
This commit is contained in:
Muhammed Mustafa 2023-10-16 15:14:23 +03:00 committed by GitHub
parent 01510c77d3
commit 4cd7fe03c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 65 additions and 58 deletions

View File

@ -74,7 +74,7 @@ export interface EditorProps {
canFocus: boolean;
challengeFiles: ChallengeFiles;
challengeType: number;
containerRef: MutableRefObject<HTMLElement | undefined>;
containerRef?: React.RefObject<HTMLElement>;
description: string;
dimensions?: Dimensions;
editorRef: MutableRefObject<editor.IStandaloneCodeEditor | undefined>;
@ -825,7 +825,7 @@ const Editor = (props: EditorProps): JSX.Element => {
}
function focusOnHotkeys() {
const currContainerRef = props.containerRef.current;
const currContainerRef = props.containerRef?.current;
if (currContainerRef) {
currContainerRef.focus();
}

View File

@ -42,6 +42,7 @@ type MultifileEditorProps = Pick<
> & {
visibleEditors: VisibleEditors;
};
const mapStateToProps = createSelector(
visibleEditorsSelector,
canFocusEditorSelector,

View File

@ -211,7 +211,7 @@ function ShowClassic({
const { t } = useTranslation();
const [resizing, setResizing] = useState(false);
const [usingKeyboardInTablist, setUsingKeyboardInTablist] = useState(false);
const containerRef = useRef<HTMLElement>();
const containerRef = useRef<HTMLElement>(null);
const editorRef = useRef<editor.IStandaloneCodeEditor>();
const instructionsPanelRef = useRef<HTMLDivElement>(null);
const isMobile = useMediaQuery({
@ -415,12 +415,12 @@ function ShowClassic({
<Hotkeys
challengeType={challengeType}
executeChallenge={executeChallenge}
innerRef={containerRef}
containerRef={containerRef}
instructionsPanelRef={instructionsPanelRef}
nextChallengePath={nextChallengePath}
prevChallengePath={prevChallengePath}
usesMultifileEditor={usesMultifileEditor}
{...(editorRef && { editorRef: editorRef })}
editorRef={editorRef}
>
<LearnLayout hasEditableBoundaries={hasEditableBoundaries}>
<Helmet title={windowTitle} />

View File

@ -1,7 +1,7 @@
// Package Utilities
import { Button } from '@freecodecamp/react-bootstrap';
import { graphql } from 'gatsby';
import React, { Component, RefObject } from 'react';
import React, { Component } from 'react';
import Helmet from 'react-helmet';
import type { TFunction } from 'i18next';
import { Trans, withTranslation } from 'react-i18next';
@ -113,7 +113,7 @@ interface ShowCodeAllyProps {
class ShowCodeAlly extends Component<ShowCodeAllyProps> {
static displayName: string;
private _container: RefObject<HTMLElement> | undefined;
private container: React.RefObject<HTMLElement> = React.createRef();
componentDidMount(): void {
const {
@ -133,8 +133,7 @@ class ShowCodeAlly extends Component<ShowCodeAllyProps> {
helpCategory
});
challengeMounted(challengeMeta.id);
this._container?.current?.focus();
this.container.current?.focus();
}
componentWillUnmount() {
@ -237,7 +236,6 @@ class ShowCodeAlly extends Component<ShowCodeAllyProps> {
challenge => challenge.id === challengeId
);
const titleContext = t('learn.github-link');
return showCodeAlly ? (
<LearnLayout>
<Helmet title={windowTitle} />
@ -252,7 +250,7 @@ class ShowCodeAlly extends Component<ShowCodeAllyProps> {
</LearnLayout>
) : (
<Hotkeys
innerRef={this._container}
containerRef={this.container}
nextChallengePath={nextChallengePath}
prevChallengePath={prevChallengePath}
>

View File

@ -1,9 +1,8 @@
import { navigate } from 'gatsby';
import React, { MutableRefObject, RefObject } from 'react';
import React from 'react';
import { HotKeys, GlobalHotKeys } from 'react-hotkeys';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { editor } from 'monaco-editor';
import type {
ChallengeFiles,
Test,
@ -25,6 +24,7 @@ import {
} from '../redux/selectors';
import './hotkeys.css';
import { isFinalProject } from '../../../../../shared/config/challenge-types';
import type { EditorProps } from '../classic/editor';
const mapStateToProps = createSelector(
canFocusEditorSelector,
@ -61,24 +61,32 @@ const keyMap = {
showShortcuts: 'shift+/'
};
interface HotkeysProps
extends Pick<ChallengeMeta, 'nextChallengePath' | 'prevChallengePath'> {
canFocusEditor: boolean;
challengeFiles: ChallengeFiles;
challengeType?: number;
children: React.ReactElement;
editorRef?: MutableRefObject<editor.IStandaloneCodeEditor | undefined>;
executeChallenge?: (options?: { showCompletionModal: boolean }) => void;
submitChallenge: () => void;
innerRef: RefObject<HTMLElement> | undefined;
instructionsPanelRef?: React.RefObject<HTMLElement>;
setEditorFocusability: (arg0: boolean) => void;
setIsAdvancing: (arg0: boolean) => void;
tests: Test[];
usesMultifileEditor?: boolean;
openShortcutsModal: () => void;
user: User;
}
export type HotkeysProps = Pick<
ChallengeMeta,
'nextChallengePath' | 'prevChallengePath'
> &
Partial<
Pick<
EditorProps,
'usesMultifileEditor' | 'editorRef' | 'challengeType' | 'executeChallenge'
>
> &
Pick<
EditorProps,
| 'containerRef'
| 'tests'
| 'challengeFiles'
| 'submitChallenge'
| 'setEditorFocusability'
> & {
canFocusEditor: boolean;
children: React.ReactElement;
instructionsPanelRef?: React.RefObject<HTMLElement>;
setEditorFocusability: (arg0: boolean) => void;
setIsAdvancing: (arg0: boolean) => void;
openShortcutsModal: () => void;
user: User;
};
function Hotkeys({
canFocusEditor,
@ -87,7 +95,7 @@ function Hotkeys({
instructionsPanelRef,
editorRef,
executeChallenge,
innerRef,
containerRef,
nextChallengePath,
prevChallengePath,
setEditorFocusability,
@ -99,12 +107,12 @@ function Hotkeys({
user: { keyboardShortcuts }
}: HotkeysProps): JSX.Element {
const handlers = {
executeChallenge: (e?: KeyboardEvent) => {
executeChallenge: (keyEvent?: KeyboardEvent) => {
// the 'enter' part of 'ctrl+enter' stops HotKeys from listening, so it
// needs to be prevented.
// TODO: 'enter' on its own also disables HotKeys, but default behaviour
// should not be prevented in that case.
e?.preventDefault();
keyEvent?.preventDefault();
if (!executeChallenge) return;
@ -126,8 +134,8 @@ function Hotkeys({
},
...(keyboardShortcuts
? {
focusEditor: (e?: KeyboardEvent) => {
e?.preventDefault();
focusEditor: (keyEvent?: KeyboardEvent) => {
keyEvent?.preventDefault();
if (editorRef && editorRef.current) {
editorRef.current.focus();
}
@ -158,8 +166,8 @@ function Hotkeys({
}
}
},
showShortcuts: (e?: KeyboardEvent) => {
if (!canFocusEditor && e?.shiftKey && e.key === '?') {
showShortcuts: (keyEvent?: KeyboardEvent) => {
if (!canFocusEditor && keyEvent?.shiftKey && keyEvent.key === '?') {
openShortcutsModal();
}
}
@ -177,7 +185,7 @@ function Hotkeys({
id='editor-layout'
allowChanges={true}
handlers={handlers}
innerRef={innerRef}
innerRef={containerRef}
keyMap={keyMap}
>
{children}

View File

@ -138,7 +138,7 @@ function convertMd(md: string): string {
class ShowExam extends Component<ShowExamProps, ShowExamState> {
static displayName: string;
private _container: RefObject<HTMLElement> | undefined;
private container: RefObject<HTMLElement> | undefined = React.createRef();
timerInterval!: NodeJS.Timeout;
constructor(props: ShowExamProps) {
@ -179,7 +179,7 @@ class ShowExam extends Component<ShowExamProps, ShowExamState> {
});
challengeMounted(challengeMeta.id);
this._container?.current?.focus();
this.container?.current?.focus();
}
componentWillUnmount() {
@ -513,7 +513,7 @@ class ShowExam extends Component<ShowExamProps, ShowExamState> {
</Container>
) : (
<Hotkeys
innerRef={this._container}
containerRef={this.container}
nextChallengePath={nextChallengePath}
prevChallengePath={prevChallengePath}
>

View File

@ -84,7 +84,7 @@ interface MsTrophyProps {
// Component
class MsTrophy extends Component<MsTrophyProps> {
static displayName: string;
private _container: HTMLElement | null = null;
private container: React.RefObject<HTMLElement> = React.createRef();
constructor(props: MsTrophyProps) {
super(props);
@ -108,7 +108,7 @@ class MsTrophy extends Component<MsTrophyProps> {
helpCategory
});
challengeMounted(challengeMeta.id);
this._container?.focus();
this.container.current?.focus();
}
componentDidUpdate(prevProps: MsTrophyProps): void {
@ -178,7 +178,7 @@ class MsTrophy extends Component<MsTrophyProps> {
return (
<Hotkeys
innerRef={(c: HTMLElement | null) => (this._container = c)}
containerRef={this.container}
nextChallengePath={nextChallengePath}
prevChallengePath={prevChallengePath}
>

View File

@ -83,7 +83,7 @@ interface ShowOdinState {
// Component
class ShowOdin extends Component<ShowOdinProps, ShowOdinState> {
static displayName: string;
private _container: HTMLElement | null | undefined;
private container: React.RefObject<HTMLElement> = React.createRef();
constructor(props: ShowOdinProps) {
super(props);
@ -119,7 +119,7 @@ class ShowOdin extends Component<ShowOdinProps, ShowOdinState> {
helpCategory
});
challengeMounted(challengeMeta.id);
this._container?.focus();
this.container.current?.focus();
}
componentDidUpdate(prevProps: ShowOdinProps): void {
@ -236,7 +236,7 @@ class ShowOdin extends Component<ShowOdinProps, ShowOdinState> {
executeChallenge={() => {
this.handleSubmit(solution, openCompletionModal, assignments);
}}
innerRef={(c: HTMLElement | null) => (this._container = c)}
containerRef={this.container}
nextChallengePath={nextChallengePath}
prevChallengePath={prevChallengePath}
>

View File

@ -99,8 +99,8 @@ interface BackEndProps {
// Component
class BackEnd extends Component<BackEndProps> {
static displayName: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private _container: any;
private container: React.RefObject<HTMLElement> = React.createRef();
constructor(props: BackEndProps) {
super(props);
this.state = {};
@ -111,7 +111,7 @@ class BackEnd extends Component<BackEndProps> {
componentDidMount() {
this.initializeComponent();
window.addEventListener('resize', () => this.updateDimensions());
this._container.focus();
this.container.current?.focus();
}
updateDimensions() {
@ -220,7 +220,7 @@ class BackEnd extends Component<BackEndProps> {
return (
<Hotkeys
innerRef={(c: HTMLElement | null) => (this._container = c)}
containerRef={this.container}
nextChallengePath={nextChallengePath}
prevChallengePath={prevChallengePath}
>

View File

@ -64,7 +64,7 @@ interface ProjectProps {
// Component
class Project extends Component<ProjectProps> {
static displayName: string;
private _container: HTMLElement | null = null;
private container: React.RefObject<HTMLElement> = React.createRef();
constructor(props: ProjectProps) {
super(props);
@ -88,7 +88,7 @@ class Project extends Component<ProjectProps> {
helpCategory
});
challengeMounted(challengeMeta.id);
this._container?.focus();
this.container.current?.focus();
}
componentDidUpdate(prevProps: ProjectProps): void {
@ -161,7 +161,7 @@ class Project extends Component<ProjectProps> {
return (
<Hotkeys
innerRef={(c: HTMLElement | null) => (this._container = c)}
containerRef={this.container}
nextChallengePath={nextChallengePath}
prevChallengePath={prevChallengePath}
>

View File

@ -83,7 +83,7 @@ interface ShowVideoState {
// Component
class ShowVideo extends Component<ShowVideoProps, ShowVideoState> {
static displayName: string;
private _container: HTMLElement | null | undefined;
private container: React.RefObject<HTMLElement> = React.createRef();
constructor(props: ShowVideoProps) {
super(props);
@ -117,7 +117,7 @@ class ShowVideo extends Component<ShowVideoProps, ShowVideoState> {
helpCategory
});
challengeMounted(challengeMeta.id);
this._container?.focus();
this.container.current?.focus();
}
componentDidUpdate(prevProps: ShowVideoProps): void {
@ -213,7 +213,7 @@ class ShowVideo extends Component<ShowVideoProps, ShowVideoState> {
executeChallenge={() => {
this.handleSubmit(solution, openCompletionModal);
}}
innerRef={(c: HTMLElement | null) => (this._container = c)}
containerRef={this.container}
nextChallengePath={nextChallengePath}
prevChallengePath={prevChallengePath}
>