diff --git a/package.json b/package.json index 09d25c3930..79f253d895 100644 --- a/package.json +++ b/package.json @@ -58,9 +58,10 @@ "ag-grid-react": "^34.1.1", "array-move": "^4.0.0", "browserfs": "^1.4.3", - "classnames": "^2.3.2", + "clsx": "^2.1.1", "dayjs": "^1.11.13", "dompurify": "^3.2.4", + "es-toolkit": "^1.39.9", "flexboxgrid": "^6.3.1", "flexboxgrid-helpers": "^1.1.3", "hastscript": "^9.0.0", @@ -72,7 +73,6 @@ "js-slang": "^1.0.88", "js-yaml": "^4.1.0", "konva": "^10.0.0", - "lodash": "^4.17.21", "lz-string": "^1.4.4", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-hast": "^13.0.0", @@ -140,7 +140,6 @@ "@types/identity-obj-proxy": "^3", "@types/js-cookie": "^3.0.6", "@types/js-yaml": "^4.0.5", - "@types/lodash": "^4.14.195", "@types/react": "^19.1.8", "@types/react-copy-to-clipboard": "^5.0.4", "@types/react-dom": "^19.1.6", @@ -200,6 +199,7 @@ }, "resolutions": { "@types/estree": "1.0.8", + "classnames": "npm:clsx@2.1.1", "vite": "^8.0.0" } } diff --git a/src/commons/Markdown.tsx b/src/commons/Markdown.tsx index 0a15c40e9a..c59d7a0180 100644 --- a/src/commons/Markdown.tsx +++ b/src/commons/Markdown.tsx @@ -1,5 +1,5 @@ import { Classes } from '@blueprintjs/core'; -import classNames from 'classnames'; +import clsx from 'clsx'; import DOMPurify from 'dompurify'; import React from 'react'; import { Converter } from 'showdown'; @@ -24,7 +24,7 @@ const Markdown: React.FC = props => { return (
{ if (!isApiHealthy) { return ( -
+
( -
+
= ({ return (
-
+
= ({ src={overview.coverImage ? overview.coverImage : defaultCoverImage} />
-
+
= props => { if (!assessment?.questions.length) { return ( } /> @@ -1046,7 +1046,7 @@ It is safe to close this window.`} mobileSideContentProps: mobileSideContentProps(questionId) }; return ( -
+
{submissionOverlay} {overlay} {resetTemplateOverlay} diff --git a/src/commons/controlBar/ControlBar.tsx b/src/commons/controlBar/ControlBar.tsx index 93dcb683fd..9b3bd04d82 100644 --- a/src/commons/controlBar/ControlBar.tsx +++ b/src/commons/controlBar/ControlBar.tsx @@ -1,5 +1,5 @@ import { Classes } from '@blueprintjs/core'; -import classNames from 'classnames'; +import clsx from 'clsx'; import React, { type JSX } from 'react'; export type ControlBarProps = { @@ -10,17 +10,15 @@ export type ControlBarProps = { const ControlBar: React.FC = props => { const editorControl = ( -
- {props.editorButtons} -
+
{props.editorButtons}
); const flowControl = props.flowButtons && ( -
{props.flowButtons}
+
{props.flowButtons}
); const editingWorkspaceControl = ( -
+
{props.editingWorkspaceButtons}
); diff --git a/src/commons/dialogs/ConfirmDialog.tsx b/src/commons/dialogs/ConfirmDialog.tsx index 81f2dfbad9..281746988c 100644 --- a/src/commons/dialogs/ConfirmDialog.tsx +++ b/src/commons/dialogs/ConfirmDialog.tsx @@ -9,7 +9,7 @@ import { IconName, Intent } from '@blueprintjs/core'; -import classNames from 'classnames'; +import clsx from 'clsx'; import React from 'react'; import classes from 'src/styles/ConfirmDialog.module.scss'; @@ -34,7 +34,7 @@ export function ConfirmDialog( onClick={() => props.onResponse && props.onResponse(choice.key)} intent={choice.intent} fill={props.largeButtons} - className={classNames(props.largeButtons && classes['large-button'])} + className={clsx(props.largeButtons && classes['large-button'])} {...choice.props} > {choice.label} @@ -47,7 +47,7 @@ export function ConfirmDialog( : () => props.onResponse && props.onResponse(escapeResponse); return ( = props => { if (assessment === null || assessment!.questions.length === 0) { return ( } /> @@ -707,7 +707,7 @@ const EditingWorkspace: React.FC = props => { } }; return ( -
+
{resetTemplateOverlay()}
diff --git a/src/commons/editor/EditorContainer.tsx b/src/commons/editor/EditorContainer.tsx index 1f116885b7..29731899fc 100644 --- a/src/commons/editor/EditorContainer.tsx +++ b/src/commons/editor/EditorContainer.tsx @@ -1,4 +1,4 @@ -import _ from 'lodash'; +import { pick } from 'es-toolkit'; import React from 'react'; import SourcecastEditor, { @@ -40,7 +40,7 @@ export const convertEditorTabStateToProps = ( return { editorTabIndex, editorValue: editorTab.value, - ..._.pick(editorTab, 'filePath', 'highlightedLines', 'breakpoints', 'newCursorPosition') + ...pick(editorTab, ['filePath', 'highlightedLines', 'breakpoints', 'newCursorPosition']) }; }; diff --git a/src/commons/editor/tabs/EditorTab.tsx b/src/commons/editor/tabs/EditorTab.tsx index 9eabde9332..e8d0c97abb 100644 --- a/src/commons/editor/tabs/EditorTab.tsx +++ b/src/commons/editor/tabs/EditorTab.tsx @@ -1,6 +1,6 @@ import { Card, Icon } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import classNames from 'classnames'; +import clsx from 'clsx'; import React from 'react'; type Props = { @@ -19,7 +19,7 @@ const EditorTab: React.FC = ({ filePath, isActive, setActive, remove }) = return ( = ({
{children} toggleMenu(false)} diff --git a/src/commons/gitHubOverlay/FileExplorerDialog.tsx b/src/commons/gitHubOverlay/FileExplorerDialog.tsx index 6afffd2236..c611a605a8 100644 --- a/src/commons/gitHubOverlay/FileExplorerDialog.tsx +++ b/src/commons/gitHubOverlay/FileExplorerDialog.tsx @@ -12,7 +12,7 @@ import { } from '@blueprintjs/core'; import { Octokit } from '@octokit/rest'; import { GetResponseTypeFromEndpointMethod } from '@octokit/types'; -import classNames from 'classnames'; +import clsx from 'clsx'; import React, { useEffect, useState } from 'react'; import { @@ -46,7 +46,7 @@ const FileExplorerDialog: React.FC = props => { return ( -
+

Select a File

@@ -55,7 +55,7 @@ const FileExplorerDialog: React.FC = props => { onNodeClick={handleNodeClick} onNodeCollapse={handleNodeCollapse} onNodeExpand={handleNodeExpand} - className={classNames('FileTree', Classes.ELEVATION_0)} + className={clsx('FileTree', Classes.ELEVATION_0)} /> {props.pickerType === 'Save' && (
diff --git a/src/commons/gitHubOverlay/RepositoryDialog.tsx b/src/commons/gitHubOverlay/RepositoryDialog.tsx index 97e539c753..fad3616051 100644 --- a/src/commons/gitHubOverlay/RepositoryDialog.tsx +++ b/src/commons/gitHubOverlay/RepositoryDialog.tsx @@ -9,7 +9,7 @@ import { Radio, RadioGroup } from '@blueprintjs/core'; -import classNames from 'classnames'; +import clsx from 'clsx'; import React, { useState } from 'react'; import { showWarningMessage } from '../utils/notifications/NotificationsHelper'; @@ -24,7 +24,7 @@ const RepositoryDialog: React.FC = props => { return ( -
+

Select a Repository

diff --git a/src/commons/grading/GradingText.tsx b/src/commons/grading/GradingText.tsx index a96d7bb75a..0b61f0e4b3 100644 --- a/src/commons/grading/GradingText.tsx +++ b/src/commons/grading/GradingText.tsx @@ -1,5 +1,5 @@ import { Classes, Text } from '@blueprintjs/core'; -import classNames from 'classnames'; +import clsx from 'clsx'; import React from 'react'; const defaultStyles: React.CSSProperties = { @@ -17,7 +17,7 @@ type Props = { const GradingText: React.FC = ({ children, style, isSecondaryText, className }) => { return ( {children} diff --git a/src/commons/mobileWorkspace/mobileSideContent/MobileSideContent.tsx b/src/commons/mobileWorkspace/mobileSideContent/MobileSideContent.tsx index 0dd3ad44cc..94edebaa7b 100644 --- a/src/commons/mobileWorkspace/mobileSideContent/MobileSideContent.tsx +++ b/src/commons/mobileWorkspace/mobileSideContent/MobileSideContent.tsx @@ -1,5 +1,5 @@ import { Classes, Icon, Tab, Tabs, Tooltip } from '@blueprintjs/core'; -import classNames from 'classnames'; +import clsx from 'clsx'; import React, { type JSX } from 'react'; import { SideContentProps } from 'src/commons/sideContent/SideContent'; import { generateIconId } from 'src/commons/sideContent/SideContentHelper'; @@ -106,7 +106,7 @@ const MobileSideContent: React.FC = ({ onChange={onChange} renderActiveTabPanelOnly={renderActiveTabPanelOnly} selectedTabId={selectedTab} - className={classNames(Classes.DARK, 'mobile-side-content')} + className={clsx(Classes.DARK, 'mobile-side-content')} > {allTabs.map(tab => renderTab(tab, isIOS))} diff --git a/src/commons/mocks/StoreMocks.ts b/src/commons/mocks/StoreMocks.ts index 9a2a914593..f83e5518f2 100644 --- a/src/commons/mocks/StoreMocks.ts +++ b/src/commons/mocks/StoreMocks.ts @@ -1,5 +1,5 @@ import { Store } from '@reduxjs/toolkit'; -import _ from 'lodash'; +import { isObject, mergeWith } from 'es-toolkit/compat'; import mockStore from 'redux-mock-store'; import { @@ -44,7 +44,7 @@ export function mockInitialStore( }; const lodashMergeCustomizer = (objValue: any, srcValue: any) => { - if (_.isObject(objValue)) { + if (isObject(objValue)) { return { ...objValue, // destination object ...srcValue // overrides @@ -52,5 +52,5 @@ export function mockInitialStore( } }; - return createStore(_.mergeWith(state, overrides, lodashMergeCustomizer)); + return createStore(mergeWith(state, overrides, lodashMergeCustomizer)); } diff --git a/src/commons/navigationBar/NavigationBar.tsx b/src/commons/navigationBar/NavigationBar.tsx index 1093e1cbb8..4b3ea71778 100644 --- a/src/commons/navigationBar/NavigationBar.tsx +++ b/src/commons/navigationBar/NavigationBar.tsx @@ -13,7 +13,7 @@ import { Position } from '@blueprintjs/core'; import { IconName, IconNames } from '@blueprintjs/icons'; -import classNames from 'classnames'; +import clsx from 'clsx'; import React, { useMemo, useState } from 'react'; import { Translation } from 'react-i18next'; import { type Location, NavLink, Route, useLocation } from 'react-router'; @@ -273,7 +273,7 @@ const NavigationBar: React.FC = () => { - classNames('NavigationBar__link', Classes.BUTTON, Classes.MINIMAL, { + clsx('NavigationBar__link', Classes.BUTTON, Classes.MINIMAL, { [Classes.ACTIVE]: isActive }) } @@ -296,12 +296,7 @@ const NavigationBar: React.FC = () => { return ( <> {Constants.playgroundOnly ? isMobileBreakpoint @@ -358,7 +353,7 @@ export const DesktopNavLink: React.FC = props => { const shouldHide = props.hiddenInBreakpoints?.some(bp => responsive[bp]); return props.disabled ? null : ( classNames(isActive && Classes.ACTIVE)} + className={({ isActive }) => clsx(isActive && Classes.ACTIVE)} to={props.to} key={props.text} title={props.text} @@ -390,7 +385,7 @@ const MobileNavLink: React.FC< props.disabled ? null : ( classNames(isActive && Classes.ACTIVE)} + className={({ isActive }) => clsx(isActive && Classes.ACTIVE)} onClick={props.handleClick} key={props.text} > diff --git a/src/commons/repl/Repl.tsx b/src/commons/repl/Repl.tsx index 75ed49bb5e..2ccdcadbbe 100644 --- a/src/commons/repl/Repl.tsx +++ b/src/commons/repl/Repl.tsx @@ -1,6 +1,6 @@ import { Card, Pre } from '@blueprintjs/core'; import { Ace } from 'ace-builds'; -import classNames from 'classnames'; +import clsx from 'clsx'; import { parseError } from 'js-slang'; import { Chapter, Variant } from 'js-slang/dist/langs'; import { stringify } from 'js-slang/dist/utils/stringify'; @@ -52,7 +52,7 @@ const Repl: React.FC = props => {
{cards} {!props.inputHidden && ( - + )} diff --git a/src/commons/repl/ReplInput.tsx b/src/commons/repl/ReplInput.tsx index 105ca0fa94..2f7400f812 100644 --- a/src/commons/repl/ReplInput.tsx +++ b/src/commons/repl/ReplInput.tsx @@ -1,6 +1,6 @@ import { Classes } from '@blueprintjs/core'; import { Ace } from 'ace-builds'; -import classNames from 'classnames'; +import clsx from 'clsx'; import { Chapter, Variant } from 'js-slang/dist/langs'; import React, { type JSX } from 'react'; import AceEditor from 'react-ace'; @@ -139,7 +139,7 @@ export const ReplInput: React.FC = props => { }} ref={replInput} /> -
{replButtons()}
+
{replButtons()}
{isDesktopBreakpoint &&
} ); diff --git a/src/commons/sagas/BackendSaga.ts b/src/commons/sagas/BackendSaga.ts index 9e45231bf3..da35c05f89 100644 --- a/src/commons/sagas/BackendSaga.ts +++ b/src/commons/sagas/BackendSaga.ts @@ -1,4 +1,5 @@ -import _ from 'lodash'; +import { camelCase } from 'es-toolkit'; +import { mapKeys } from 'es-toolkit/compat'; import type { SagaIterator } from 'redux-saga'; import { all, call, fork, put, select } from 'redux-saga/effects'; import AcademyActions from 'src/features/academy/AcademyActions'; @@ -149,7 +150,7 @@ const newBackendSagaOne = combineSagaHandlers({ }, [SessionActions.handleSamlRedirect.type]: function* (action) { const { jwtCookie } = action.payload; - const tokens = _.mapKeys(JSON.parse(jwtCookie), (v, k) => _.camelCase(k)) as Tokens; + const tokens = mapKeys(JSON.parse(jwtCookie), (v, k) => camelCase(k)) as Tokens; yield put(actions.setTokens(tokens)); yield put(actions.fetchUserAndCourse()); diff --git a/src/commons/sagas/RemoteExecutionSaga.ts b/src/commons/sagas/RemoteExecutionSaga.ts index 90d1ce2538..5ce0a0a262 100644 --- a/src/commons/sagas/RemoteExecutionSaga.ts +++ b/src/commons/sagas/RemoteExecutionSaga.ts @@ -1,8 +1,8 @@ import { SlingClient } from '@sourceacademy/sling-client'; +import { pickBy } from 'es-toolkit/compat'; import { assemble, compileFiles, type Context } from 'js-slang'; import { ExceptionError } from 'js-slang/dist/errors/errors'; import { Chapter, Variant } from 'js-slang/dist/langs'; -import _ from 'lodash'; import { call, put, race, select, take } from 'redux-saga/effects'; import RemoteExecutionActions from 'src/features/remoteExecution/RemoteExecutionActions'; import { @@ -126,7 +126,7 @@ const RemoteExecutionSaga = combineSagaHandlers({ device: { ...currentSession.device, peripherals: { - ..._.pickBy( + ...pickBy( currentSession.device.peripherals, p => Date.now() - p.lastUpdated < 3000 ), diff --git a/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts b/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts index d56b67c26e..dea802d4af 100644 --- a/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts +++ b/src/commons/sagas/WorkspaceSaga/helpers/evalCode.ts @@ -2,12 +2,12 @@ import { compileAndRun as compileAndRunCCode } from '@sourceacademy/c-slang/ctow import type { IConduit } from '@sourceacademy/conductor/conduit'; import { IEvaluatorDefinition } from '@sourceacademy/language-directory/dist/types'; import { tokenizer } from 'acorn'; +import { pick } from 'es-toolkit'; import { type Context, interrupt, type Result, resume, runFilesInContext } from 'js-slang'; import { ACORN_PARSE_OPTIONS } from 'js-slang/dist/constants'; import { ErrorSeverity, ErrorType, type SourceError } from 'js-slang/dist/errors/base'; import { InterruptedError } from 'js-slang/dist/errors/errors'; import { Chapter, Variant } from 'js-slang/dist/langs'; -import { pick } from 'lodash'; import { eventChannel, type SagaIterator } from 'redux-saga'; import { call, cancel, cancelled, fork, put, race, select, take } from 'redux-saga/effects'; import * as Sourceror from 'sourceror'; diff --git a/src/commons/sagas/WorkspaceSaga/helpers/runTestCase.ts b/src/commons/sagas/WorkspaceSaga/helpers/runTestCase.ts index 35452c49cd..5ad30228e6 100644 --- a/src/commons/sagas/WorkspaceSaga/helpers/runTestCase.ts +++ b/src/commons/sagas/WorkspaceSaga/helpers/runTestCase.ts @@ -1,5 +1,5 @@ +import { random } from 'es-toolkit/compat'; import type { Context } from 'js-slang'; -import { random } from 'lodash'; import { call, put, select, StrictEffect } from 'redux-saga/effects'; import type { OverallState } from '../../../application/ApplicationTypes'; diff --git a/src/commons/sideBar/SideBar.tsx b/src/commons/sideBar/SideBar.tsx index 27581bbe27..1b65e86c9f 100644 --- a/src/commons/sideBar/SideBar.tsx +++ b/src/commons/sideBar/SideBar.tsx @@ -1,5 +1,5 @@ import { Card, Icon, IconName } from '@blueprintjs/core'; -import classNames from 'classnames'; +import clsx from 'clsx'; import React, { type JSX } from 'react'; import { SideContentType } from '../sideContent/SideContentTypes'; @@ -52,7 +52,7 @@ const SideBar: React.FC = ({ tabs, isExpanded, expandSideBar, collapseSid {tabs.map((tab, index) => ( handleTabSelection(index)} diff --git a/src/commons/sideContent/SideContentHelper.ts b/src/commons/sideContent/SideContentHelper.ts index bd5ed857da..eae3788c61 100644 --- a/src/commons/sideContent/SideContentHelper.ts +++ b/src/commons/sideContent/SideContentHelper.ts @@ -1,9 +1,9 @@ import * as bpcore from '@blueprintjs/core'; import { TabId } from '@blueprintjs/core'; import * as bpicons from '@blueprintjs/icons'; +import compat from 'es-toolkit/compat'; import * as jsslang from 'js-slang'; import * as jsslangDist from 'js-slang/dist'; -import lodash from 'lodash'; import phaser from 'phaser'; import React, { useCallback } from 'react'; import JSXRuntime from 'react/jsx-runtime'; @@ -35,7 +35,7 @@ const requireProvider = (x: string) => { '@blueprintjs/icons': bpicons, 'js-slang': jsslang, 'js-slang/dist': jsslangDist, - lodash, + lodash: compat, phaser }; diff --git a/src/commons/sideContent/content/SideContentContestVoting.tsx b/src/commons/sideContent/content/SideContentContestVoting.tsx index 2087965baf..2203150a46 100644 --- a/src/commons/sideContent/content/SideContentContestVoting.tsx +++ b/src/commons/sideContent/content/SideContentContestVoting.tsx @@ -1,6 +1,6 @@ import { Button, Card, Classes, Collapse, Elevation, Icon, Pre, Tooltip } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import classNames from 'classnames'; +import clsx from 'clsx'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -105,7 +105,7 @@ const SideContentContestVoting: React.FC = ({ const tierBoard = useMemo(() => { return TIERS.map((tier, index) => (
= ({ > {sortedContestEntries.map((contestEntry: ContestEntry, index) => (
overflow: this.state.visualization ? 'hidden' : 'auto' }} > -
+
-
+
{this.state.steps.length > 1 ? (
{step.length > 1 && (
Structure {i + 1} diff --git a/src/commons/sideContent/content/SideContentLeaderboardCard.tsx b/src/commons/sideContent/content/SideContentLeaderboardCard.tsx index f96b7b457c..3e330bef00 100644 --- a/src/commons/sideContent/content/SideContentLeaderboardCard.tsx +++ b/src/commons/sideContent/content/SideContentLeaderboardCard.tsx @@ -1,5 +1,5 @@ import { Card, Classes, Elevation, Pre } from '@blueprintjs/core'; -import classNames from 'classnames'; +import clsx from 'clsx'; import React from 'react'; import { ContestEntry } from '../../assessment/AssessmentTypes'; @@ -21,7 +21,7 @@ const SideContentLeaderboardCard: React.FC = ({ rank }) => { return ( -
+
= ({ index, result }) => { const { t } = useTranslation('sideContent', { keyPrefix: 'resultCard' }); return (
diff --git a/src/commons/sideContent/content/SideContentSessionManagement.tsx b/src/commons/sideContent/content/SideContentSessionManagement.tsx index 7d2bbbc841..03938294bc 100644 --- a/src/commons/sideContent/content/SideContentSessionManagement.tsx +++ b/src/commons/sideContent/content/SideContentSessionManagement.tsx @@ -1,7 +1,7 @@ import { Classes, HTMLTable, Icon, Switch } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import { CollabEditingAccess, type SharedbAceUser } from '@sourceacademy/sharedb-ace/types'; -import classNames from 'classnames'; +import clsx from 'clsx'; import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; @@ -75,7 +75,7 @@ function AdminView({ users, playgroundCode }: AdminViewProps) { alignIndicator="left" checked={toggleAll} onChange={event => handleAllToggleAccess(event.target.checked)} - className={classNames(classes['switch'], classes['default-switch'])} + className={clsx(classes['switch'], classes['default-switch'])} />
@@ -86,7 +86,7 @@ function AdminView({ users, playgroundCode }: AdminViewProps) { alignIndicator="left" checked={defaultRole} onChange={event => handleDefaultToggleAccess(event.target.checked)} - className={classNames(classes['switch'], classes['default-switch'])} + className={clsx(classes['switch'], classes['default-switch'])} /> @@ -99,7 +99,7 @@ function AdminView({ users, playgroundCode }: AdminViewProps) { {Object.entries(users).map(([userId, user], index) => ( - +
{user.name}
@@ -187,14 +187,14 @@ const SideContentSessionManagement: React.FC = ({ {Object.values(users).map((user, index) => { return ( - +
{user.name}
- + {user.role === CollabEditingAccess.OWNER ? 'Admin' : user.role.charAt(0).toUpperCase() + user.role.slice(1)} diff --git a/src/commons/sideContent/content/SideContentSubstVisualizer.tsx b/src/commons/sideContent/content/SideContentSubstVisualizer.tsx index ff91ec09d4..263523517a 100644 --- a/src/commons/sideContent/content/SideContentSubstVisualizer.tsx +++ b/src/commons/sideContent/content/SideContentSubstVisualizer.tsx @@ -13,7 +13,7 @@ import { Slider } from '@blueprintjs/core'; import { getHotkeyHandler, HotkeyItem } from '@mantine/hooks'; -import classNames from 'classnames'; +import clsx from 'clsx'; import { HighlightRulesSelector, ModeSelector } from 'js-slang/dist/editors/ace/modes/source'; import { IStepperPropContents } from 'js-slang/dist/tracer'; import { StepperBaseNode } from 'js-slang/dist/tracer/interface'; @@ -158,7 +158,7 @@ const SideContentSubstVisualizer: React.FC = props => { return (
@@ -260,7 +260,7 @@ const FunctionDefinitionPopoverContent: React.FC { return ( -
+
{' Function definition'} diff --git a/src/commons/sideContent/content/SideContentTestcaseCard.tsx b/src/commons/sideContent/content/SideContentTestcaseCard.tsx index a8342d6cb2..e66dfd2a80 100644 --- a/src/commons/sideContent/content/SideContentTestcaseCard.tsx +++ b/src/commons/sideContent/content/SideContentTestcaseCard.tsx @@ -1,5 +1,5 @@ import { Card, Classes, Elevation, Pre } from '@blueprintjs/core'; -import classNames from 'classnames'; +import clsx from 'clsx'; import { parseError } from 'js-slang'; import { stringify } from 'js-slang/dist/utils/stringify'; import React from 'react'; @@ -61,7 +61,7 @@ const SideContentTestcaseCard: React.FC = props => * be rendered in the GitHubAssessmentWorkspace for students. */ return ( -
+
{testcase.type === TestcaseTypes.opaque && props.workspaceLocation === 'assessment' ? ( // Render a placeholder cell in place of the actual testcase data for opaque testcases diff --git a/src/commons/sideContent/content/SideContentToneMatrix.tsx b/src/commons/sideContent/content/SideContentToneMatrix.tsx index 4c4a307d20..1991c1d0a2 100644 --- a/src/commons/sideContent/content/SideContentToneMatrix.tsx +++ b/src/commons/sideContent/content/SideContentToneMatrix.tsx @@ -1,5 +1,5 @@ import { Button, Classes } from '@blueprintjs/core'; -import classNames from 'classnames'; +import clsx from 'clsx'; import React, { useEffect, useRef } from 'react'; const SideContentToneMatrix: React.FC = () => { @@ -22,7 +22,7 @@ const SideContentToneMatrix: React.FC = () => { return (
-
+
diff --git a/src/commons/sideContent/content/githubAssessments/SideContentEditableTestcaseCard.tsx b/src/commons/sideContent/content/githubAssessments/SideContentEditableTestcaseCard.tsx index 2407f595eb..1a7afc4431 100644 --- a/src/commons/sideContent/content/githubAssessments/SideContentEditableTestcaseCard.tsx +++ b/src/commons/sideContent/content/githubAssessments/SideContentEditableTestcaseCard.tsx @@ -1,6 +1,6 @@ import { Button, Card, Classes, Elevation, InputGroup } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import classNames from 'classnames'; +import clsx from 'clsx'; import { parseError } from 'js-slang'; import { stringify } from 'js-slang/dist/utils/stringify'; import React from 'react'; @@ -72,7 +72,7 @@ const SideContentEditableTestcaseCard: React.FC +
{ <> diff --git a/src/commons/sideContent/content/remoteExecution/SideContentRemoteExecution.tsx b/src/commons/sideContent/content/remoteExecution/SideContentRemoteExecution.tsx index 6f5c04d1d0..55ae7372d6 100644 --- a/src/commons/sideContent/content/remoteExecution/SideContentRemoteExecution.tsx +++ b/src/commons/sideContent/content/remoteExecution/SideContentRemoteExecution.tsx @@ -7,7 +7,7 @@ import { NonIdealState, Spinner } from '@blueprintjs/core'; -import classNames from 'classnames'; +import clsx from 'clsx'; import React, { SetStateAction, useCallback } from 'react'; import { useDispatch } from 'react-redux'; import { NavLink } from 'react-router'; @@ -150,7 +150,7 @@ const SideContentRemoteExecution: React.FC = pr
- + dispatch(actions.remoteExecDisconnect())} diff --git a/src/commons/sourceRecorder/SourceRecorderEditor.tsx b/src/commons/sourceRecorder/SourceRecorderEditor.tsx index 5a70034a2f..5041c08100 100644 --- a/src/commons/sourceRecorder/SourceRecorderEditor.tsx +++ b/src/commons/sourceRecorder/SourceRecorderEditor.tsx @@ -4,7 +4,7 @@ import 'js-slang/dist/editors/ace/theme/source'; import { Card } from '@blueprintjs/core'; import { Ace } from 'ace-builds'; -import { isEqual } from 'lodash'; +import { isEqual } from 'es-toolkit'; import React from 'react'; import AceEditor, { IAceEditorProps } from 'react-ace'; diff --git a/src/commons/sourceRecorder/SourceRecorderTable.tsx b/src/commons/sourceRecorder/SourceRecorderTable.tsx index 7c98c08ddc..e8ee106c15 100644 --- a/src/commons/sourceRecorder/SourceRecorderTable.tsx +++ b/src/commons/sourceRecorder/SourceRecorderTable.tsx @@ -9,7 +9,7 @@ import { import { ColDef, GridApi, GridReadyEvent } from 'ag-grid-community'; import { themeBalham } from 'ag-grid-community'; import { AgGridReact } from 'ag-grid-react'; -import { sortBy } from 'lodash'; +import { sortBy } from 'es-toolkit/compat'; import React from 'react'; import { PlaybackData, SourcecastData } from '../../features/sourceRecorder/SourceRecorderTypes'; diff --git a/src/commons/utils/JsSlangHelper.ts b/src/commons/utils/JsSlangHelper.ts index 0fa4c03b80..e94e3d6c61 100644 --- a/src/commons/utils/JsSlangHelper.ts +++ b/src/commons/utils/JsSlangHelper.ts @@ -1,9 +1,10 @@ /* tslint:disable: ban-types*/ +import { difference } from 'es-toolkit'; +import { keys } from 'es-toolkit/compat'; import createSlangContext, { defineBuiltin, importBuiltins } from 'js-slang/dist/createContext'; import { type Chapter, LanguageOptions, Variant } from 'js-slang/dist/langs'; import { type Context, type CustomBuiltIns, type Value } from 'js-slang/dist/types'; import { stringify } from 'js-slang/dist/utils/stringify'; -import { difference, keys } from 'lodash'; import CseMachine from 'src/features/cseMachine/CseMachine'; import DataVisualizer from '../../features/dataVisualizer/dataVisualizer'; diff --git a/src/commons/utils/MemoizeHelper.ts b/src/commons/utils/MemoizeHelper.ts index da39edaa0a..9e7d17f4bc 100644 --- a/src/commons/utils/MemoizeHelper.ts +++ b/src/commons/utils/MemoizeHelper.ts @@ -1,4 +1,4 @@ -import * as _ from 'lodash'; +import { isEqual } from 'es-toolkit'; /** * Performs a deep comparison between the previous & next props state @@ -9,4 +9,4 @@ import * as _ from 'lodash'; * @param nextProps The next state of the props passed into a component */ export const propsAreEqual = (prevProps: T, nextProps: T): boolean => - _.isEqual(prevProps, nextProps); + isEqual(prevProps, nextProps); diff --git a/src/commons/utils/RequestHelper.tsx b/src/commons/utils/RequestHelper.tsx index dbcf9dd0f4..aa51b4ba4f 100644 --- a/src/commons/utils/RequestHelper.tsx +++ b/src/commons/utils/RequestHelper.tsx @@ -1,5 +1,5 @@ import { Button } from '@blueprintjs/core'; -import _ from 'lodash'; +import { cloneDeep } from 'es-toolkit'; import { assessmentFullPathRegex } from 'src/features/academy/AcademyTypes'; import { store } from 'src/pages/createStore'; @@ -91,7 +91,7 @@ export const request = async ( } store.dispatch(actions.setTokens(newTokens)); - const updatedFetchOptions = _.cloneDeep(fetchOptions); + const updatedFetchOptions = cloneDeep(fetchOptions); updatedFetchOptions.headers.set('Authorization', `Bearer ${newTokens.accessToken}`); const retriedResp = await fetch(`${Constants.backendUrl}/v2/${path}`, updatedFetchOptions); diff --git a/src/commons/workspace/__tests__/WorkspaceReducer.test.ts b/src/commons/workspace/__tests__/WorkspaceReducer.test.ts index 2b43499e06..772d3bbd16 100644 --- a/src/commons/workspace/__tests__/WorkspaceReducer.test.ts +++ b/src/commons/workspace/__tests__/WorkspaceReducer.test.ts @@ -1,5 +1,5 @@ +import { cloneDeep } from 'es-toolkit'; import { Chapter, Variant } from 'js-slang/dist/langs'; -import { cloneDeep } from 'lodash'; import CommonsActions from 'src/commons/application/actions/CommonsActions'; import InterpreterActions from 'src/commons/application/actions/InterpreterActions'; import { diff --git a/src/features/cseMachine/CseMachineUtils.ts b/src/features/cseMachine/CseMachineUtils.ts index e42bb4c37d..98603eadea 100644 --- a/src/features/cseMachine/CseMachineUtils.ts +++ b/src/features/cseMachine/CseMachineUtils.ts @@ -1,3 +1,5 @@ +import { cloneDeep } from 'es-toolkit'; +import { isObject } from 'es-toolkit/compat'; import JsSlangClosure from 'js-slang/dist/cse-machine/closure'; import { AppInstr, @@ -17,7 +19,6 @@ import { Group } from 'konva/lib/Group'; import { Node } from 'konva/lib/Node'; import { Shape } from 'konva/lib/Shape'; import { Text } from 'konva/lib/shapes/Text'; -import { cloneDeep, isObject } from 'lodash'; import classes from 'src/styles/Draggable.module.scss'; import { ArrayUnit } from './components/ArrayUnit'; diff --git a/src/features/game/chapter/GameChapterHelpers.ts b/src/features/game/chapter/GameChapterHelpers.ts index aad52ae7c4..cdc351bfe7 100644 --- a/src/features/game/chapter/GameChapterHelpers.ts +++ b/src/features/game/chapter/GameChapterHelpers.ts @@ -1,4 +1,4 @@ -import _ from 'lodash'; +import { sortBy } from 'es-toolkit/compat'; import { request } from 'src/commons/utils/RequestHelper'; import { store } from '../../../pages/createStore'; @@ -19,7 +19,7 @@ export async function fetchGameChapters(): Promise { }); if (!response) return []; const chapterDetails = response.status === 200 ? await response.json() : []; - const sortedChapters = _.sortBy(chapterDetails, chapterDetail => new Date(chapterDetail.openAt)); + const sortedChapters = sortBy(chapterDetails, chapterDetail => new Date(chapterDetail.openAt)); sortedChapters.forEach(chapter => (chapter.filenames = chapter.filenames.map(toTxtPath))); return sortedChapters; } diff --git a/src/features/game/save/GameSaveRequests.ts b/src/features/game/save/GameSaveRequests.ts index 149a7af37e..bec751382f 100644 --- a/src/features/game/save/GameSaveRequests.ts +++ b/src/features/game/save/GameSaveRequests.ts @@ -1,4 +1,4 @@ -import _ from 'lodash'; +import { isEmpty } from 'es-toolkit/compat'; import Constants from 'src/commons/utils/Constants'; import SourceAcademyGame from '../SourceAcademyGame'; @@ -44,7 +44,7 @@ export async function loadData(): Promise { const message = await resp.text(); const json = JSON.parse(message).courseRegistration?.gameStates; - return _.isEmpty(json) ? createEmptySaveState() : json; + return isEmpty(json) ? createEmptySaveState() : json; } /** diff --git a/src/features/game/utils/StyleUtils.ts b/src/features/game/utils/StyleUtils.ts index 071cc14dbd..003cecadc7 100644 --- a/src/features/game/utils/StyleUtils.ts +++ b/src/features/game/utils/StyleUtils.ts @@ -1,4 +1,5 @@ -import _ from 'lodash'; +import { mapValues } from 'es-toolkit'; +import { times } from 'es-toolkit/compat'; import { screenSize } from '../commons/CommonConstants'; @@ -21,7 +22,7 @@ export const Color = { }; const hex = (str: string) => parseInt(str.slice(1), 16); -export const HexColor = _.mapValues(Color, hex); +export const HexColor = mapValues(Color, hex); type TableFormatPosConfig = { direction?: Direction; @@ -64,7 +65,7 @@ export function calcTableFormatPos({ let itemsPerList = numItemLimit || numOfItems; const numOfLists = Math.ceil(numOfItems / itemsPerList); - return _.times(numOfItems, itemNumber => { + return times(numOfItems, itemNumber => { const itemIndexInList = itemNumber % itemsPerList; const listIndex = Math.floor(itemNumber / itemsPerList); diff --git a/src/features/gameSimulator/GameSimulatorService.ts b/src/features/gameSimulator/GameSimulatorService.ts index a8cafb305c..3a39daaebf 100644 --- a/src/features/gameSimulator/GameSimulatorService.ts +++ b/src/features/gameSimulator/GameSimulatorService.ts @@ -1,4 +1,4 @@ -import _ from 'lodash'; +import { sortBy } from 'es-toolkit/compat'; import { sendAdminStoryRequest, sendAssetRequest, sendStoryRequest } from './GameSimulatorRequest'; import { ChapterDetail } from './GameSimulatorTypes'; @@ -114,7 +114,7 @@ export async function uploadAssetToS3(file: File, folderName: string) { export async function fetchChapters(): Promise { const response = await sendStoryRequest('', 'GET'); const chapterDetails = response.status === 200 ? await response.json() : []; - return _.sortBy(chapterDetails, (chapterDetail: ChapterDetail) => new Date(chapterDetail.openAt)); + return sortBy(chapterDetails, (chapterDetail: ChapterDetail) => new Date(chapterDetail.openAt)); } /** diff --git a/src/features/remoteExecution/PeripheralContainer.tsx b/src/features/remoteExecution/PeripheralContainer.tsx index a02f8af07c..41f4053abe 100644 --- a/src/features/remoteExecution/PeripheralContainer.tsx +++ b/src/features/remoteExecution/PeripheralContainer.tsx @@ -1,5 +1,5 @@ import { Classes } from '@blueprintjs/core'; -import classNames from 'classnames'; +import clsx from 'clsx'; type PeripheralContainerProps = { src: string | React.ReactElement; @@ -11,7 +11,7 @@ const PeripheralContainer: React.FC = ({ src, alt = 'I return (
{typeof src == 'string' ? {alt} : src} diff --git a/src/features/remoteExecution/RemoteExecutionDeviceDialog.tsx b/src/features/remoteExecution/RemoteExecutionDeviceDialog.tsx index 9b16045e64..ac42cc4c24 100644 --- a/src/features/remoteExecution/RemoteExecutionDeviceDialog.tsx +++ b/src/features/remoteExecution/RemoteExecutionDeviceDialog.tsx @@ -10,7 +10,7 @@ import { InputGroup, Tooltip } from '@blueprintjs/core'; -import classNames from 'classnames'; +import clsx from 'clsx'; import React from 'react'; import { QrReader } from 'react-qr-reader'; import { useDispatch } from 'react-redux'; @@ -112,7 +112,7 @@ const RemoteExecutionDeviceDialog: React.FC = ({ > = ({ void (typeField.ref.current = element)} disabled={isSubmitting || !!deviceToEdit} {...(deviceToEdit ? { value: deviceToEdit.type } : undefined)} @@ -144,7 +144,7 @@ const RemoteExecutionDeviceDialog: React.FC = ({ { ) : routeCourseId === courseId ? ( ) : ( -
+
| string[] | any; diff --git a/src/pages/academy/grading/subcomponents/GradingBadges.tsx b/src/pages/academy/grading/subcomponents/GradingBadges.tsx index 53d157afbb..e1a650ad3e 100644 --- a/src/pages/academy/grading/subcomponents/GradingBadges.tsx +++ b/src/pages/academy/grading/subcomponents/GradingBadges.tsx @@ -1,6 +1,6 @@ import { Icon } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; -import classNames from 'classnames'; +import clsx from 'clsx'; import React from 'react'; import { ProgressStatus, ProgressStatuses } from 'src/commons/assessment/AssessmentTypes'; import { ColumnFilter } from 'src/features/grading/GradingTypes'; @@ -21,7 +21,7 @@ type BadgeProps = { const Badge: React.FC = props => { return (
= props => { return ( {props.displayName} {!props.disabledSortCols.includes(props.column.getColId()) && (
nextSortState()} > @@ -50,7 +47,7 @@ const GradingColumnCustomHeaders: React.FC = props => { )}
props.hideColumn(props.column.getColId())} > diff --git a/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx b/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx index aea66d238b..69443c3a3d 100644 --- a/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx +++ b/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx @@ -2,8 +2,8 @@ import { Button, H6, Icon, InputGroup } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import { CellClickedEvent, ColDef, themeQuartz } from 'ag-grid-community'; import { AgGridReact } from 'ag-grid-react'; -import classNames from 'classnames'; -import { debounce } from 'lodash'; +import clsx from 'clsx'; +import { debounce } from 'es-toolkit'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; import { useNavigate } from 'react-router'; @@ -138,7 +138,7 @@ const GradingSubmissionTable: React.FC = ({ "Hmm... we didn't find any submissions, you might want to debug your filter() function.", pageSize: pageSize, pagination: true, - rowClass: classNames(classes['grading-left-align'], classes['grading-table-rows']), + rowClass: clsx(classes['grading-left-align'], classes['grading-table-rows']), rowHeight: ROW_HEIGHT, suppressMenuHide: true, suppressPaginationPanel: true, @@ -371,7 +371,7 @@ const GradingSubmissionTable: React.FC = ({