From 0ec02d6e4005269925c399d913d13facffdf11cd Mon Sep 17 00:00:00 2001 From: Linda Paiste Date: Sun, 4 Jun 2023 19:17:00 -0500 Subject: [PATCH] convert Sidebar to a function component & connect it to Redux --- client/modules/IDE/components/Sidebar.jsx | 260 +++++++++------------- client/modules/IDE/pages/IDEView.jsx | 33 +-- client/modules/IDE/selectors/users.js | 8 +- 3 files changed, 116 insertions(+), 185 deletions(-) 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 3c323fe88c..4be04093d3 100644 --- a/client/modules/IDE/pages/IDEView.jsx +++ b/client/modules/IDE/pages/IDEView.jsx @@ -302,22 +302,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 +);