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/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';
diff --git a/client/modules/IDE/components/Console.jsx b/client/modules/IDE/components/Console.jsx
index 3494fccdee..d1d7befde9 100644
--- a/client/modules/IDE/components/Console.jsx
+++ b/client/modules/IDE/components/Console.jsx
@@ -11,10 +11,9 @@ 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 useHandleMessageEvent from '../hooks/useHandleMessageEvent';
+import { useDidUpdate, useHandleMessageEvent } from '../hooks';
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/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/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/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/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();
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/custom-hooks.js b/client/modules/IDE/hooks/custom-hooks.js
deleted file mode 100644
index 6471bbcac2..0000000000
--- a/client/modules/IDE/hooks/custom-hooks.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import { useEffect, useRef, useState } from 'react';
-
-export const noop = () => {};
-
-export const useDidUpdate = (callback, deps) => {
- const hasMount = useRef(false);
-
- useEffect(() => {
- if (hasMount.current) {
- callback();
- } else {
- hasMount.current = true;
- }
- }, 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.js b/client/modules/IDE/hooks/index.js
deleted file mode 100644
index d309cd4b88..0000000000
--- a/client/modules/IDE/hooks/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export { default as useSketchActions } from './useSketchActions';
-export { default as useWhatPage } from './useWhatPage';
diff --git a/client/modules/IDE/hooks/index.ts b/client/modules/IDE/hooks/index.ts
new file mode 100644
index 0000000000..fd3a9b3f52
--- /dev/null
+++ b/client/modules/IDE/hooks/index.ts
@@ -0,0 +1,6 @@
+export * from './useSketchActions';
+export * from './useWhatPage';
+export * from './useIsMobile';
+export * from './useDidUpdate';
+export * from './useInterval';
+export * from './useHandleMessageEvent';
diff --git a/client/modules/IDE/hooks/useDidUpdate.ts b/client/modules/IDE/hooks/useDidUpdate.ts
new file mode 100644
index 0000000000..4d04f1065b
--- /dev/null
+++ b/client/modules/IDE/hooks/useDidUpdate.ts
@@ -0,0 +1,16 @@
+import React, { useEffect, useRef } from 'react';
+
+export const useDidUpdate = (
+ callback: () => void,
+ deps: React.DependencyList = []
+) => {
+ const hasMount = useRef(false);
+
+ useEffect(() => {
+ if (hasMount.current) {
+ callback();
+ } else {
+ hasMount.current = true;
+ }
+ }, deps);
+};
diff --git a/client/modules/IDE/hooks/useHandleMessageEvent.js b/client/modules/IDE/hooks/useHandleMessageEvent.ts
similarity index 69%
rename from client/modules/IDE/hooks/useHandleMessageEvent.js
rename to client/modules/IDE/hooks/useHandleMessageEvent.ts
index 5603c1d697..db39665f5a 100644
--- a/client/modules/IDE/hooks/useHandleMessageEvent.js
+++ 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