diff --git a/client/modules/IDE/components/Sidebar.jsx b/client/modules/IDE/components/Sidebar.jsx
index 1553c40361..3e92fd8436 100644
--- a/client/modules/IDE/components/Sidebar.jsx
+++ b/client/modules/IDE/components/Sidebar.jsx
@@ -1,178 +1,134 @@
-import PropTypes from 'prop-types';
-import React from 'react';
+import React, { useRef, useState } from 'react';
import classNames from 'classnames';
-import { withTranslation } from 'react-i18next';
+import { useTranslation } from 'react-i18next';
+import { useDispatch, useSelector } from 'react-redux';
+import {
+ closeProjectOptions,
+ newFile,
+ newFolder,
+ openProjectOptions,
+ openUploadFileModal
+} from '../actions/ide';
+import { getAuthenticated, selectCanEditSketch } from '../selectors/users';
import ConnectedFileNode from './FileNode';
import DownArrowIcon from '../../../images/down-filled-triangle.svg';
-class Sidebar extends React.Component {
- constructor(props) {
- super(props);
- this.resetSelectedFile = this.resetSelectedFile.bind(this);
- this.toggleProjectOptions = this.toggleProjectOptions.bind(this);
- this.onBlurComponent = this.onBlurComponent.bind(this);
- this.onFocusComponent = this.onFocusComponent.bind(this);
+// TODO: use a generic Dropdown UI component
- this.state = {
- isFocused: false
- };
- }
+export default function SideBar() {
+ const { t } = useTranslation();
+ const dispatch = useDispatch();
- onBlurComponent() {
- this.setState({ isFocused: false });
+ const [isFocused, setIsFocused] = useState(false);
+
+ const files = useSelector((state) => state.files);
+ // TODO: use `selectRootFile` defined in another PR
+ const rootFile = files.filter((file) => file.name === 'root')[0];
+ const projectOptionsVisible = useSelector(
+ (state) => state.ide.projectOptionsVisible
+ );
+ const isExpanded = useSelector((state) => state.ide.sidebarIsExpanded);
+ const canEditProject = useSelector(selectCanEditSketch);
+ const isAuthenticated = useSelector(getAuthenticated);
+
+ const sidebarOptionsRef = useRef(null);
+
+ const onBlurComponent = () => {
+ setIsFocused(false);
setTimeout(() => {
- if (!this.state.isFocused) {
- this.props.closeProjectOptions();
+ if (!isFocused) {
+ dispatch(closeProjectOptions());
}
}, 200);
- }
+ };
- onFocusComponent() {
- this.setState({ isFocused: true });
- }
+ const onFocusComponent = () => {
+ setIsFocused(true);
+ };
- resetSelectedFile() {
- this.props.setSelectedFile(this.props.files[1].id);
- }
-
- toggleProjectOptions(e) {
+ const toggleProjectOptions = (e) => {
e.preventDefault();
- if (this.props.projectOptionsVisible) {
- this.props.closeProjectOptions();
+ if (projectOptionsVisible) {
+ dispatch(closeProjectOptions());
} else {
- this.sidebarOptions.focus();
- this.props.openProjectOptions();
+ sidebarOptionsRef.current?.focus();
+ dispatch(openProjectOptions());
}
- }
+ };
- userCanEditProject() {
- let canEdit;
- if (!this.props.owner) {
- canEdit = true;
- } else if (
- this.props.user.authenticated &&
- this.props.owner.id === this.props.user.id
- ) {
- canEdit = true;
- } else {
- canEdit = false;
- }
- return canEdit;
- }
-
- render() {
- const canEditProject = this.userCanEditProject();
- const sidebarClass = classNames({
- sidebar: true,
- 'sidebar--contracted': !this.props.isExpanded,
- 'sidebar--project-options': this.props.projectOptionsVisible,
- 'sidebar--cant-edit': !canEditProject
- });
- const rootFile = this.props.files.filter((file) => file.name === 'root')[0];
+ const sidebarClass = classNames({
+ sidebar: true,
+ 'sidebar--contracted': !isExpanded,
+ 'sidebar--project-options': projectOptionsVisible,
+ 'sidebar--cant-edit': !canEditProject
+ });
- return (
-
-
-
- {this.props.t('Sidebar.Title')}
-
-
-
-
+ return (
+
+
+
+ {t('Sidebar.Title')}
+
+
+
+
+ -
+
+
+ -
+
+
+ {isAuthenticated && (
-
- -
-
-
- {this.props.user.authenticated && (
- -
-
-
- )}
-
-
-
-
-
- );
- }
+ )}
+
+
+
+
+
+ );
}
-
-Sidebar.propTypes = {
- files: PropTypes.arrayOf(
- PropTypes.shape({
- name: PropTypes.string.isRequired,
- id: PropTypes.string.isRequired
- })
- ).isRequired,
- setSelectedFile: PropTypes.func.isRequired,
- isExpanded: PropTypes.bool.isRequired,
- projectOptionsVisible: PropTypes.bool.isRequired,
- newFile: PropTypes.func.isRequired,
- openProjectOptions: PropTypes.func.isRequired,
- closeProjectOptions: PropTypes.func.isRequired,
- newFolder: PropTypes.func.isRequired,
- openUploadFileModal: PropTypes.func.isRequired,
- owner: PropTypes.shape({
- id: PropTypes.string
- }),
- user: PropTypes.shape({
- id: PropTypes.string,
- authenticated: PropTypes.bool.isRequired
- }).isRequired,
- t: PropTypes.func.isRequired
-};
-
-Sidebar.defaultProps = {
- owner: undefined
-};
-
-export default withTranslation()(Sidebar);
diff --git a/client/modules/IDE/pages/IDEView.jsx b/client/modules/IDE/pages/IDEView.jsx
index 5158e33c44..685dc3fd5b 100644
--- a/client/modules/IDE/pages/IDEView.jsx
+++ b/client/modules/IDE/pages/IDEView.jsx
@@ -304,22 +304,7 @@ class IDEView extends React.Component {
allowResize={this.props.ide.sidebarIsExpanded}
minSize={125}
>
-
+
file.isSelectedFile) ||
state.files.find((file) => file.name === 'sketch.js') ||
diff --git a/client/modules/IDE/selectors/users.js b/client/modules/IDE/selectors/users.js
index 4086348041..4bda34ef11 100644
--- a/client/modules/IDE/selectors/users.js
+++ b/client/modules/IDE/selectors/users.js
@@ -1,7 +1,7 @@
import { createSelector } from 'reselect';
import getConfig from '../../../utils/getConfig';
-const getAuthenticated = (state) => state.user.authenticated;
+export const getAuthenticated = (state) => state.user.authenticated;
const getTotalSize = (state) => state.user.totalSize;
const getAssetsTotalSize = (state) => state.assets.totalSize;
const getSketchOwner = (state) => state.project.owner;
@@ -39,3 +39,9 @@ export const getIsUserOwner = createSelector(
return sketchOwner.id === userId;
}
);
+
+export const selectCanEditSketch = createSelector(
+ getSketchOwner,
+ getIsUserOwner,
+ (sketchOwner, isOwner) => !sketchOwner || isOwner
+);