Skip to content

Commit a6d1175

Browse files
committed
Merge remote-tracking branch 'upstream/develop' into refactor/overlay
# Conflicts: # client/components/Nav/NavBar.jsx # client/modules/App/components/Overlay.jsx # client/modules/IDE/pages/IDEView.jsx
2 parents 74084cd + d5eee45 commit a6d1175

File tree

108 files changed

+4846
-6300
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

108 files changed

+4846
-6300
lines changed

.storybook/preview-head.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
// https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/176#issuecomment-683150213
3+
window.$RefreshReg$ = () => {};
4+
window.$RefreshSig$ = () => () => {};
5+
</script>

.storybook/preview.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import React from 'react';
22
import { Provider } from 'react-redux';
3+
import { MemoryRouter } from 'react-router';
34

45
import ThemeProvider from '../client/modules/App/components/ThemeProvider';
56
import configureStore from '../client/store';
67
import '../client/i18n-test';
7-
import '../client/styles/build/css/main.css'
8+
import '../client/styles/storybook.css'
89

910
const initialState = window.__INITIAL_STATE__;
1011

@@ -13,9 +14,11 @@ const store = configureStore(initialState);
1314
export const decorators = [
1415
(Story) => (
1516
<Provider store={store}>
16-
<ThemeProvider>
17-
<Story />
18-
</ThemeProvider>
17+
<MemoryRouter>
18+
<ThemeProvider>
19+
<Story />
20+
</ThemeProvider>
21+
</MemoryRouter>
1922
</Provider>
2023
),
2124
]

client/common/ButtonOrLink.test.jsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { render, screen, fireEvent } from '../test-utils';
2+
import { render, screen, fireEvent, waitFor, history } from '../test-utils';
33
import ButtonOrLink from './ButtonOrLink';
44

55
describe('ButtonOrLink', () => {
@@ -25,8 +25,12 @@ describe('ButtonOrLink', () => {
2525
expect(link).toHaveAttribute('href', 'https://p5js.org');
2626
});
2727

28-
it('can render an internal link with react-router', () => {
28+
it('can render an internal link with react-router', async () => {
2929
render(<ButtonOrLink href="/about">About</ButtonOrLink>);
30-
// TODO: how can this be tested? Needs a router provider?
30+
31+
const link = screen.getByText('About');
32+
fireEvent.click(link);
33+
34+
await waitFor(() => expect(history.location.pathname).toEqual('/about'));
3135
});
3236
});

client/common/icons.jsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,18 @@ import DropdownArrow from '../images/down-filled-triangle.svg';
1313
import Preferences from '../images/preferences.svg';
1414
import Play from '../images/triangle-arrow-right.svg';
1515
import More from '../images/more.svg';
16+
import Editor from '../images/editor.svg';
17+
import Account from '../images/account.svg';
1618
import Code from '../images/code.svg';
1719
import Save from '../images/save.svg';
1820
import Terminal from '../images/terminal.svg';
19-
2021
import Folder from '../images/folder-padded.svg';
21-
2222
import CircleTerminal from '../images/circle-terminal.svg';
2323
import CircleFolder from '../images/circle-folder.svg';
2424
import CircleInfo from '../images/circle-info.svg';
25+
import Add from '../images/add.svg';
26+
import Filter from '../images/filter.svg';
27+
import Cross from '../images/cross.svg';
2528

2629
// HOC that adds the right web accessibility props
2730
// https://www.scottohara.me/blog/2019/05/22/contextual-images-svgs-and-a11y.html
@@ -83,16 +86,19 @@ export const GoogleIcon = withLabel(Google);
8386
export const PlusIcon = withLabel(Plus);
8487
export const CloseIcon = withLabel(Close);
8588
export const ExitIcon = withLabel(Exit);
89+
export const EditorIcon = withLabel(Editor);
90+
export const AccountIcon = withLabel(Account);
8691
export const DropdownArrowIcon = withLabel(DropdownArrow);
8792
export const PreferencesIcon = withLabel(Preferences);
8893
export const PlayIcon = withLabel(Play);
8994
export const MoreIcon = withLabel(More);
9095
export const TerminalIcon = withLabel(Terminal);
9196
export const CodeIcon = withLabel(Code);
9297
export const SaveIcon = withLabel(Save);
93-
9498
export const FolderIcon = withLabel(Folder);
95-
99+
export const CrossIcon = withLabel(Cross);
96100
export const CircleTerminalIcon = withLabel(CircleTerminal);
97101
export const CircleFolderIcon = withLabel(CircleFolder);
98102
export const CircleInfoIcon = withLabel(CircleInfo);
103+
export const AddIcon = withLabel(Add);
104+
export const FilterIcon = withLabel(Filter);

client/common/useKeyDownHandlers.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import mapKeys from 'lodash/mapKeys';
2+
import PropTypes from 'prop-types';
3+
import { useCallback, useEffect, useRef } from 'react';
4+
5+
/**
6+
* Attaches keydown handlers to the global document.
7+
*
8+
* Handles Mac/PC switching of Ctrl to Cmd.
9+
*
10+
* @param {Record<string, (e: KeyboardEvent) => void>} keyHandlers - an object
11+
* which maps from the key to its event handler. The object keys are a combination
12+
* of the key and prefixes `ctrl-` `shift-` (ie. 'ctrl-f', 'ctrl-shift-f')
13+
* and the values are the function to call when that specific key is pressed.
14+
*/
15+
export default function useKeyDownHandlers(keyHandlers) {
16+
/**
17+
* Instead of memoizing the handlers, use a ref and call the current
18+
* handler at the time of the event.
19+
*/
20+
const handlers = useRef(keyHandlers);
21+
22+
useEffect(() => {
23+
handlers.current = mapKeys(keyHandlers, (value, key) => key?.toLowerCase());
24+
}, [keyHandlers]);
25+
26+
/**
27+
* Will call all matching handlers, starting with the most specific: 'ctrl-shift-f' => 'ctrl-f' => 'f'.
28+
* Can use e.stopPropagation() to prevent subsequent handlers.
29+
* @type {(function(KeyboardEvent): void)}
30+
*/
31+
const handleEvent = useCallback((e) => {
32+
if (!e.key) return;
33+
const isMac = navigator.userAgent.toLowerCase().indexOf('mac') !== -1;
34+
const isCtrl = isMac ? e.metaKey : e.ctrlKey;
35+
if (e.shiftKey && isCtrl) {
36+
handlers.current[`ctrl-shift-${e.key.toLowerCase()}`]?.(e);
37+
} else if (isCtrl) {
38+
handlers.current[`ctrl-${e.key.toLowerCase()}`]?.(e);
39+
}
40+
handlers.current[e.key?.toLowerCase()]?.(e);
41+
}, []);
42+
43+
useEffect(() => {
44+
document.addEventListener('keydown', handleEvent);
45+
46+
return () => document.removeEventListener('keydown', handleEvent);
47+
}, [handleEvent]);
48+
}
49+
50+
/**
51+
* Component version can be used in class components where hooks can't be used.
52+
*
53+
* @param {Record<string, (e: KeyboardEvent) => void>} handlers
54+
*/
55+
export const DocumentKeyDown = ({ handlers }) => {
56+
useKeyDownHandlers(handlers);
57+
return null;
58+
};
59+
DocumentKeyDown.propTypes = {
60+
handlers: PropTypes.objectOf(PropTypes.func)
61+
};

client/common/useModalClose.js

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useEffect, useRef } from 'react';
2+
import useKeyDownHandlers from './useKeyDownHandlers';
23

34
/**
45
* Common logic for Modal, Overlay, etc.
@@ -24,27 +25,21 @@ export default function useModalClose(onClose, passedRef) {
2425
useEffect(() => {
2526
modalRef.current?.focus();
2627

27-
function handleKeyDown(e) {
28-
if (e.key === 'Escape') {
29-
onClose?.();
30-
}
31-
}
32-
3328
function handleClick(e) {
3429
// ignore clicks on the component itself
3530
if (modalRef.current && !modalRef.current.contains(e.target)) {
3631
onClose?.();
3732
}
3833
}
3934

40-
document.addEventListener('mousedown', handleClick, false);
41-
document.addEventListener('keydown', handleKeyDown, false);
35+
document.addEventListener('click', handleClick, false);
4236

4337
return () => {
44-
document.removeEventListener('mousedown', handleClick, false);
45-
document.removeEventListener('keydown', handleKeyDown, false);
38+
document.removeEventListener('click', handleClick, false);
4639
};
4740
}, [onClose, modalRef]);
4841

42+
useKeyDownHandlers({ escape: onClose });
43+
4944
return modalRef;
5045
}

0 commit comments

Comments
 (0)