Skip to content

Commit c87978b

Browse files
authored
Merge branch 'develop' into refactor/redux-toolkit
2 parents 3d827d6 + 5471f8a commit c87978b

File tree

15 files changed

+309
-427
lines changed

15 files changed

+309
-427
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ terraform/.terraform/
2020

2121
storybook-static
2222
duplicates.json
23+
24+
coverage

client/components/Nav.jsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { logoutUser } from '../modules/User/actions';
1818
import getConfig from '../utils/getConfig';
1919
import { metaKeyName, metaKey } from '../utils/metaKey';
2020
import { getIsUserOwner } from '../modules/IDE/selectors/users';
21+
import { selectSketchPath } from '../modules/IDE/selectors/project';
2122

2223
import CaretLeftIcon from '../images/left-arrow.svg';
2324
import TriangleIcon from '../images/down-filled-triangle.svg';
@@ -245,7 +246,7 @@ class Nav extends React.PureComponent {
245246
/>
246247
</li>
247248
<li className="nav__item nav__item--no-icon">
248-
<Link to="/" className="nav__back-link">
249+
<Link to={this.props.editorLink} className="nav__back-link">
249250
<CaretLeftIcon
250251
className="nav__back-icon"
251252
focusable="false"
@@ -990,7 +991,8 @@ Nav.propTypes = {
990991
t: PropTypes.func.isRequired,
991992
setLanguage: PropTypes.func.isRequired,
992993
language: PropTypes.string.isRequired,
993-
isUserOwner: PropTypes.bool.isRequired
994+
isUserOwner: PropTypes.bool.isRequired,
995+
editorLink: PropTypes.string
994996
};
995997

996998
Nav.defaultProps = {
@@ -1003,7 +1005,8 @@ Nav.defaultProps = {
10031005
warnIfUnsavedChanges: undefined,
10041006
params: {
10051007
username: undefined
1006-
}
1008+
},
1009+
editorLink: '/'
10071010
};
10081011

10091012
function mapStateToProps(state) {
@@ -1013,7 +1016,8 @@ function mapStateToProps(state) {
10131016
unsavedChanges: state.ide.unsavedChanges,
10141017
rootFile: state.files.filter((file) => file.name === 'root')[0],
10151018
language: state.preferences.language,
1016-
isUserOwner: getIsUserOwner(state)
1019+
isUserOwner: getIsUserOwner(state),
1020+
editorLink: selectSketchPath(state)
10171021
};
10181022
}
10191023

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import classNames from 'classnames';
2+
import PropTypes from 'prop-types';
3+
import React, { useEffect, useRef } from 'react';
4+
import ExitIcon from '../../../images/exit.svg';
5+
6+
// Common logic from NewFolderModal, NewFileModal, UploadFileModal
7+
8+
const Modal = ({
9+
title,
10+
onClose,
11+
closeAriaLabel,
12+
contentClassName,
13+
children
14+
}) => {
15+
const modalRef = useRef(null);
16+
17+
const handleOutsideClick = (e) => {
18+
// ignore clicks on the component itself
19+
if (modalRef.current?.contains?.(e.target)) return;
20+
21+
onClose();
22+
};
23+
24+
useEffect(() => {
25+
modalRef.current?.focus();
26+
document.addEventListener('click', handleOutsideClick, false);
27+
28+
return () => {
29+
document.removeEventListener('click', handleOutsideClick, false);
30+
};
31+
}, []);
32+
33+
return (
34+
<section className="modal" ref={modalRef}>
35+
<div className={classNames('modal-content', contentClassName)}>
36+
<div className="modal__header">
37+
<h2 className="modal__title">{title}</h2>
38+
<button
39+
className="modal__exit-button"
40+
onClick={onClose}
41+
aria-label={closeAriaLabel}
42+
>
43+
<ExitIcon focusable="false" aria-hidden="true" />
44+
</button>
45+
</div>
46+
{children}
47+
</div>
48+
</section>
49+
);
50+
};
51+
52+
Modal.propTypes = {
53+
title: PropTypes.string.isRequired,
54+
onClose: PropTypes.func.isRequired,
55+
closeAriaLabel: PropTypes.string.isRequired,
56+
contentClassName: PropTypes.string,
57+
children: PropTypes.node.isRequired
58+
};
59+
60+
Modal.defaultProps = {
61+
contentClassName: ''
62+
};
63+
64+
export default Modal;
Lines changed: 16 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,22 @@
1-
import PropTypes from 'prop-types';
21
import React from 'react';
3-
import { connect } from 'react-redux';
4-
import { bindActionCreators } from 'redux';
5-
import { withTranslation } from 'react-i18next';
2+
import { useDispatch } from 'react-redux';
3+
import { useTranslation } from 'react-i18next';
4+
import Modal from './Modal';
65
import NewFileForm from './NewFileForm';
76
import { closeNewFileModal } from '../actions/ide';
8-
import ExitIcon from '../../../images/exit.svg';
97

10-
// At some point this will probably be generalized to a generic modal
11-
// in which you can insert different content
12-
// but for now, let's just make this work
13-
class NewFileModal extends React.Component {
14-
constructor(props) {
15-
super(props);
16-
this.focusOnModal = this.focusOnModal.bind(this);
17-
this.handleOutsideClick = this.handleOutsideClick.bind(this);
18-
}
19-
20-
componentDidMount() {
21-
this.focusOnModal();
22-
document.addEventListener('click', this.handleOutsideClick, false);
23-
}
24-
25-
componentWillUnmount() {
26-
document.removeEventListener('click', this.handleOutsideClick, false);
27-
}
28-
29-
handleOutsideClick(e) {
30-
// ignore clicks on the component itself
31-
if (e.path.includes(this.modal)) return;
32-
33-
this.props.closeNewFileModal();
34-
}
35-
36-
focusOnModal() {
37-
this.modal.focus();
38-
}
39-
40-
render() {
41-
return (
42-
<section
43-
className="modal"
44-
ref={(element) => {
45-
this.modal = element;
46-
}}
47-
>
48-
<div className="modal-content">
49-
<div className="modal__header">
50-
<h2 className="modal__title">
51-
{this.props.t('NewFileModal.Title')}
52-
</h2>
53-
<button
54-
className="modal__exit-button"
55-
onClick={this.props.closeNewFileModal}
56-
aria-label={this.props.t('NewFileModal.CloseButtonARIA')}
57-
>
58-
<ExitIcon focusable="false" aria-hidden="true" />
59-
</button>
60-
</div>
61-
<NewFileForm focusOnModal={this.focusOnModal} />
62-
</div>
63-
</section>
64-
);
65-
}
66-
}
67-
68-
NewFileModal.propTypes = {
69-
closeNewFileModal: PropTypes.func.isRequired,
70-
t: PropTypes.func.isRequired
8+
const NewFileModal = () => {
9+
const { t } = useTranslation();
10+
const dispatch = useDispatch();
11+
return (
12+
<Modal
13+
title={t('NewFileModal.Title')}
14+
closeAriaLabel={t('NewFileModal.CloseButtonARIA')}
15+
onClose={() => dispatch(closeNewFileModal())}
16+
>
17+
<NewFileForm />
18+
</Modal>
19+
);
7120
};
7221

73-
function mapStateToProps() {
74-
return {};
75-
}
76-
77-
function mapDispatchToProps(dispatch) {
78-
return bindActionCreators({ closeNewFileModal }, dispatch);
79-
}
80-
81-
export default withTranslation()(
82-
connect(mapStateToProps, mapDispatchToProps)(NewFileModal)
83-
);
22+
export default NewFileModal;
Lines changed: 18 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,23 @@
1-
import PropTypes from 'prop-types';
21
import React from 'react';
3-
import { withTranslation } from 'react-i18next';
2+
import { useTranslation } from 'react-i18next';
3+
import { useDispatch } from 'react-redux';
4+
import { closeNewFolderModal } from '../actions/ide';
5+
import Modal from './Modal';
46
import NewFolderForm from './NewFolderForm';
5-
import ExitIcon from '../../../images/exit.svg';
67

7-
class NewFolderModal extends React.Component {
8-
constructor(props) {
9-
super(props);
10-
this.handleOutsideClick = this.handleOutsideClick.bind(this);
11-
}
12-
13-
componentDidMount() {
14-
this.newFolderModal.focus();
15-
document.addEventListener('click', this.handleOutsideClick, false);
16-
}
17-
18-
componentWillUnmount() {
19-
document.removeEventListener('click', this.handleOutsideClick, false);
20-
}
21-
22-
handleOutsideClick(e) {
23-
// ignore clicks on the component itself
24-
if (e.path.includes(this.newFolderModal)) return;
25-
26-
this.props.closeModal();
27-
}
28-
29-
render() {
30-
return (
31-
<section
32-
className="modal"
33-
ref={(element) => {
34-
this.newFolderModal = element;
35-
}}
36-
>
37-
<div className="modal-content-folder">
38-
<div className="modal__header">
39-
<h2 className="modal__title">
40-
{this.props.t('NewFolderModal.Title')}
41-
</h2>
42-
<button
43-
className="modal__exit-button"
44-
onClick={this.props.closeModal}
45-
aria-label={this.props.t('NewFolderModal.CloseButtonARIA')}
46-
>
47-
<ExitIcon focusable="false" aria-hidden="true" />
48-
</button>
49-
</div>
50-
<NewFolderForm />
51-
</div>
52-
</section>
53-
);
54-
}
55-
}
56-
57-
NewFolderModal.propTypes = {
58-
closeModal: PropTypes.func.isRequired,
59-
t: PropTypes.func.isRequired
8+
const NewFolderModal = () => {
9+
const { t } = useTranslation();
10+
const dispatch = useDispatch();
11+
return (
12+
<Modal
13+
title={t('NewFolderModal.Title')}
14+
closeAriaLabel={t('NewFolderModal.CloseButtonARIA')}
15+
onClose={() => dispatch(closeNewFolderModal())}
16+
contentClassName="modal-content-folder"
17+
>
18+
<NewFolderForm />
19+
</Modal>
20+
);
6021
};
6122

62-
export default withTranslation()(NewFolderModal);
23+
export default NewFolderModal;

0 commit comments

Comments
 (0)