Skip to content

Commit aa3b454

Browse files
committed
Merge remote-tracking branch 'upstream/develop' into fix/toolbar-title
# Conflicts: # client/modules/IDE/components/Header/Toolbar.jsx # client/modules/IDE/components/Header/Toolbar.unit.test.jsx
2 parents 002945e + e668ae4 commit aa3b454

File tree

16 files changed

+700
-995
lines changed

16 files changed

+700
-995
lines changed

client/modules/IDE/components/Editor.jsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import '../../../utils/htmlmixed';
4444
import '../../../utils/p5-javascript';
4545
import Timer from '../components/Timer';
4646
import EditorAccessibility from '../components/EditorAccessibility';
47+
import { selectActiveFile } from '../selectors/files';
4748
import AssetPreview from './AssetPreview';
4849
import { metaKey } from '../../../utils/metaKey';
4950
import './show-hint';
@@ -605,10 +606,7 @@ Editor.propTypes = {
605606
function mapStateToProps(state) {
606607
return {
607608
files: state.files,
608-
file:
609-
state.files.find((file) => file.isSelectedFile) ||
610-
state.files.find((file) => file.name === 'sketch.js') ||
611-
state.files.find((file) => file.name !== 'root'),
609+
file: selectActiveFile(state),
612610
htmlFile: getHTMLFile(state.files),
613611
ide: state.ide,
614612
preferences: state.preferences,

client/modules/IDE/components/Header/Nav.jsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { setLanguage } from '../../actions/preferences';
1414
import NavBar from '../../../../components/Nav/NavBar';
1515
import CaretLeftIcon from '../../../../images/left-arrow.svg';
1616
import LogoIcon from '../../../../images/p5js-logo-small.svg';
17+
import { selectRootFile } from '../../selectors/files';
1718
import { selectSketchPath } from '../../selectors/project';
1819
import { metaKey, metaKeyName } from '../../../../utils/metaKey';
1920
import { useSketchActions } from '../../hooks';
@@ -102,17 +103,14 @@ const DashboardMenu = () => {
102103
);
103104
};
104105

105-
const ProjectMenu = (props) => {
106+
const ProjectMenu = () => {
106107
const isUserOwner = useSelector(getIsUserOwner);
107108
const project = useSelector((state) => state.project);
108109
const user = useSelector((state) => state.user);
109110

110111
const isUnsaved = !project?.id;
111112

112-
// TODO: use selectRootFile selector
113-
const rootFile = useSelector(
114-
(state) => state.files.filter((file) => file.name === 'root')[0]
115-
);
113+
const rootFile = useSelector(selectRootFile);
116114

117115
const cmRef = useContext(CmControllerContext);
118116

Lines changed: 121 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -1,194 +1,140 @@
1-
import PropTypes from 'prop-types';
21
import React from 'react';
3-
import { connect } from 'react-redux';
4-
import { Link } from 'react-router-dom';
52
import classNames from 'classnames';
6-
import { withTranslation } from 'react-i18next';
7-
import * as IDEActions from '../../actions/ide';
8-
import * as preferenceActions from '../../actions/preferences';
9-
import * as projectActions from '../../actions/project';
3+
import PropTypes from 'prop-types';
4+
import { useTranslation } from 'react-i18next';
5+
import { useDispatch, useSelector } from 'react-redux';
6+
import { Link } from 'react-router-dom';
7+
import {
8+
openPreferences,
9+
startAccessibleSketch,
10+
startSketch,
11+
stopSketch
12+
} from '../../actions/ide';
13+
import {
14+
setAutorefresh,
15+
setGridOutput,
16+
setTextOutput
17+
} from '../../actions/preferences';
18+
import { useSketchActions } from '../../hooks';
1019

1120
import PlayIcon from '../../../../images/play.svg';
1221
import StopIcon from '../../../../images/stop.svg';
1322
import PreferencesIcon from '../../../../images/preferences.svg';
1423
import EditableInput from '../EditableInput';
1524

16-
class Toolbar extends React.Component {
17-
constructor(props) {
18-
super(props);
19-
this.handleProjectNameSave = this.handleProjectNameSave.bind(this);
20-
}
25+
const Toolbar = (props) => {
26+
const { isPlaying, infiniteLoop, preferencesIsVisible } = useSelector(
27+
(state) => state.ide
28+
);
29+
const project = useSelector((state) => state.project);
30+
const autorefresh = useSelector((state) => state.preferences.autorefresh);
31+
const dispatch = useDispatch();
2132

22-
handleProjectNameSave(value) {
23-
const newProjectName = value.trim();
24-
this.props.setProjectName(newProjectName);
25-
if (this.props.project.id) {
26-
this.props.saveProject();
27-
}
28-
}
33+
const { t } = useTranslation();
34+
const { changeSketchName, canEditProjectName } = useSketchActions();
2935

30-
canEditProjectName() {
31-
return (
32-
(this.props.owner &&
33-
this.props.owner.username &&
34-
this.props.owner.username === this.props.currentUser) ||
35-
!this.props.owner ||
36-
!this.props.owner.username
37-
);
38-
}
36+
const playButtonClass = classNames({
37+
'toolbar__play-button': true,
38+
'toolbar__play-button--selected': isPlaying
39+
});
40+
const stopButtonClass = classNames({
41+
'toolbar__stop-button': true,
42+
'toolbar__stop-button--selected': !isPlaying
43+
});
44+
const preferencesButtonClass = classNames({
45+
'toolbar__preferences-button': true,
46+
'toolbar__preferences-button--selected': preferencesIsVisible
47+
});
3948

40-
render() {
41-
const playButtonClass = classNames({
42-
'toolbar__play-button': true,
43-
'toolbar__play-button--selected': this.props.isPlaying
44-
});
45-
const stopButtonClass = classNames({
46-
'toolbar__stop-button': true,
47-
'toolbar__stop-button--selected': !this.props.isPlaying
48-
});
49-
const preferencesButtonClass = classNames({
50-
'toolbar__preferences-button': true,
51-
'toolbar__preferences-button--selected': this.props.preferencesIsVisible
52-
});
53-
54-
const canEditProjectName = this.canEditProjectName();
55-
56-
return (
57-
<div className="toolbar">
58-
<button
59-
className="toolbar__play-sketch-button"
60-
onClick={() => {
61-
this.props.syncFileContent();
62-
this.props.startAccessibleSketch();
63-
this.props.setTextOutput(true);
64-
this.props.setGridOutput(true);
49+
return (
50+
<div className="toolbar">
51+
<button
52+
className="toolbar__play-sketch-button"
53+
onClick={() => {
54+
props.syncFileContent();
55+
dispatch(startAccessibleSketch());
56+
dispatch(setTextOutput(true));
57+
dispatch(setGridOutput(true));
58+
}}
59+
aria-label={t('Toolbar.PlaySketchARIA')}
60+
disabled={infiniteLoop}
61+
>
62+
<PlayIcon focusable="false" aria-hidden="true" />
63+
</button>
64+
<button
65+
className={playButtonClass}
66+
onClick={() => {
67+
props.syncFileContent();
68+
dispatch(startSketch());
69+
}}
70+
aria-label={t('Toolbar.PlayOnlyVisualSketchARIA')}
71+
title={t('Toolbar.PlaySketchARIA')}
72+
disabled={infiniteLoop}
73+
>
74+
<PlayIcon focusable="false" aria-hidden="true" />
75+
</button>
76+
<button
77+
className={stopButtonClass}
78+
onClick={() => dispatch(stopSketch())}
79+
aria-label={t('Toolbar.StopSketchARIA')}
80+
title={t('Toolbar.StopSketchARIA')}
81+
>
82+
<StopIcon focusable="false" aria-hidden="true" />
83+
</button>
84+
<div className="toolbar__autorefresh">
85+
<input
86+
id="autorefresh"
87+
className="checkbox__autorefresh"
88+
type="checkbox"
89+
checked={autorefresh}
90+
onChange={(event) => {
91+
dispatch(setAutorefresh(event.target.checked));
6592
}}
66-
aria-label={this.props.t('Toolbar.PlaySketchARIA')}
67-
disabled={this.props.infiniteLoop}
68-
>
69-
<PlayIcon focusable="false" aria-hidden="true" />
70-
</button>
71-
<button
72-
className={playButtonClass}
73-
onClick={() => {
74-
this.props.syncFileContent();
75-
this.props.startSketch();
93+
/>
94+
<label htmlFor="autorefresh" className="toolbar__autorefresh-label">
95+
{t('Toolbar.Auto-refresh')}
96+
</label>
97+
</div>
98+
<div className="toolbar__project-name-container">
99+
<EditableInput
100+
value={project.name}
101+
disabled={!canEditProjectName}
102+
aria-label={t('Toolbar.EditSketchARIA')}
103+
inputProps={{
104+
maxLength: 128,
105+
'aria-label': t('Toolbar.NewSketchNameARIA')
76106
}}
77-
aria-label={this.props.t('Toolbar.PlayOnlyVisualSketchARIA')}
78-
title={this.props.t('Toolbar.PlaySketchARIA')}
79-
disabled={this.props.infiniteLoop}
80-
>
81-
<PlayIcon focusable="false" aria-hidden="true" />
82-
</button>
83-
<button
84-
className={stopButtonClass}
85-
onClick={this.props.stopSketch}
86-
aria-label={this.props.t('Toolbar.StopSketchARIA')}
87-
title={this.props.t('Toolbar.StopSketchARIA')}
88-
>
89-
<StopIcon focusable="false" aria-hidden="true" />
90-
</button>
91-
<div className="toolbar__autorefresh">
92-
<input
93-
id="autorefresh"
94-
className="checkbox__autorefresh"
95-
type="checkbox"
96-
checked={this.props.autorefresh}
97-
onChange={(event) => {
98-
this.props.setAutorefresh(event.target.checked);
99-
}}
100-
/>
101-
<label htmlFor="autorefresh" className="toolbar__autorefresh-label">
102-
{this.props.t('Toolbar.Auto-refresh')}
103-
</label>
104-
</div>
105-
<div className="toolbar__project-name-container">
106-
<EditableInput
107-
value={this.props.project.name}
108-
disabled={!canEditProjectName}
109-
aria-label={this.props.t('Toolbar.EditSketchARIA')}
110-
inputProps={{
111-
maxLength: 128,
112-
'aria-label': this.props.t('Toolbar.NewSketchNameARIA')
113-
}}
114-
validate={(text) => text.trim().length > 0}
115-
onChange={this.handleProjectNameSave}
116-
/>
117-
{(() => {
118-
if (this.props.owner) {
119-
return (
120-
<p className="toolbar__project-owner">
121-
{this.props.t('Toolbar.By')}{' '}
122-
<Link to={`/${this.props.owner.username}/sketches`}>
123-
{this.props.owner.username}
124-
</Link>
125-
</p>
126-
);
127-
}
128-
return null;
129-
})()}
130-
</div>
131-
<button
132-
className={preferencesButtonClass}
133-
onClick={this.props.openPreferences}
134-
aria-label={this.props.t('Toolbar.OpenPreferencesARIA')}
135-
title={this.props.t('Toolbar.OpenPreferencesARIA')}
136-
>
137-
<PreferencesIcon focusable="false" aria-hidden="true" />
138-
</button>
107+
validate={(text) => text.trim().length > 0}
108+
onChange={changeSketchName}
109+
/>
110+
{(() => {
111+
if (project.owner) {
112+
return (
113+
<p className="toolbar__project-project.owner">
114+
{t('Toolbar.By')}{' '}
115+
<Link to={`/${project.owner.username}/sketches`}>
116+
{project.owner.username}
117+
</Link>
118+
</p>
119+
);
120+
}
121+
return null;
122+
})()}
139123
</div>
140-
);
141-
}
142-
}
124+
<button
125+
className={preferencesButtonClass}
126+
onClick={() => dispatch(openPreferences())}
127+
aria-label={t('Toolbar.OpenPreferencesARIA')}
128+
title={t('Toolbar.OpenPreferencesARIA')}
129+
>
130+
<PreferencesIcon focusable="false" aria-hidden="true" />
131+
</button>
132+
</div>
133+
);
134+
};
143135

144136
Toolbar.propTypes = {
145-
isPlaying: PropTypes.bool.isRequired,
146-
preferencesIsVisible: PropTypes.bool.isRequired,
147-
stopSketch: PropTypes.func.isRequired,
148-
setProjectName: PropTypes.func.isRequired,
149-
openPreferences: PropTypes.func.isRequired,
150-
owner: PropTypes.shape({
151-
username: PropTypes.string
152-
}),
153-
project: PropTypes.shape({
154-
name: PropTypes.string.isRequired,
155-
id: PropTypes.string
156-
}).isRequired,
157-
infiniteLoop: PropTypes.bool.isRequired,
158-
autorefresh: PropTypes.bool.isRequired,
159-
setAutorefresh: PropTypes.func.isRequired,
160-
setTextOutput: PropTypes.func.isRequired,
161-
setGridOutput: PropTypes.func.isRequired,
162-
startSketch: PropTypes.func.isRequired,
163-
startAccessibleSketch: PropTypes.func.isRequired,
164-
saveProject: PropTypes.func.isRequired,
165-
currentUser: PropTypes.string,
166-
t: PropTypes.func.isRequired,
167137
syncFileContent: PropTypes.func.isRequired
168138
};
169139

170-
Toolbar.defaultProps = {
171-
owner: undefined,
172-
currentUser: undefined
173-
};
174-
175-
function mapStateToProps(state) {
176-
return {
177-
autorefresh: state.preferences.autorefresh,
178-
currentUser: state.user.username,
179-
infiniteLoop: state.ide.infiniteLoop,
180-
isPlaying: state.ide.isPlaying,
181-
owner: state.project.owner,
182-
preferencesIsVisible: state.ide.preferencesIsVisible,
183-
project: state.project
184-
};
185-
}
186-
187-
const mapDispatchToProps = {
188-
...IDEActions,
189-
...preferenceActions,
190-
...projectActions
191-
};
192-
193-
export const ToolbarComponent = withTranslation()(Toolbar);
194-
export default connect(mapStateToProps, mapDispatchToProps)(ToolbarComponent);
140+
export default Toolbar;

0 commit comments

Comments
 (0)