From a82316e469f616d0fadb417eb203625e9953c790 Mon Sep 17 00:00:00 2001 From: Oliver Eyton-Williams Date: Wed, 2 Apr 2025 20:50:43 +0200 Subject: [PATCH] refactor(client): simplify sort challenge files (#59179) Co-authored-by: Naomi --- client/src/redux/prop-types.ts | 11 +---- .../Challenges/classic/desktop-layout.tsx | 23 ++------- .../Challenges/classic/editor-tabs.tsx | 48 ++++++++----------- .../templates/Challenges/classic/editor.tsx | 7 ++- .../Challenges/classic/multifile-editor.tsx | 3 +- .../Challenges/utils/get-target-editor.ts | 10 ++-- client/utils/gatsby/challenge-page-creator.js | 18 +++---- client/utils/sort-challengefiles.js | 17 ------- ...es.test.js => sort-challengefiles.test.ts} | 4 +- client/utils/sort-challengefiles.ts | 17 +++++++ 10 files changed, 64 insertions(+), 94 deletions(-) delete mode 100644 client/utils/sort-challengefiles.js rename client/utils/{sort-challengefiles.test.js => sort-challengefiles.test.ts} (87%) create mode 100644 client/utils/sort-challengefiles.ts diff --git a/client/src/redux/prop-types.ts b/client/src/redux/prop-types.ts index e349ceef89a..96550a2b556 100644 --- a/client/src/redux/prop-types.ts +++ b/client/src/redux/prop-types.ts @@ -198,7 +198,7 @@ export type ChallengeNode = { required: Required[]; scene: FullScene; solutions: { - [T in FileKey]: FileKeyChallenge; + [T: string]: FileKeyChallenge; }; sourceInstanceName: string; superOrder: number; @@ -383,13 +383,6 @@ export interface ChallengeData extends CompletedChallenge { challengeFiles: ChallengeFile[] | null; } -export type FileKey = - | 'scriptjs' - | 'indexts' - | 'indexhtml' - | 'stylescss' - | 'indexjsx'; - export type ChallengeMeta = { block: string; id: string; @@ -421,7 +414,7 @@ export type FileKeyChallenge = { ext: Ext; head: string; id: string; - key: FileKey; + key: string; name: string; tail: string; }; diff --git a/client/src/templates/Challenges/classic/desktop-layout.tsx b/client/src/templates/Challenges/classic/desktop-layout.tsx index 306ee4be4f7..688060f9e31 100644 --- a/client/src/templates/Challenges/classic/desktop-layout.tsx +++ b/client/src/templates/Challenges/classic/desktop-layout.tsx @@ -1,16 +1,11 @@ -import { first } from 'lodash-es'; +import { isEmpty } from 'lodash-es'; import React, { useState, useEffect, ReactElement } from 'react'; import { ReflexContainer, ReflexSplitter, ReflexElement } from 'react-reflex'; import { createSelector } from 'reselect'; import { connect } from 'react-redux'; import store from 'store'; -import { sortChallengeFiles } from '../../../../utils/sort-challengefiles'; import { challengeTypes } from '../../../../../shared/config/challenge-types'; -import { - ChallengeFile, - ChallengeFiles, - ResizeProps -} from '../../../redux/prop-types'; +import { ChallengeFiles, ResizeProps } from '../../../redux/prop-types'; import { removePortalWindow, setShowPreviewPortal, @@ -204,12 +199,8 @@ const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => { } }; - const getChallengeFile = () => { - const { challengeFiles } = props; - return first(sortChallengeFiles(challengeFiles) as ChallengeFile[]); - }; - const { + challengeFiles, challengeType, resizeProps, instructions, @@ -235,7 +226,6 @@ const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => { } }, []); - const challengeFile = getChallengeFile(); const projectBasedChallenge = hasEditableBoundaries; const isMultifileProject = challengeType === challengeTypes.multifileCertProject || @@ -301,11 +291,8 @@ const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => { {...resizeProps} data-playwright-test-label='editor-pane' > - {challengeFile && ( - + {!isEmpty(challengeFiles) && ( + { render() { const { challengeFiles, toggleVisibleEditor, visibleEditors } = this.props; const isMobile = window.innerWidth < MAX_MOBILE_WIDTH; - const isRenderChallengeFiles = - /* eslint-disable-next-line @typescript-eslint/no-unsafe-member-access */ - !isMobile || sortChallengeFiles(challengeFiles).length > 1; + const sortedFiles = sortChallengeFiles(challengeFiles ?? []); + const showTabs = !isMobile || sortedFiles.length > 1; return ( - isRenderChallengeFiles && ( + showTabs && (
- { - /* eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */ - sortChallengeFiles(challengeFiles).map( - (challengeFile: ChallengeFile) => ( - - ) - ) - } + {sortedFiles.map((challengeFile: ChallengeFile) => ( + + ))}
) ); diff --git a/client/src/templates/Challenges/classic/editor.tsx b/client/src/templates/Challenges/classic/editor.tsx index 73ce85c5e99..717e469fd62 100644 --- a/client/src/templates/Challenges/classic/editor.tsx +++ b/client/src/templates/Challenges/classic/editor.tsx @@ -25,10 +25,9 @@ import { isSignedInSelector, themeSelector } from '../../../redux/selectors'; -import { +import type { ChallengeFiles, Dimensions, - FileKey, ResizeProps, Test } from '../../../redux/prop-types'; @@ -85,7 +84,7 @@ export interface EditorProps { dimensions?: Dimensions; editorRef: MutableRefObject; executeChallenge: (options?: { showCompletionModal: boolean }) => void; - fileKey: FileKey; + fileKey: string; canFocusOnMountRef: MutableRefObject; initTests: (tests: Test[]) => void; initialTests: Test[]; @@ -108,7 +107,7 @@ export interface EditorProps { showProjectPreview: boolean; previewOpen: boolean; updateFile: (object: { - fileKey: FileKey; + fileKey: string; editorValue: string; editableRegionBoundaries?: number[]; }) => void; diff --git a/client/src/templates/Challenges/classic/multifile-editor.tsx b/client/src/templates/Challenges/classic/multifile-editor.tsx index 1d2a494bd70..9eaaaba1052 100644 --- a/client/src/templates/Challenges/classic/multifile-editor.tsx +++ b/client/src/templates/Challenges/classic/multifile-editor.tsx @@ -10,7 +10,6 @@ import { } from '../redux/selectors'; import { getTargetEditor } from '../utils/get-target-editor'; import './editor.css'; -import { FileKey } from '../../../redux/prop-types'; import Editor, { type EditorProps } from './editor'; export type VisibleEditors = { @@ -146,7 +145,7 @@ const MultifileEditor = (props: MultifileEditorProps) => { containerRef={containerRef} description={targetEditor === key ? description : ''} editorRef={editorRef} - fileKey={key as FileKey} + fileKey={key} initialTests={initialTests} isMobileLayout={isMobileLayout} isUsingKeyboardInTablist={isUsingKeyboardInTablist} diff --git a/client/src/templates/Challenges/utils/get-target-editor.ts b/client/src/templates/Challenges/utils/get-target-editor.ts index 1303de55c2b..0dd4298e75d 100644 --- a/client/src/templates/Challenges/utils/get-target-editor.ts +++ b/client/src/templates/Challenges/utils/get-target-editor.ts @@ -1,10 +1,8 @@ import { isEmpty } from 'lodash-es'; import { sortChallengeFiles } from '../../../../utils/sort-challengefiles'; -import { ChallengeFiles, FileKey } from '../../../redux/prop-types'; +import { ChallengeFiles } from '../../../redux/prop-types'; -export function getTargetEditor( - challengeFiles: ChallengeFiles -): FileKey | null { +export function getTargetEditor(challengeFiles: ChallengeFiles): string | null { if (isEmpty(challengeFiles)) return null; const targetEditor = challengeFiles?.find( @@ -12,6 +10,6 @@ export function getTargetEditor( )?.fileKey; // fallback for when there is no editable region. - /* eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return */ - return targetEditor || sortChallengeFiles(challengeFiles)[0].fileKey; + + return targetEditor || sortChallengeFiles(challengeFiles ?? [])[0].fileKey; } diff --git a/client/utils/gatsby/challenge-page-creator.js b/client/utils/gatsby/challenge-page-creator.js index 42544bec07f..1d0c01aa8ec 100644 --- a/client/utils/gatsby/challenge-page-creator.js +++ b/client/utils/gatsby/challenge-page-creator.js @@ -1,5 +1,4 @@ const path = require('path'); -const { sortChallengeFiles } = require('../sort-challengefiles'); const { viewTypes } = require('../../../shared/config/challenge-types'); const backend = path.resolve( @@ -138,15 +137,16 @@ function getProjectPreviewConfig(challenge, allChallengeEdges) { .filter(({ node: { challenge } }) => challenge.block === block) .map(({ node: { challenge } }) => challenge); const lastChallenge = challengesInBlock[challengesInBlock.length - 1]; - const solutionToLastChallenge = sortChallengeFiles( - lastChallenge.solutions[0] ?? [] - ); - const lastChallengeFiles = sortChallengeFiles( - lastChallenge.challengeFiles ?? [] - ); - const projectPreviewChallengeFiles = lastChallengeFiles.map((file, id) => ({ + const solutionFiles = lastChallenge.solutions[0] ?? []; + const lastChallengeFiles = lastChallenge.challengeFiles ?? []; + + const findFileByKey = (key, files) => + files.find(file => file.fileKey === key); + + const projectPreviewChallengeFiles = lastChallengeFiles.map(file => ({ ...file, - contents: solutionToLastChallenge[id]?.contents ?? file.contents + contents: + findFileByKey(file.fileKey, solutionFiles)?.contents ?? file.contents })); return { diff --git a/client/utils/sort-challengefiles.js b/client/utils/sort-challengefiles.js deleted file mode 100644 index d44461524f5..00000000000 --- a/client/utils/sort-challengefiles.js +++ /dev/null @@ -1,17 +0,0 @@ -exports.sortChallengeFiles = function sortChallengeFiles(challengeFiles) { - const xs = challengeFiles.slice(); - xs.sort((a, b) => { - if (a.history[0] === 'index.jsx') return -1; - if (b.history[0] === 'index.jsx') return 1; - if (a.history[0] === 'index.html') return -1; - if (b.history[0] === 'index.html') return 1; - if (a.history[0] === 'styles.css') return -1; - if (b.history[0] === 'styles.css') return 1; - if (a.history[0] === 'script.js') return -1; - if (b.history[0] === 'script.js') return 1; - if (a.history[0] === 'index.ts') return -1; - if (b.history[0] === 'index.ts') return 1; - return 0; - }); - return xs; -}; diff --git a/client/utils/sort-challengefiles.test.js b/client/utils/sort-challengefiles.test.ts similarity index 87% rename from client/utils/sort-challengefiles.test.js rename to client/utils/sort-challengefiles.test.ts index 6994c7a2f40..34b658e04e2 100644 --- a/client/utils/sort-challengefiles.test.js +++ b/client/utils/sort-challengefiles.test.ts @@ -1,5 +1,5 @@ -const { challengeFiles } = require('./__fixtures__/challenges'); -const { sortChallengeFiles } = require('./sort-challengefiles'); +import { challengeFiles } from './__fixtures__/challenges'; +import { sortChallengeFiles } from './sort-challengefiles'; describe('sort-files', () => { describe('sortChallengeFiles', () => { diff --git a/client/utils/sort-challengefiles.ts b/client/utils/sort-challengefiles.ts new file mode 100644 index 00000000000..3656061b830 --- /dev/null +++ b/client/utils/sort-challengefiles.ts @@ -0,0 +1,17 @@ +export function sortChallengeFiles( + challengeFiles: File[] +): File[] { + return challengeFiles.toSorted((a, b) => { + if (a.fileKey === 'indexjsx') return -1; + if (b.fileKey === 'indexjsx') return 1; + if (a.fileKey === 'indexhtml') return -1; + if (b.fileKey === 'indexhtml') return 1; + if (a.fileKey === 'stylescss') return -1; + if (b.fileKey === 'stylescss') return 1; + if (a.fileKey === 'scriptjs') return -1; + if (b.fileKey === 'scriptjs') return 1; + if (a.fileKey === 'indexts') return -1; + if (b.fileKey === 'indexts') return 1; + return 0; + }); +}