From 5ee0befa670b77151726fe8a6cdee5d195521950 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 10 Jan 2026 17:27:42 +0000 Subject: [PATCH 01/24] consoleStyles: update to ts, no verify --- client/modules/IDE/utils/{consoleStyles.js => consoleStyles.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename client/modules/IDE/utils/{consoleStyles.js => consoleStyles.ts} (100%) diff --git a/client/modules/IDE/utils/consoleStyles.js b/client/modules/IDE/utils/consoleStyles.ts similarity index 100% rename from client/modules/IDE/utils/consoleStyles.js rename to client/modules/IDE/utils/consoleStyles.ts From 2c3f3d900b7499e692feb5ceb8672d1353d8bbbd Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 10 Jan 2026 17:28:11 +0000 Subject: [PATCH 02/24] add .svg?byContent and .svg?byUrl to custom types --- client/custom.d.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/client/custom.d.ts b/client/custom.d.ts index 729f9e03de..26c52f63cc 100644 --- a/client/custom.d.ts +++ b/client/custom.d.ts @@ -1,3 +1,15 @@ +declare module '*.svg?byUrl' { + const url: string; + // eslint-disable-next-line import/no-default-export + export default url; +} + +declare module '*.svg?byContent' { + const content: string; + // eslint-disable-next-line import/no-default-export + export default content; +} + declare module '*.svg' { import * as React from 'react'; From 7445aecc26169dee0a1ac5fe82341e6c5ccd0ded Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 10 Jan 2026 17:29:15 +0000 Subject: [PATCH 03/24] consoleStyles: resolve type errors & update to named export --- client/modules/IDE/components/Console.jsx | 2 +- client/modules/IDE/utils/consoleStyles.ts | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/client/modules/IDE/components/Console.jsx b/client/modules/IDE/components/Console.jsx index 3494fccdee..866ec196c0 100644 --- a/client/modules/IDE/components/Console.jsx +++ b/client/modules/IDE/components/Console.jsx @@ -14,7 +14,7 @@ import * as ConsoleActions from '../actions/console'; import { useDidUpdate } from '../hooks/custom-hooks'; import useHandleMessageEvent from '../hooks/useHandleMessageEvent'; import { listen } from '../../../utils/dispatcher'; -import getConsoleFeedStyle from '../utils/consoleStyles'; +import { getConsoleFeedStyle } from '../utils/consoleStyles'; const Console = () => { const { t } = useTranslation(); diff --git a/client/modules/IDE/utils/consoleStyles.ts b/client/modules/IDE/utils/consoleStyles.ts index 83d53b49c3..f94125c45b 100644 --- a/client/modules/IDE/utils/consoleStyles.ts +++ b/client/modules/IDE/utils/consoleStyles.ts @@ -150,7 +150,7 @@ const CONSOLE_FEED_CONTRAST_ICONS = { LOG_RESULT_ICON: `url(${resultContrastUrl})` }; -const getConsoleFeedStyle = (theme, fontSize) => { +export const getConsoleFeedStyle = (theme: string, fontSize: number) => { const CONSOLE_FEED_SIZES = { TREENODE_LINE_HEIGHT: 1.2, BASE_FONT_SIZE: `${fontSize}px`, @@ -184,5 +184,3 @@ const getConsoleFeedStyle = (theme, fontSize) => { ); } }; - -export default getConsoleFeedStyle; From 2413816e88e06e82407413f4790b6d6bf405c638 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 10 Jan 2026 17:39:58 +0000 Subject: [PATCH 04/24] hooks/index: update to ts --- client/modules/IDE/hooks/{index.js => index.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename client/modules/IDE/hooks/{index.js => index.ts} (100%) diff --git a/client/modules/IDE/hooks/index.js b/client/modules/IDE/hooks/index.ts similarity index 100% rename from client/modules/IDE/hooks/index.js rename to client/modules/IDE/hooks/index.ts From 3dbcf68a7fca3b41da7cf1df66b032faa118f2ad Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 10 Jan 2026 17:45:59 +0000 Subject: [PATCH 05/24] hooks/useWhatPage: update to ts, no-verify --- client/modules/IDE/hooks/{useWhatPage.js => useWhatPage.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename client/modules/IDE/hooks/{useWhatPage.js => useWhatPage.ts} (100%) diff --git a/client/modules/IDE/hooks/useWhatPage.js b/client/modules/IDE/hooks/useWhatPage.ts similarity index 100% rename from client/modules/IDE/hooks/useWhatPage.js rename to client/modules/IDE/hooks/useWhatPage.ts From f6fb4c4aff3dea2511669a103cb825e2550ca66f Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 10 Jan 2026 17:46:23 +0000 Subject: [PATCH 06/24] hooks/useWhatPage: update to named export & resolve type errors --- client/modules/IDE/hooks/index.ts | 2 +- client/modules/IDE/hooks/useWhatPage.ts | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/client/modules/IDE/hooks/index.ts b/client/modules/IDE/hooks/index.ts index d309cd4b88..1d18339c80 100644 --- a/client/modules/IDE/hooks/index.ts +++ b/client/modules/IDE/hooks/index.ts @@ -1,2 +1,2 @@ export { default as useSketchActions } from './useSketchActions'; -export { default as useWhatPage } from './useWhatPage'; +export * from './useWhatPage'; diff --git a/client/modules/IDE/hooks/useWhatPage.ts b/client/modules/IDE/hooks/useWhatPage.ts index 8126c596c0..ed6af44031 100644 --- a/client/modules/IDE/hooks/useWhatPage.ts +++ b/client/modules/IDE/hooks/useWhatPage.ts @@ -1,13 +1,16 @@ import { useMemo } from 'react'; import { useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; +import type { RootState } from '../../../reducers'; -/** - * - * @returns {"home" | "myStuff" | "login" | "signup" | "account" | "examples"} - */ -const useWhatPage = () => { - const username = useSelector((state) => state.user.username); +export const useWhatPage = (): + | 'home' + | 'myStuff' + | 'login' + | 'signup' + | 'account' + | 'examples' => { + const username = useSelector((state: RootState) => state.user.username); const { pathname } = useLocation(); const pageName = useMemo(() => { @@ -26,5 +29,3 @@ const useWhatPage = () => { return pageName; }; - -export default useWhatPage; From d6538ca684691241a3c1a4d23efc0d188ee14c2b Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 10 Jan 2026 17:49:58 +0000 Subject: [PATCH 07/24] hooks/useIsMobile: update to ts, no-verify --- client/modules/IDE/hooks/{useIsMobile.js => useIsMobile.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename client/modules/IDE/hooks/{useIsMobile.js => useIsMobile.ts} (100%) diff --git a/client/modules/IDE/hooks/useIsMobile.js b/client/modules/IDE/hooks/useIsMobile.ts similarity index 100% rename from client/modules/IDE/hooks/useIsMobile.js rename to client/modules/IDE/hooks/useIsMobile.ts From 66ddcb71ad8ef9caee8fe0b513cf57967543cb66 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 10 Jan 2026 17:50:19 +0000 Subject: [PATCH 08/24] hooks/useIsMobile: update to named export & resolve type errors --- client/components/Dropdown/TableDropdown.tsx | 2 +- client/modules/IDE/components/Header/Nav.jsx | 2 +- client/modules/IDE/components/Header/index.jsx | 2 +- client/modules/IDE/hooks/index.ts | 1 + client/modules/IDE/hooks/useIsMobile.ts | 4 +--- client/modules/IDE/pages/IDEView.jsx | 2 +- client/modules/User/components/DashboardTabSwitcher.tsx | 2 +- client/modules/User/pages/DashboardView.jsx | 2 +- 8 files changed, 8 insertions(+), 9 deletions(-) diff --git a/client/components/Dropdown/TableDropdown.tsx b/client/components/Dropdown/TableDropdown.tsx index e9408dd6b9..f91325e341 100644 --- a/client/components/Dropdown/TableDropdown.tsx +++ b/client/components/Dropdown/TableDropdown.tsx @@ -9,7 +9,7 @@ import { import DownFilledTriangleIcon from '../../images/down-filled-triangle.svg'; import MoreIconSvg from '../../images/more.svg'; -import useIsMobile from '../../modules/IDE/hooks/useIsMobile'; +import { useIsMobile } from '../../modules/IDE/hooks'; const DotsHorizontal = styled(MoreIconSvg)` transform: rotate(90deg); diff --git a/client/modules/IDE/components/Header/Nav.jsx b/client/modules/IDE/components/Header/Nav.jsx index 28995a2d56..12a710a0d4 100644 --- a/client/modules/IDE/components/Header/Nav.jsx +++ b/client/modules/IDE/components/Header/Nav.jsx @@ -30,7 +30,7 @@ import { import { logoutUser } from '../../../User/actions'; import { CmControllerContext } from '../../pages/IDEView'; import MobileNav from './MobileNav'; -import useIsMobile from '../../hooks/useIsMobile'; +import { useIsMobile } from '../../hooks'; const Nav = ({ layout }) => { const isMobile = useIsMobile(); diff --git a/client/modules/IDE/components/Header/index.jsx b/client/modules/IDE/components/Header/index.jsx index 79f78ff67e..871188641f 100644 --- a/client/modules/IDE/components/Header/index.jsx +++ b/client/modules/IDE/components/Header/index.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { useSelector } from 'react-redux'; -import useIsMobile from '../../hooks/useIsMobile'; +import { useIsMobile } from '../../hooks'; import Nav from './Nav'; import Toolbar from './Toolbar'; diff --git a/client/modules/IDE/hooks/index.ts b/client/modules/IDE/hooks/index.ts index 1d18339c80..4d402c3573 100644 --- a/client/modules/IDE/hooks/index.ts +++ b/client/modules/IDE/hooks/index.ts @@ -1,2 +1,3 @@ export { default as useSketchActions } from './useSketchActions'; export * from './useWhatPage'; +export * from './useIsMobile'; diff --git a/client/modules/IDE/hooks/useIsMobile.ts b/client/modules/IDE/hooks/useIsMobile.ts index c278dcbd8a..1dbec11a42 100644 --- a/client/modules/IDE/hooks/useIsMobile.ts +++ b/client/modules/IDE/hooks/useIsMobile.ts @@ -1,9 +1,7 @@ import { useMediaQuery } from 'react-responsive'; -const useIsMobile = (customBreakpoint) => { +export const useIsMobile = (customBreakpoint?: number): boolean => { const breakPoint = customBreakpoint || 770; const isMobile = useMediaQuery({ maxWidth: breakPoint }); return isMobile; }; - -export default useIsMobile; diff --git a/client/modules/IDE/pages/IDEView.jsx b/client/modules/IDE/pages/IDEView.jsx index d81930a327..30264b0fed 100644 --- a/client/modules/IDE/pages/IDEView.jsx +++ b/client/modules/IDE/pages/IDEView.jsx @@ -25,7 +25,7 @@ import { PreviewWrapper } from '../components/Editor/MobileEditor'; import IDEOverlays from '../components/IDEOverlays'; -import useIsMobile from '../hooks/useIsMobile'; +import { useIsMobile } from '../hooks'; import Banner from '../components/Banner'; import { P5VersionProvider } from '../hooks/useP5Version'; diff --git a/client/modules/User/components/DashboardTabSwitcher.tsx b/client/modules/User/components/DashboardTabSwitcher.tsx index 8ec810cf64..607fbc9bfc 100644 --- a/client/modules/User/components/DashboardTabSwitcher.tsx +++ b/client/modules/User/components/DashboardTabSwitcher.tsx @@ -7,7 +7,7 @@ import { IconButton } from '../../../common/IconButton'; import { RouterTab } from '../../../common/RouterTab'; import { Options } from '../../IDE/components/Header/MobileNav'; import { toggleDirectionForField } from '../../IDE/actions/sorting'; -import useIsMobile from '../../IDE/hooks/useIsMobile'; +import { useIsMobile } from '../../IDE/hooks'; export enum TabKey { assets = 'assets', diff --git a/client/modules/User/pages/DashboardView.jsx b/client/modules/User/pages/DashboardView.jsx index 8044de028a..4870393941 100644 --- a/client/modules/User/pages/DashboardView.jsx +++ b/client/modules/User/pages/DashboardView.jsx @@ -22,7 +22,7 @@ import { DashboardTabSwitcher, TabKey } from '../components/DashboardTabSwitcher'; -import useIsMobile from '../../IDE/hooks/useIsMobile'; +import { useIsMobile } from '../../IDE/hooks'; const DashboardView = () => { const isMobile = useIsMobile(); From 164ce988cb8b74321d1ea4033910d50540f52683 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 10 Jan 2026 17:59:25 +0000 Subject: [PATCH 09/24] hooks/custom-hooks: update to ts, no-verify --- client/modules/IDE/hooks/{custom-hooks.js => custom-hooks.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename client/modules/IDE/hooks/{custom-hooks.js => custom-hooks.ts} (100%) diff --git a/client/modules/IDE/hooks/custom-hooks.js b/client/modules/IDE/hooks/custom-hooks.ts similarity index 100% rename from client/modules/IDE/hooks/custom-hooks.js rename to client/modules/IDE/hooks/custom-hooks.ts From f8ceb97c757bda0e9e46cbc73fe713a3ad435239 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 10 Jan 2026 17:59:55 +0000 Subject: [PATCH 10/24] hooks/custom-hooks: delete unused hooks, resolve type errors --- client/modules/IDE/components/Console.jsx | 2 +- client/modules/IDE/hooks/custom-hooks.ts | 62 ++--------------------- client/modules/IDE/hooks/index.ts | 1 + 3 files changed, 7 insertions(+), 58 deletions(-) diff --git a/client/modules/IDE/components/Console.jsx b/client/modules/IDE/components/Console.jsx index 866ec196c0..764f1bd8ce 100644 --- a/client/modules/IDE/components/Console.jsx +++ b/client/modules/IDE/components/Console.jsx @@ -11,7 +11,7 @@ import DownArrowIcon from '../../../images/down-arrow.svg'; import * as IDEActions from '../actions/ide'; import * as ConsoleActions from '../actions/console'; -import { useDidUpdate } from '../hooks/custom-hooks'; +import { useDidUpdate } from '../hooks'; import useHandleMessageEvent from '../hooks/useHandleMessageEvent'; import { listen } from '../../../utils/dispatcher'; import { getConsoleFeedStyle } from '../utils/consoleStyles'; diff --git a/client/modules/IDE/hooks/custom-hooks.ts b/client/modules/IDE/hooks/custom-hooks.ts index 6471bbcac2..043035f0c9 100644 --- a/client/modules/IDE/hooks/custom-hooks.ts +++ b/client/modules/IDE/hooks/custom-hooks.ts @@ -1,8 +1,11 @@ -import { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef } from 'react'; export const noop = () => {}; -export const useDidUpdate = (callback, deps) => { +export const useDidUpdate = ( + callback: () => void, + deps: React.DependencyList +) => { const hasMount = useRef(false); useEffect(() => { @@ -13,58 +16,3 @@ export const useDidUpdate = (callback, deps) => { } }, deps); }; - -// Usage: const ref = useModalBehavior(() => setSomeState(false)) -// place this ref on a component -export const useModalBehavior = (hideOverlay) => { - const ref = useRef({}); - - // Return values - const setRef = (r) => { - ref.current = r; - }; - const [visible, setVisible] = useState(false); - const trigger = () => setVisible(!visible); - - const hide = () => setVisible(false); - - const handleClickOutside = ({ target }) => { - if ( - ref && - ref.current && - !(ref.current.contains && ref.current.contains(target)) - ) { - hide(); - } - }; - - useEffect(() => { - document.addEventListener('mousedown', handleClickOutside); - return () => document.removeEventListener('mousedown', handleClickOutside); - }, [ref]); - - return [visible, trigger, setRef]; -}; - -// Usage: useEffectWithComparison((props, prevProps) => { ... }, { prop1, prop2 }) -// This hook basically applies useEffect but keeps track of the last value of relevant props -// So you can pass a 2-param function to capture new and old values and do whatever with them. -export const useEffectWithComparison = (fn, props) => { - const [prevProps, update] = useState({}); - - return useEffect(() => { - fn(props, prevProps); - update(props); - }, Object.values(props)); -}; - -export const useEventListener = ( - event, - callback, - useCapture = false, - list = [] -) => - useEffect(() => { - document.addEventListener(event, callback, useCapture); - return () => document.removeEventListener(event, callback, useCapture); - }, list); diff --git a/client/modules/IDE/hooks/index.ts b/client/modules/IDE/hooks/index.ts index 4d402c3573..ef90997267 100644 --- a/client/modules/IDE/hooks/index.ts +++ b/client/modules/IDE/hooks/index.ts @@ -1,3 +1,4 @@ export { default as useSketchActions } from './useSketchActions'; export * from './useWhatPage'; export * from './useIsMobile'; +export * from './custom-hooks'; From b8975015377cda3fc744a04a36a903fe421cf2f9 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 10 Jan 2026 18:02:31 +0000 Subject: [PATCH 11/24] hooks/custom-hooks: update file to useDidUpdate --- client/modules/IDE/hooks/index.ts | 2 +- client/modules/IDE/hooks/{custom-hooks.ts => useDidUpdate.ts} | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) rename client/modules/IDE/hooks/{custom-hooks.ts => useDidUpdate.ts} (90%) diff --git a/client/modules/IDE/hooks/index.ts b/client/modules/IDE/hooks/index.ts index ef90997267..fd57d8586a 100644 --- a/client/modules/IDE/hooks/index.ts +++ b/client/modules/IDE/hooks/index.ts @@ -1,4 +1,4 @@ export { default as useSketchActions } from './useSketchActions'; export * from './useWhatPage'; export * from './useIsMobile'; -export * from './custom-hooks'; +export * from './useDidUpdate'; diff --git a/client/modules/IDE/hooks/custom-hooks.ts b/client/modules/IDE/hooks/useDidUpdate.ts similarity index 90% rename from client/modules/IDE/hooks/custom-hooks.ts rename to client/modules/IDE/hooks/useDidUpdate.ts index 043035f0c9..0ad84f0321 100644 --- a/client/modules/IDE/hooks/custom-hooks.ts +++ b/client/modules/IDE/hooks/useDidUpdate.ts @@ -1,7 +1,5 @@ import React, { useEffect, useRef } from 'react'; -export const noop = () => {}; - export const useDidUpdate = ( callback: () => void, deps: React.DependencyList From 0f40933d84d688120f48487a62761d21a3f16cdd Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 10 Jan 2026 18:03:52 +0000 Subject: [PATCH 12/24] IDE/hooks/useInterval: update to ts, no-verify --- client/modules/IDE/hooks/{useInterval.js => useInterval.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename client/modules/IDE/hooks/{useInterval.js => useInterval.ts} (100%) diff --git a/client/modules/IDE/hooks/useInterval.js b/client/modules/IDE/hooks/useInterval.ts similarity index 100% rename from client/modules/IDE/hooks/useInterval.js rename to client/modules/IDE/hooks/useInterval.ts From 17a18c2c8d2ae30495a56919beac7f38268a1721 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 10 Jan 2026 18:22:40 +0000 Subject: [PATCH 13/24] IDE/hooks/useInterval: update to named export and resolve type errors --- client/modules/IDE/components/Timer.jsx | 2 +- client/modules/IDE/hooks/index.ts | 1 + client/modules/IDE/hooks/useInterval.ts | 14 +++++++++----- client/modules/IDE/pages/FullView.jsx | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/client/modules/IDE/components/Timer.jsx b/client/modules/IDE/components/Timer.jsx index 2502e9f33c..4eeeb5be50 100644 --- a/client/modules/IDE/components/Timer.jsx +++ b/client/modules/IDE/components/Timer.jsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; import { distanceInWordsToNow } from '../../../utils/formatDate'; -import useInterval from '../hooks/useInterval'; +import { useInterval } from '../hooks/useInterval'; import { getIsUserOwner } from '../selectors/users'; const Timer = () => { diff --git a/client/modules/IDE/hooks/index.ts b/client/modules/IDE/hooks/index.ts index fd57d8586a..c3209dfdda 100644 --- a/client/modules/IDE/hooks/index.ts +++ b/client/modules/IDE/hooks/index.ts @@ -2,3 +2,4 @@ export { default as useSketchActions } from './useSketchActions'; export * from './useWhatPage'; export * from './useIsMobile'; export * from './useDidUpdate'; +export * from './useInterval'; diff --git a/client/modules/IDE/hooks/useInterval.ts b/client/modules/IDE/hooks/useInterval.ts index 63ddaf5503..b6a2ed5a66 100644 --- a/client/modules/IDE/hooks/useInterval.ts +++ b/client/modules/IDE/hooks/useInterval.ts @@ -1,9 +1,11 @@ // https://overreacted.io/making-setinterval-declarative-with-react-hooks/ import { useState, useEffect, useRef } from 'react'; -export default function useInterval(callback, delay) { - const savedCallback = useRef(); - const [intervalId, setIntervalId] = useState(); +export function useInterval(callback: () => void, delay: number) { + const savedCallback = useRef<() => void>(); + const [intervalId, setIntervalId] = useState< + ReturnType + >(); // Remember the latest callback. useEffect(() => { @@ -11,16 +13,18 @@ export default function useInterval(callback, delay) { }, [callback]); // Set up the interval. + // eslint-disable-next-line consistent-return useEffect(() => { function tick() { - savedCallback.current(); + if (savedCallback.current) { + savedCallback.current(); + } } if (delay !== null) { const id = setInterval(tick, delay); setIntervalId(id); return () => clearInterval(id); } - return null; }, [delay]); return () => clearInterval(intervalId); } diff --git a/client/modules/IDE/pages/FullView.jsx b/client/modules/IDE/pages/FullView.jsx index bf2e66fdcb..791ba4b51b 100644 --- a/client/modules/IDE/pages/FullView.jsx +++ b/client/modules/IDE/pages/FullView.jsx @@ -11,7 +11,7 @@ import { dispatchMessage, MessageTypes } from '../../../utils/dispatcher'; -import useInterval from '../hooks/useInterval'; +import { useInterval } from '../hooks'; import { RootPage } from '../../../components/RootPage'; function FullView() { From 515f2caae518385c4bcfd852f16e118c7aa1e8d9 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 10 Jan 2026 18:23:01 +0000 Subject: [PATCH 14/24] hooks/useIsMobile: add types for react-responsive --- package-lock.json | 20 ++++++++++++++++++++ package.json | 1 + 2 files changed, 21 insertions(+) diff --git a/package-lock.json b/package-lock.json index 8f13046ae3..58e0a62eaa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -173,6 +173,7 @@ "@types/react": "^16.14.0", "@types/react-dom": "^16.9.25", "@types/react-helmet": "^6.1.11", + "@types/react-responsive": "^8.0.8", "@types/react-router-dom": "^5.3.3", "@types/react-tabs": "^2.3.1", "@types/react-transition-group": "^4.4.12", @@ -16678,6 +16679,16 @@ "redux": "^4.0.0" } }, + "node_modules/@types/react-responsive": { + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@types/react-responsive/-/react-responsive-8.0.8.tgz", + "integrity": "sha512-HDUZtoeFRHrShCGaND23HmXAB9evOOTjkghd2wAasLkuorYYitm5A1XLeKkhXKZppcMBxqB/8V4Snl6hRUTA8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-router": { "version": "5.1.20", "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", @@ -53311,6 +53322,15 @@ "redux": "^4.0.0" } }, + "@types/react-responsive": { + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/@types/react-responsive/-/react-responsive-8.0.8.tgz", + "integrity": "sha512-HDUZtoeFRHrShCGaND23HmXAB9evOOTjkghd2wAasLkuorYYitm5A1XLeKkhXKZppcMBxqB/8V4Snl6hRUTA8g==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/react-router": { "version": "5.1.20", "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", diff --git a/package.json b/package.json index de6b1f40eb..e5c020d8a8 100644 --- a/package.json +++ b/package.json @@ -148,6 +148,7 @@ "@types/react": "^16.14.0", "@types/react-dom": "^16.9.25", "@types/react-helmet": "^6.1.11", + "@types/react-responsive": "^8.0.8", "@types/react-router-dom": "^5.3.3", "@types/react-tabs": "^2.3.1", "@types/react-transition-group": "^4.4.12", From e22ae364946c1a48ad5b68957253c769a422b3a1 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 10 Jan 2026 18:27:24 +0000 Subject: [PATCH 15/24] hooks/useDidUpdate: minor cleanup --- client/modules/IDE/hooks/useDidUpdate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/modules/IDE/hooks/useDidUpdate.ts b/client/modules/IDE/hooks/useDidUpdate.ts index 0ad84f0321..4d04f1065b 100644 --- a/client/modules/IDE/hooks/useDidUpdate.ts +++ b/client/modules/IDE/hooks/useDidUpdate.ts @@ -2,7 +2,7 @@ import React, { useEffect, useRef } from 'react'; export const useDidUpdate = ( callback: () => void, - deps: React.DependencyList + deps: React.DependencyList = [] ) => { const hasMount = useRef(false); From 218ad0fcfe186e9486a1373ed60c2021ca7ff57f Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 10 Jan 2026 18:53:29 +0000 Subject: [PATCH 16/24] hooks/useP5Version: update to ts, no-verify --- client/modules/IDE/hooks/{useP5Version.jsx => useP5Version.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename client/modules/IDE/hooks/{useP5Version.jsx => useP5Version.tsx} (100%) diff --git a/client/modules/IDE/hooks/useP5Version.jsx b/client/modules/IDE/hooks/useP5Version.tsx similarity index 100% rename from client/modules/IDE/hooks/useP5Version.jsx rename to client/modules/IDE/hooks/useP5Version.tsx From 3164d151e8b8b2243ce0a48f1f86892f70e827a0 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 10 Jan 2026 19:36:29 +0000 Subject: [PATCH 17/24] hooks/useP5Versions: resolve type errors --- client/modules/IDE/hooks/useP5Version.tsx | 79 ++++++++++++----------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/client/modules/IDE/hooks/useP5Version.tsx b/client/modules/IDE/hooks/useP5Version.tsx index a21a7c3b7f..a594c86372 100644 --- a/client/modules/IDE/hooks/useP5Version.tsx +++ b/client/modules/IDE/hooks/useP5Version.tsx @@ -1,7 +1,6 @@ /* eslint-disable func-names */ import React, { useContext, useMemo } from 'react'; import { useSelector } from 'react-redux'; -import PropTypes from 'prop-types'; import { currentP5Version, p5Versions } from '../../../../common/p5Versions'; import { p5SoundURLOldTemplate, @@ -11,8 +10,9 @@ import { p5DataAddonURL, p5URLTemplate } from '../../../../common/p5URLs'; +import type { RootState } from '../../../reducers'; -export const majorVersion = (version) => version.split('.')[0]; +export const majorVersion = (version: string) => version.split('.')[0]; export const p5SoundURLOld = p5SoundURLOldTemplate.replace( '$VERSION', @@ -22,10 +22,11 @@ export const p5URL = p5URLTemplate.replace('$VERSION', currentP5Version); const P5VersionContext = React.createContext({}); -export function P5VersionProvider(props) { - const files = useSelector((state) => state.files); +export function P5VersionProvider(props: { children: React.ReactNode }) { + const files = useSelector((state: RootState) => state.files); const indexFile = files.find( - (file) => + // TODO: clairepeng94 - update this to Project > File type once backend migration is complete + (file: { fileType: string; name: string; filePath: string }) => file.fileType === 'file' && file.name === 'index.html' && file.filePath === '' @@ -33,7 +34,6 @@ export function P5VersionProvider(props) { const indexSrc = indexFile?.content; const indexID = indexFile?.id; - // { version: string, minified: boolean, replaceVersion: (version: string) => string } | null const versionInfo = useMemo(() => { if (!indexSrc) return null; const dom = new DOMParser().parseFromString(indexSrc, 'text/html'); @@ -49,7 +49,9 @@ export function P5VersionProvider(props) { return src; }; - const usedP5Versions = [...dom.documentElement.querySelectorAll('script')] + const scriptNodes = [...dom.documentElement.querySelectorAll('script')]; + + const usedP5Versions = scriptNodes .map((scriptNode) => { const src = scriptNode.getAttribute('src') || ''; const matches = [ @@ -74,18 +76,17 @@ export function P5VersionProvider(props) { if (usedP5Versions.length === 1) { const { version, minified, scriptNode } = usedP5Versions[0]; - const p5SoundNode = [ - ...dom.documentElement.querySelectorAll('script') - ].find((s) => + const p5SoundNode = scriptNodes.find((s) => [ /^https?:\/\/cdnjs.cloudflare.com\/ajax\/libs\/p5.js\/(.+)\/addons\/p5\.sound(?:\.min)?\.js$/, /^https?:\/\/cdn.jsdelivr.net\/npm\/p5@(.+)\/lib\/addons\/p5\.sound(?:\.min)?\.js$/, /^https?:\/\/cdn.jsdelivr.net\/npm\/p5.sound@(.+)\/dist\/p5\.sound(?:\.min)?\.js$/ ].some((regex) => regex.exec(s.getAttribute('src') || '')) ); - const setP5Sound = function (enabled) { + + const setP5Sound = function (enabled: boolean) { if (!enabled && p5SoundNode) { - p5SoundNode.parentNode.removeChild(p5SoundNode); + p5SoundNode.parentNode?.removeChild(p5SoundNode); } else if (enabled && !p5SoundNode) { const newNode = document.createElement('script'); newNode.setAttribute( @@ -94,23 +95,23 @@ export function P5VersionProvider(props) { ? p5SoundURL : p5SoundURLOldTemplate.replace('$VERSION', version) ); - scriptNode.parentNode.insertBefore(newNode, scriptNode.nextSibling); + scriptNode.parentNode?.insertBefore(newNode, scriptNode.nextSibling); } return serializeResult(); }; - const setP5SoundURL = function (url) { + const setP5SoundURL = function (url: string) { if (p5SoundNode) { p5SoundNode.setAttribute('src', url); } else { const newNode = document.createElement('script'); newNode.setAttribute('src', url); - scriptNode.parentNode.insertBefore(newNode, scriptNode.nextSibling); + scriptNode.parentNode?.insertBefore(newNode, scriptNode.nextSibling); } return serializeResult(); }; - const replaceVersion = function (newVersion) { + const replaceVersion = function (newVersion: string) { const file = minified ? 'p5.min.js' : 'p5.js'; scriptNode.setAttribute( 'src', @@ -135,44 +136,47 @@ export function P5VersionProvider(props) { return serializeResult(); }; - const p5PreloadAddonNode = [ - ...dom.documentElement.querySelectorAll('script') - ].find((s) => s.getAttribute('src') === p5PreloadAddonURL); - const setP5PreloadAddon = function (enabled) { + const p5PreloadAddonNode = scriptNodes.find( + (s) => s.getAttribute('src') === p5PreloadAddonURL + ); + + const setP5PreloadAddon = function (enabled: boolean) { if (!enabled && p5PreloadAddonNode) { - p5PreloadAddonNode.parentNode.removeChild(p5PreloadAddonNode); + p5PreloadAddonNode.parentNode?.removeChild(p5PreloadAddonNode); } else if (enabled && !p5PreloadAddonNode) { const newNode = document.createElement('script'); newNode.setAttribute('src', p5PreloadAddonURL); - scriptNode.parentNode.insertBefore(newNode, scriptNode.nextSibling); + scriptNode.parentNode?.insertBefore(newNode, scriptNode.nextSibling); } return serializeResult(); }; - const p5ShapesAddonNode = [ - ...dom.documentElement.querySelectorAll('script') - ].find((s) => s.getAttribute('src') === p5ShapesAddonURL); - const setP5ShapesAddon = function (enabled) { + const p5ShapesAddonNode = scriptNodes.find( + (s) => s.getAttribute('src') === p5ShapesAddonURL + ); + + const setP5ShapesAddon = function (enabled: boolean) { if (!enabled && p5ShapesAddonNode) { - p5ShapesAddonNode.parentNode.removeChild(p5ShapesAddonNode); + p5ShapesAddonNode.parentNode?.removeChild(p5ShapesAddonNode); } else if (enabled && !p5ShapesAddonNode) { const newNode = document.createElement('script'); newNode.setAttribute('src', p5ShapesAddonURL); - scriptNode.parentNode.insertBefore(newNode, scriptNode.nextSibling); + scriptNode.parentNode?.insertBefore(newNode, scriptNode.nextSibling); } return serializeResult(); }; - const p5DataAddonNode = [ - ...dom.documentElement.querySelectorAll('script') - ].find((s) => s.getAttribute('src') === p5DataAddonURL); - const setP5DataAddon = function (enabled) { + const p5DataAddonNode = scriptNodes.find( + (s) => s.getAttribute('src') === p5DataAddonURL + ); + + const setP5DataAddon = function (enabled: boolean) { if (!enabled && p5DataAddonNode) { - p5DataAddonNode.parentNode.removeChild(p5DataAddonNode); + p5DataAddonNode.parentNode?.removeChild(p5DataAddonNode); } else if (enabled && !p5DataAddonNode) { const newNode = document.createElement('script'); newNode.setAttribute('src', p5DataAddonURL); - scriptNode.parentNode.insertBefore(newNode, scriptNode.nextSibling); + scriptNode.parentNode?.insertBefore(newNode, scriptNode.nextSibling); } return serializeResult(); }; @@ -207,10 +211,9 @@ export function P5VersionProvider(props) { ); } -P5VersionProvider.propTypes = { - children: PropTypes.node.isRequired -}; - export function useP5Version() { + if (!P5VersionContext) { + throw new Error('useP5Version must be used within a P5VersionProvider'); + } return useContext(P5VersionContext); } From 814d9d7589d19cbd95d118bf3ea48e3a9def8b0b Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 10 Jan 2026 19:50:05 +0000 Subject: [PATCH 18/24] hooks/useSketchActions: update to ts, no-verify --- .../IDE/hooks/{useSketchActions.js => useSketchActions.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename client/modules/IDE/hooks/{useSketchActions.js => useSketchActions.ts} (100%) diff --git a/client/modules/IDE/hooks/useSketchActions.js b/client/modules/IDE/hooks/useSketchActions.ts similarity index 100% rename from client/modules/IDE/hooks/useSketchActions.js rename to client/modules/IDE/hooks/useSketchActions.ts From 3c5a071dad40cc4b771d93324f415c65c8cab0b1 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 10 Jan 2026 19:50:30 +0000 Subject: [PATCH 19/24] hooks/useSketchActions: update to named export, resolve type errors --- client/modules/IDE/hooks/index.ts | 2 +- client/modules/IDE/hooks/useSketchActions.ts | 23 +++++++++++--------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/client/modules/IDE/hooks/index.ts b/client/modules/IDE/hooks/index.ts index c3209dfdda..4fecaff994 100644 --- a/client/modules/IDE/hooks/index.ts +++ b/client/modules/IDE/hooks/index.ts @@ -1,4 +1,4 @@ -export { default as useSketchActions } from './useSketchActions'; +export * from './useSketchActions'; export * from './useWhatPage'; export * from './useIsMobile'; export * from './useDidUpdate'; diff --git a/client/modules/IDE/hooks/useSketchActions.ts b/client/modules/IDE/hooks/useSketchActions.ts index a4c5d70235..21cb1fb05b 100644 --- a/client/modules/IDE/hooks/useSketchActions.ts +++ b/client/modules/IDE/hooks/useSketchActions.ts @@ -11,16 +11,21 @@ import { import { showToast } from '../actions/toast'; import { showErrorModal, showShareModal } from '../actions/ide'; import { selectCanEditSketch } from '../selectors/users'; +import type { RootState } from '../../../reducers'; -const useSketchActions = () => { - const unsavedChanges = useSelector((state) => state.ide.unsavedChanges); - const authenticated = useSelector((state) => state.user.authenticated); - const project = useSelector((state) => state.project); - const user = useSelector((state) => state.user); +export const useSketchActions = () => { + const unsavedChanges = useSelector( + (state: RootState) => state.ide.unsavedChanges + ); + const authenticated = useSelector( + (state: RootState) => state.user.authenticated + ); + const project = useSelector((state: RootState) => state.project); + const user = useSelector((state: RootState) => state.user); const canEditProjectName = useSelector(selectCanEditSketch); const dispatch = useDispatch(); const { t } = useTranslation(); - const params = useParams(); + const params = useParams<{ username: string }>(); function newSketch() { if (!unsavedChanges) { @@ -32,7 +37,7 @@ const useSketchActions = () => { } } - function saveSketch(cmController) { + function saveSketch(cmController?: { getContent: () => null | undefined }) { if (authenticated) { dispatch(saveProject(cmController?.getContent())); } else { @@ -52,7 +57,7 @@ const useSketchActions = () => { dispatch(showShareModal(project.id, project.name, username)); } - function changeSketchName(name) { + function changeSketchName(name: string) { const newProjectName = name.trim(); if (newProjectName.length > 0) { dispatch(setProjectName(newProjectName)); @@ -69,5 +74,3 @@ const useSketchActions = () => { canEditProjectName }; }; - -export default useSketchActions; From f2e0d888ad8276b58c45c62166cc3c8606c9c108 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 10 Jan 2026 19:57:25 +0000 Subject: [PATCH 20/24] hooks/useSketchActions: memoise methods --- client/modules/IDE/hooks/useSketchActions.ts | 55 +++++++++++++------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/client/modules/IDE/hooks/useSketchActions.ts b/client/modules/IDE/hooks/useSketchActions.ts index 21cb1fb05b..9ca7345a69 100644 --- a/client/modules/IDE/hooks/useSketchActions.ts +++ b/client/modules/IDE/hooks/useSketchActions.ts @@ -1,3 +1,4 @@ +import { useCallback } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router'; @@ -27,7 +28,7 @@ export const useSketchActions = () => { const { t } = useTranslation(); const params = useParams<{ username: string }>(); - function newSketch() { + const newSketch = useCallback(() => { if (!unsavedChanges) { dispatch(showToast('Toast.OpenedNewSketch')); dispatch(newProject()); @@ -35,35 +36,49 @@ export const useSketchActions = () => { dispatch(showToast('Toast.OpenedNewSketch')); dispatch(newProject()); } - } + }, [dispatch, showToast, newProject, unsavedChanges]); - function saveSketch(cmController?: { getContent: () => null | undefined }) { - if (authenticated) { - dispatch(saveProject(cmController?.getContent())); - } else { - dispatch(showErrorModal('forceAuthentication')); - } - } + const saveSketch = useCallback( + (cmController: { getContent: () => null | undefined }) => { + if (authenticated) { + dispatch(saveProject(cmController.getContent())); + } else { + dispatch(showErrorModal('forceAuthentication')); + } + }, + [dispatch, saveProject, showErrorModal, authenticated] + ); - function downloadSketch() { + const downloadSketch = useCallback(() => { if (authenticated && user.id === project.owner.id) { dispatch(autosaveProject()); exportProjectAsZip(project.id); } - } + }, [ + dispatch, + authenticated, + autosaveProject, + user, + project, + autosaveProject, + exportProjectAsZip + ]); - function shareSketch() { + const shareSketch = useCallback(() => { const { username } = params; dispatch(showShareModal(project.id, project.name, username)); - } + }, [params, dispatch, showShareModal, project]); - function changeSketchName(name: string) { - const newProjectName = name.trim(); - if (newProjectName.length > 0) { - dispatch(setProjectName(newProjectName)); - if (project.id) dispatch(saveProject()); - } - } + const changeSketchName = useCallback( + (name: string) => { + const newProjectName = name.trim(); + if (newProjectName.length > 0) { + dispatch(setProjectName(newProjectName)); + if (project.id) dispatch(saveProject()); + } + }, + [dispatch, setProjectName, project.id, saveProject] + ); return { newSketch, From ad4cdc020a2988cb9e577aa4e0aeb227bda1af01 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 10 Jan 2026 20:06:39 +0000 Subject: [PATCH 21/24] hooks/useHandleMessageEvent: update to ts, no-verify --- .../hooks/{useHandleMessageEvent.js => useHandleMessageEvent.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename client/modules/IDE/hooks/{useHandleMessageEvent.js => useHandleMessageEvent.ts} (100%) diff --git a/client/modules/IDE/hooks/useHandleMessageEvent.js b/client/modules/IDE/hooks/useHandleMessageEvent.ts similarity index 100% rename from client/modules/IDE/hooks/useHandleMessageEvent.js rename to client/modules/IDE/hooks/useHandleMessageEvent.ts From 04e839a1de8e9cdc584dc502460d17ee9b1a46cb Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 10 Jan 2026 20:34:39 +0000 Subject: [PATCH 22/24] hooks/useHandleMessageEvent: resolve type errors, add Message type from console-feed --- client/modules/IDE/components/Console.jsx | 3 +- client/modules/IDE/hooks/index.ts | 1 + .../IDE/hooks/useHandleMessageEvent.ts | 29 +++++++++++++------ client/utils/dispatcher.ts | 9 ++++-- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/client/modules/IDE/components/Console.jsx b/client/modules/IDE/components/Console.jsx index 764f1bd8ce..d1d7befde9 100644 --- a/client/modules/IDE/components/Console.jsx +++ b/client/modules/IDE/components/Console.jsx @@ -11,8 +11,7 @@ import DownArrowIcon from '../../../images/down-arrow.svg'; import * as IDEActions from '../actions/ide'; import * as ConsoleActions from '../actions/console'; -import { useDidUpdate } from '../hooks'; -import useHandleMessageEvent from '../hooks/useHandleMessageEvent'; +import { useDidUpdate, useHandleMessageEvent } from '../hooks'; import { listen } from '../../../utils/dispatcher'; import { getConsoleFeedStyle } from '../utils/consoleStyles'; diff --git a/client/modules/IDE/hooks/index.ts b/client/modules/IDE/hooks/index.ts index 4fecaff994..fd3a9b3f52 100644 --- a/client/modules/IDE/hooks/index.ts +++ b/client/modules/IDE/hooks/index.ts @@ -3,3 +3,4 @@ export * from './useWhatPage'; export * from './useIsMobile'; export * from './useDidUpdate'; export * from './useInterval'; +export * from './useHandleMessageEvent'; diff --git a/client/modules/IDE/hooks/useHandleMessageEvent.ts b/client/modules/IDE/hooks/useHandleMessageEvent.ts index 5603c1d697..db39665f5a 100644 --- a/client/modules/IDE/hooks/useHandleMessageEvent.ts +++ b/client/modules/IDE/hooks/useHandleMessageEvent.ts @@ -1,18 +1,25 @@ import { useDispatch } from 'react-redux'; import { Decode } from 'console-feed'; +import { Message } from 'console-feed/lib/definitions/Console'; import { dispatchConsoleEvent } from '../actions/console'; import { stopSketch, expandConsole } from '../actions/ide'; -export default function useHandleMessageEvent() { +type SafeValue = string | number | boolean | null | SafeObject | SafeArray; +interface SafeObject { + [key: string]: SafeValue; +} +interface SafeArray extends Array {} + +export function useHandleMessageEvent() { const dispatch = useDispatch(); const safeStringify = ( - obj, + obj: unknown, depth = 0, maxDepth = 10, - seen = new WeakMap() - ) => { - if (typeof obj !== 'object' || obj === null) return obj; + seen = new WeakMap() + ): SafeValue => { + if (typeof obj !== 'object' || obj === null) return obj as SafeValue; if (depth >= maxDepth) { if (seen.has(obj)) return '[Circular Reference]'; @@ -30,9 +37,13 @@ export default function useHandleMessageEvent() { ); }; - const handleMessageEvent = (data) => { + const handleMessageEvent = (data: Message['data']) => { if (!data || typeof data !== 'object') return; - const { source, messages } = data; + + const { source, messages } = data as { + source?: string; + messages?: { log?: unknown }[]; + }; if (source !== 'sketch' || !Array.isArray(messages)) return; const decodedMessages = messages.map((message) => { @@ -48,8 +59,8 @@ export default function useHandleMessageEvent() { // Detect infinite loop warnings const hasInfiniteLoop = decodedMessages.some( (message) => - message?.data && - Object.values(message.data).some( + (message as SafeObject)?.data && + Object.values((message as SafeObject)?.data as SafeObject).some( (arg) => typeof arg === 'string' && arg.includes('Exiting potential infinite loop') diff --git a/client/utils/dispatcher.ts b/client/utils/dispatcher.ts index 4966a1620c..95d27ea1cb 100644 --- a/client/utils/dispatcher.ts +++ b/client/utils/dispatcher.ts @@ -1,3 +1,4 @@ +import type { Message as ConsoleMessage } from 'console-feed/lib/definitions/Console'; // Inspired by // https://github.com/codesandbox/codesandbox-client/blob/master/packages/codesandbox-api/src/dispatcher/index.ts @@ -28,7 +29,7 @@ export interface Message { payload?: unknown; } -let listener: ((message: Message) => void) | null = null; +let listener: ((message: Message | ConsoleMessage) => void) | null = null; /** * Registers a frame to receive future dispatched messages. @@ -48,7 +49,7 @@ export function registerFrame( }; } -function notifyListener(message: Message): void { +function notifyListener(message: Message | ConsoleMessage): void { if (listener) listener(message); } @@ -74,7 +75,9 @@ export function dispatchMessage(message: Message | undefined | null): void { /** * Call callback to remove listener */ -export function listen(callback: (message: Message) => void): () => void { +export function listen( + callback: (message: Message | ConsoleMessage) => void +): () => void { listener = callback; return () => { listener = null; From 7173565b08fda2d3263a4b11c99f8bc147ea74c8 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sat, 10 Jan 2026 22:13:26 +0000 Subject: [PATCH 23/24] IDE/hooks/useP5Version: correct the context type definition --- client/modules/IDE/hooks/useP5Version.tsx | 29 +++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/client/modules/IDE/hooks/useP5Version.tsx b/client/modules/IDE/hooks/useP5Version.tsx index a594c86372..84c7fddf4e 100644 --- a/client/modules/IDE/hooks/useP5Version.tsx +++ b/client/modules/IDE/hooks/useP5Version.tsx @@ -20,7 +20,25 @@ export const p5SoundURLOld = p5SoundURLOldTemplate.replace( ); export const p5URL = p5URLTemplate.replace('$VERSION', currentP5Version); -const P5VersionContext = React.createContext({}); +const P5VersionContext = React.createContext<{ + indexID: string; + versionInfo: { + version: string; + isVersion2: boolean; + minified: boolean; + replaceVersion: (newVersion: string) => void; + p5Sound: boolean; + setP5Sound: (enabled: boolean) => string; + setP5SoundURL: (url: string) => string; + p5SoundURL: string | null; + p5PreloadAddon: boolean; + setP5PreloadAddon: (enabled: boolean) => string; + p5ShapesAddon: boolean; + setP5ShapesAddon: (enabled: boolean) => string; + p5DataAddon: boolean; + setP5DataAddon: (enabled: boolean) => string; + } | null; +} | null>(null); export function P5VersionProvider(props: { children: React.ReactNode }) { const files = useSelector((state: RootState) => state.files); @@ -189,7 +207,7 @@ export function P5VersionProvider(props: { children: React.ReactNode }) { p5Sound: !!p5SoundNode, setP5Sound, setP5SoundURL, - p5SoundURL: p5SoundNode?.getAttribute('src'), + p5SoundURL: p5SoundNode?.getAttribute('src') ?? null, p5PreloadAddon: !!p5PreloadAddonNode, setP5PreloadAddon, p5ShapesAddon: !!p5ShapesAddonNode, @@ -212,8 +230,11 @@ export function P5VersionProvider(props: { children: React.ReactNode }) { } export function useP5Version() { - if (!P5VersionContext) { + const context = useContext(P5VersionContext); + + if (!context) { throw new Error('useP5Version must be used within a P5VersionProvider'); } - return useContext(P5VersionContext); + + return context; } From 28b9b8c32fcbfd1af0558bcf32f050bdf316f5d1 Mon Sep 17 00:00:00 2001 From: Claire Peng Date: Sun, 18 Jan 2026 00:11:54 +0000 Subject: [PATCH 24/24] resolve type errors --- .../components/Header/Toolbar.unit.test.jsx | 8 +++++++- .../Preferences/Preferences.unit.test.jsx | 18 ++++++++++++------ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/client/modules/IDE/components/Header/Toolbar.unit.test.jsx b/client/modules/IDE/components/Header/Toolbar.unit.test.jsx index 82b4cb7f4c..ef0b7c21a9 100644 --- a/client/modules/IDE/components/Header/Toolbar.unit.test.jsx +++ b/client/modules/IDE/components/Header/Toolbar.unit.test.jsx @@ -11,6 +11,7 @@ import { } from '../../../../test-utils'; import { selectProjectName } from '../../selectors/project'; import ToolbarComponent from './Toolbar'; +import { P5VersionProvider } from '../../hooks/useP5Version'; const server = setupServer( rest.put(`/projects/id`, (req, res, ctx) => res(ctx.json(req.body))) @@ -49,7 +50,12 @@ const renderComponent = (extraState = {}) => { return { ...props, - ...reduxRender(, { initialState }) + ...reduxRender( + + + , + { initialState } + ) }; }; diff --git a/client/modules/IDE/components/Preferences/Preferences.unit.test.jsx b/client/modules/IDE/components/Preferences/Preferences.unit.test.jsx index 13e4c88881..41391a615b 100644 --- a/client/modules/IDE/components/Preferences/Preferences.unit.test.jsx +++ b/client/modules/IDE/components/Preferences/Preferences.unit.test.jsx @@ -3,6 +3,7 @@ import { act, fireEvent, reduxRender, screen } from '../../../../test-utils'; import { initialState } from '../../reducers/preferences'; import Preferences from './index'; import * as PreferencesActions from '../../actions/preferences'; +import { P5VersionProvider } from '../../hooks/useP5Version'; describe('', () => { // For backwards compatibility, spy on each action creator to see when it was dispatched. @@ -14,14 +15,19 @@ describe('', () => { ); const subject = (initialPreferences = {}) => - reduxRender(, { - initialState: { - preferences: { - ...initialState, - ...initialPreferences + reduxRender( + + + , + { + initialState: { + preferences: { + ...initialState, + ...initialPreferences + } } } - }); + ); afterEach(() => { jest.clearAllMocks();