Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/develop' into fix/toolbar-title
Browse files Browse the repository at this point in the history
# Conflicts:
#	client/modules/IDE/components/Header/Toolbar.jsx
#	client/modules/IDE/components/Header/Toolbar.unit.test.jsx
  • Loading branch information
lindapaiste committed Aug 13, 2023
2 parents 002945e + e668ae4 commit aa3b454
Show file tree
Hide file tree
Showing 16 changed files with 700 additions and 995 deletions.
6 changes: 2 additions & 4 deletions client/modules/IDE/components/Editor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import '../../../utils/htmlmixed';
import '../../../utils/p5-javascript';
import Timer from '../components/Timer';
import EditorAccessibility from '../components/EditorAccessibility';
import { selectActiveFile } from '../selectors/files';
import AssetPreview from './AssetPreview';
import { metaKey } from '../../../utils/metaKey';
import './show-hint';
Expand Down Expand Up @@ -605,10 +606,7 @@ Editor.propTypes = {
function mapStateToProps(state) {
return {
files: state.files,
file:
state.files.find((file) => file.isSelectedFile) ||
state.files.find((file) => file.name === 'sketch.js') ||
state.files.find((file) => file.name !== 'root'),
file: selectActiveFile(state),
htmlFile: getHTMLFile(state.files),
ide: state.ide,
preferences: state.preferences,
Expand Down
8 changes: 3 additions & 5 deletions client/modules/IDE/components/Header/Nav.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { setLanguage } from '../../actions/preferences';
import NavBar from '../../../../components/Nav/NavBar';
import CaretLeftIcon from '../../../../images/left-arrow.svg';
import LogoIcon from '../../../../images/p5js-logo-small.svg';
import { selectRootFile } from '../../selectors/files';
import { selectSketchPath } from '../../selectors/project';
import { metaKey, metaKeyName } from '../../../../utils/metaKey';
import { useSketchActions } from '../../hooks';
Expand Down Expand Up @@ -102,17 +103,14 @@ const DashboardMenu = () => {
);
};

const ProjectMenu = (props) => {
const ProjectMenu = () => {
const isUserOwner = useSelector(getIsUserOwner);
const project = useSelector((state) => state.project);
const user = useSelector((state) => state.user);

const isUnsaved = !project?.id;

// TODO: use selectRootFile selector
const rootFile = useSelector(
(state) => state.files.filter((file) => file.name === 'root')[0]
);
const rootFile = useSelector(selectRootFile);

const cmRef = useContext(CmControllerContext);

Expand Down
296 changes: 121 additions & 175 deletions client/modules/IDE/components/Header/Toolbar.jsx
Original file line number Diff line number Diff line change
@@ -1,194 +1,140 @@
import PropTypes from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import classNames from 'classnames';
import { withTranslation } from 'react-i18next';
import * as IDEActions from '../../actions/ide';
import * as preferenceActions from '../../actions/preferences';
import * as projectActions from '../../actions/project';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import {
openPreferences,
startAccessibleSketch,
startSketch,
stopSketch
} from '../../actions/ide';
import {
setAutorefresh,
setGridOutput,
setTextOutput
} from '../../actions/preferences';
import { useSketchActions } from '../../hooks';

import PlayIcon from '../../../../images/play.svg';
import StopIcon from '../../../../images/stop.svg';
import PreferencesIcon from '../../../../images/preferences.svg';
import EditableInput from '../EditableInput';

class Toolbar extends React.Component {
constructor(props) {
super(props);
this.handleProjectNameSave = this.handleProjectNameSave.bind(this);
}
const Toolbar = (props) => {
const { isPlaying, infiniteLoop, preferencesIsVisible } = useSelector(
(state) => state.ide
);
const project = useSelector((state) => state.project);
const autorefresh = useSelector((state) => state.preferences.autorefresh);
const dispatch = useDispatch();

handleProjectNameSave(value) {
const newProjectName = value.trim();
this.props.setProjectName(newProjectName);
if (this.props.project.id) {
this.props.saveProject();
}
}
const { t } = useTranslation();
const { changeSketchName, canEditProjectName } = useSketchActions();

canEditProjectName() {
return (
(this.props.owner &&
this.props.owner.username &&
this.props.owner.username === this.props.currentUser) ||
!this.props.owner ||
!this.props.owner.username
);
}
const playButtonClass = classNames({
'toolbar__play-button': true,
'toolbar__play-button--selected': isPlaying
});
const stopButtonClass = classNames({
'toolbar__stop-button': true,
'toolbar__stop-button--selected': !isPlaying
});
const preferencesButtonClass = classNames({
'toolbar__preferences-button': true,
'toolbar__preferences-button--selected': preferencesIsVisible
});

render() {
const playButtonClass = classNames({
'toolbar__play-button': true,
'toolbar__play-button--selected': this.props.isPlaying
});
const stopButtonClass = classNames({
'toolbar__stop-button': true,
'toolbar__stop-button--selected': !this.props.isPlaying
});
const preferencesButtonClass = classNames({
'toolbar__preferences-button': true,
'toolbar__preferences-button--selected': this.props.preferencesIsVisible
});

const canEditProjectName = this.canEditProjectName();

return (
<div className="toolbar">
<button
className="toolbar__play-sketch-button"
onClick={() => {
this.props.syncFileContent();
this.props.startAccessibleSketch();
this.props.setTextOutput(true);
this.props.setGridOutput(true);
return (
<div className="toolbar">
<button
className="toolbar__play-sketch-button"
onClick={() => {
props.syncFileContent();
dispatch(startAccessibleSketch());
dispatch(setTextOutput(true));
dispatch(setGridOutput(true));
}}
aria-label={t('Toolbar.PlaySketchARIA')}
disabled={infiniteLoop}
>
<PlayIcon focusable="false" aria-hidden="true" />
</button>
<button
className={playButtonClass}
onClick={() => {
props.syncFileContent();
dispatch(startSketch());
}}
aria-label={t('Toolbar.PlayOnlyVisualSketchARIA')}
title={t('Toolbar.PlaySketchARIA')}
disabled={infiniteLoop}
>
<PlayIcon focusable="false" aria-hidden="true" />
</button>
<button
className={stopButtonClass}
onClick={() => dispatch(stopSketch())}
aria-label={t('Toolbar.StopSketchARIA')}
title={t('Toolbar.StopSketchARIA')}
>
<StopIcon focusable="false" aria-hidden="true" />
</button>
<div className="toolbar__autorefresh">
<input
id="autorefresh"
className="checkbox__autorefresh"
type="checkbox"
checked={autorefresh}
onChange={(event) => {
dispatch(setAutorefresh(event.target.checked));
}}
aria-label={this.props.t('Toolbar.PlaySketchARIA')}
disabled={this.props.infiniteLoop}
>
<PlayIcon focusable="false" aria-hidden="true" />
</button>
<button
className={playButtonClass}
onClick={() => {
this.props.syncFileContent();
this.props.startSketch();
/>
<label htmlFor="autorefresh" className="toolbar__autorefresh-label">
{t('Toolbar.Auto-refresh')}
</label>
</div>
<div className="toolbar__project-name-container">
<EditableInput
value={project.name}
disabled={!canEditProjectName}
aria-label={t('Toolbar.EditSketchARIA')}
inputProps={{
maxLength: 128,
'aria-label': t('Toolbar.NewSketchNameARIA')
}}
aria-label={this.props.t('Toolbar.PlayOnlyVisualSketchARIA')}
title={this.props.t('Toolbar.PlaySketchARIA')}
disabled={this.props.infiniteLoop}
>
<PlayIcon focusable="false" aria-hidden="true" />
</button>
<button
className={stopButtonClass}
onClick={this.props.stopSketch}
aria-label={this.props.t('Toolbar.StopSketchARIA')}
title={this.props.t('Toolbar.StopSketchARIA')}
>
<StopIcon focusable="false" aria-hidden="true" />
</button>
<div className="toolbar__autorefresh">
<input
id="autorefresh"
className="checkbox__autorefresh"
type="checkbox"
checked={this.props.autorefresh}
onChange={(event) => {
this.props.setAutorefresh(event.target.checked);
}}
/>
<label htmlFor="autorefresh" className="toolbar__autorefresh-label">
{this.props.t('Toolbar.Auto-refresh')}
</label>
</div>
<div className="toolbar__project-name-container">
<EditableInput
value={this.props.project.name}
disabled={!canEditProjectName}
aria-label={this.props.t('Toolbar.EditSketchARIA')}
inputProps={{
maxLength: 128,
'aria-label': this.props.t('Toolbar.NewSketchNameARIA')
}}
validate={(text) => text.trim().length > 0}
onChange={this.handleProjectNameSave}
/>
{(() => {
if (this.props.owner) {
return (
<p className="toolbar__project-owner">
{this.props.t('Toolbar.By')}{' '}
<Link to={`/${this.props.owner.username}/sketches`}>
{this.props.owner.username}
</Link>
</p>
);
}
return null;
})()}
</div>
<button
className={preferencesButtonClass}
onClick={this.props.openPreferences}
aria-label={this.props.t('Toolbar.OpenPreferencesARIA')}
title={this.props.t('Toolbar.OpenPreferencesARIA')}
>
<PreferencesIcon focusable="false" aria-hidden="true" />
</button>
validate={(text) => text.trim().length > 0}
onChange={changeSketchName}
/>
{(() => {
if (project.owner) {
return (
<p className="toolbar__project-project.owner">
{t('Toolbar.By')}{' '}
<Link to={`/${project.owner.username}/sketches`}>
{project.owner.username}
</Link>
</p>
);
}
return null;
})()}
</div>
);
}
}
<button
className={preferencesButtonClass}
onClick={() => dispatch(openPreferences())}
aria-label={t('Toolbar.OpenPreferencesARIA')}
title={t('Toolbar.OpenPreferencesARIA')}
>
<PreferencesIcon focusable="false" aria-hidden="true" />
</button>
</div>
);
};

Toolbar.propTypes = {
isPlaying: PropTypes.bool.isRequired,
preferencesIsVisible: PropTypes.bool.isRequired,
stopSketch: PropTypes.func.isRequired,
setProjectName: PropTypes.func.isRequired,
openPreferences: PropTypes.func.isRequired,
owner: PropTypes.shape({
username: PropTypes.string
}),
project: PropTypes.shape({
name: PropTypes.string.isRequired,
id: PropTypes.string
}).isRequired,
infiniteLoop: PropTypes.bool.isRequired,
autorefresh: PropTypes.bool.isRequired,
setAutorefresh: PropTypes.func.isRequired,
setTextOutput: PropTypes.func.isRequired,
setGridOutput: PropTypes.func.isRequired,
startSketch: PropTypes.func.isRequired,
startAccessibleSketch: PropTypes.func.isRequired,
saveProject: PropTypes.func.isRequired,
currentUser: PropTypes.string,
t: PropTypes.func.isRequired,
syncFileContent: PropTypes.func.isRequired
};

Toolbar.defaultProps = {
owner: undefined,
currentUser: undefined
};

function mapStateToProps(state) {
return {
autorefresh: state.preferences.autorefresh,
currentUser: state.user.username,
infiniteLoop: state.ide.infiniteLoop,
isPlaying: state.ide.isPlaying,
owner: state.project.owner,
preferencesIsVisible: state.ide.preferencesIsVisible,
project: state.project
};
}

const mapDispatchToProps = {
...IDEActions,
...preferenceActions,
...projectActions
};

export const ToolbarComponent = withTranslation()(Toolbar);
export default connect(mapStateToProps, mapDispatchToProps)(ToolbarComponent);
export default Toolbar;
Loading

0 comments on commit aa3b454

Please sign in to comment.