diff --git a/client/modules/IDE/components/SketchList.jsx b/client/modules/IDE/components/SketchList.jsx index bce30da1dc..0dc02bd2db 100644 --- a/client/modules/IDE/components/SketchList.jsx +++ b/client/modules/IDE/components/SketchList.jsx @@ -1,5 +1,5 @@ +import React, { useState, useRef } from 'react'; import PropTypes from 'prop-types'; -import React from 'react'; import { Helmet } from 'react-helmet'; import { withTranslation } from 'react-i18next'; import { connect } from 'react-redux'; @@ -30,121 +30,108 @@ const ROOT_URL = getConfig('API_URL'); const formatDateCell = (date, mobile = false) => dates.format(date, { showTime: !mobile }); -class SketchListRowBase extends React.Component { - constructor(props) { - super(props); - this.state = { - optionsOpen: false, - renameOpen: false, - renameValue: props.sketch.name, - isFocused: false - }; - this.renameInput = React.createRef(); - } - - onFocusComponent = () => { - this.setState({ isFocused: true }); +const SketchListRowBase = ({ + sketch, + username, + mobile, + user, + changeProjectName, + cloneProject, + showShareModal, + deleteProject, + onAddToCollection, + handleRowClick, + t +}) => { + const [optionsOpen, setOptionsOpen] = useState(false); + const [renameOpen, setRenameOpen] = useState(false); + const [renameValue, setRenameValue] = useState(sketch.name); + const [isFocused, setIsFocused] = useState(false); + const renameInput = useRef(null); + + const closeAll = () => { + setRenameOpen(false); + setOptionsOpen(false); }; - onBlurComponent = () => { - this.setState({ isFocused: false }); + const updateName = () => { + const isValid = renameValue.trim().length !== 0; + if (isValid) { + changeProjectName(sketch.id, renameValue.trim()); + } + }; + + const onFocusComponent = () => { + setIsFocused(true); + }; + + const onBlurComponent = () => { + setIsFocused(false); setTimeout(() => { - if (!this.state.isFocused) { - this.closeAll(); + if (!isFocused) { + closeAll(); } }, 200); }; - openOptions = () => { - this.setState({ - optionsOpen: true - }); + const openOptions = () => { + setOptionsOpen(true); }; - closeOptions = () => { - this.setState({ - optionsOpen: false - }); + const closeOptions = () => { + setOptionsOpen(false); }; - toggleOptions = () => { - if (this.state.optionsOpen) { - this.closeOptions(); + const toggleOptions = () => { + if (optionsOpen) { + closeOptions(); } else { - this.openOptions(); + openOptions(); } }; - openRename = () => { - this.setState( - { - renameOpen: true, - renameValue: this.props.sketch.name - }, - () => this.renameInput.current.focus() - ); + const openRename = () => { + setRenameOpen(true); + setRenameValue(sketch.name); + renameInput.current.focus(); }; - closeRename = () => { - this.setState({ - renameOpen: false - }); + const closeRename = () => { + setRenameOpen(false); }; - closeAll = () => { - this.setState({ - renameOpen: false, - optionsOpen: false - }); - }; - - handleRenameChange = (e) => { - this.setState({ - renameValue: e.target.value - }); + const handleRenameChange = (e) => { + setRenameValue(e.target.value); }; - handleRenameEnter = (e) => { + const handleRenameEnter = (e) => { if (e.key === 'Enter') { - this.updateName(); - this.closeAll(); + updateName(); + closeAll(); } }; - handleRenameBlur = () => { - this.updateName(); - this.closeAll(); + const handleRenameBlur = () => { + updateName(); + closeAll(); }; - updateName = () => { - const isValid = this.state.renameValue.trim().length !== 0; - if (isValid) { - this.props.changeProjectName( - this.props.sketch.id, - this.state.renameValue.trim() - ); - } + const resetSketchName = () => { + setRenameValue(sketch.name); + setRenameOpen(false); }; - resetSketchName = () => { - this.setState({ - renameValue: this.props.sketch.name, - renameOpen: false - }); + const handleDropdownOpen = () => { + closeAll(); + openOptions(); }; - handleDropdownOpen = () => { - this.closeAll(); - this.openOptions(); + const handleRenameOpen = () => { + closeAll(); + openRename(); }; - handleRenameOpen = () => { - this.closeAll(); - this.openRename(); - }; - - handleSketchDownload = () => { - const { sketch } = this.props; + const handleSketchDownload = () => { const downloadLink = document.createElement('a'); downloadLink.href = `${ROOT_URL}/projects/${sketch.id}/zip`; downloadLink.download = `${sketch.name}.zip`; @@ -153,53 +140,48 @@ class SketchListRowBase extends React.Component { document.body.removeChild(downloadLink); }; - handleSketchDuplicate = () => { - this.closeAll(); - this.props.cloneProject(this.props.sketch); + const handleSketchDuplicate = () => { + closeAll(); + cloneProject(sketch); }; - handleSketchShare = () => { - this.closeAll(); - this.props.showShareModal( - this.props.sketch.id, - this.props.sketch.name, - this.props.username - ); + const handleSketchShare = () => { + closeAll(); + showShareModal(sketch.id, sketch.name, username); }; - handleSketchDelete = () => { - this.closeAll(); + const handleSketchDelete = () => { + closeAll(); if ( window.confirm( - this.props.t('Common.DeleteConfirmation', { - name: this.props.sketch.name + t('Common.DeleteConfirmation', { + name: sketch.name }) ) ) { - this.props.deleteProject(this.props.sketch.id); + deleteProject(sketch.id); } }; - renderViewButton = (sketchURL) => ( + const renderViewButton = (sketchURL) => ( - {this.props.t('SketchList.View')} + {t('SketchList.View')} ); - renderDropdown = () => { - const { optionsOpen } = this.state; - const userIsOwner = this.props.user.username === this.props.username; + const renderDropdown = () => { + const userIsOwner = user.username === username; return ( )}
  • - {this.props.user.authenticated && ( + {user.authenticated && (
  • )} - {this.props.user.authenticated && ( + {user.authenticated && (
  • )} {/*
  • @@ -270,11 +252,11 @@ class SketchListRowBase extends React.Component {
  • )} @@ -284,46 +266,42 @@ class SketchListRowBase extends React.Component { ); }; - render() { - const { sketch, username, mobile } = this.props; - const { renameOpen, renameValue } = this.state; - let url = `/${username}/sketches/${sketch.id}`; - if (username === 'p5') { - url = `/${username}/sketches/${slugify(sketch.name, '_')}`; - } - - const name = ( - - {renameOpen ? '' : sketch.name} - {renameOpen && ( - e.stopPropagation()} - ref={this.renameInput} - /> - )} - - ); + const url = + username === 'p5' + ? `/${username}/sketches/${slugify(sketch.name, '_')}` + : `/${username}/sketches/${sketch.id}`; + + const name = ( + + {renameOpen ? '' : sketch.name} + {renameOpen && ( + e.stopPropagation()} + ref={renameInput} + /> + )} + + ); - return ( - - - {name} - {formatDateCell(sketch.createdAt, mobile)} - {formatDateCell(sketch.updatedAt, mobile)} - {this.renderDropdown()} - - - ); - } -} + return ( + + handleRowClick(sketch)} + > + {name} + {formatDateCell(sketch.createdAt, mobile)} + {formatDateCell(sketch.updatedAt, mobile)} + {renderDropdown()} + + + ); +}; SketchListRowBase.propTypes = { sketch: PropTypes.shape({ @@ -343,6 +321,7 @@ SketchListRowBase.propTypes = { changeProjectName: PropTypes.func.isRequired, onAddToCollection: PropTypes.func.isRequired, mobile: PropTypes.bool, + handleRowClick: PropTypes.func.isRequired, t: PropTypes.func.isRequired }; @@ -350,111 +329,94 @@ SketchListRowBase.defaultProps = { mobile: false }; -function mapDispatchToPropsSketchListRow(dispatch) { - return bindActionCreators( - Object.assign({}, ProjectActions, IdeActions), - dispatch - ); -} - -const SketchListRow = connect( - null, - mapDispatchToPropsSketchListRow -)(SketchListRowBase); - -class SketchList extends React.Component { - constructor(props) { - super(props); - this.props.getProjects(this.props.username); - this.props.resetSorting(); - - this.state = { - isInitialDataLoad: true - }; - } - - componentDidUpdate(prevProps) { - if ( - this.props.sketches !== prevProps.sketches && - Array.isArray(this.props.sketches) - ) { - // eslint-disable-next-line react/no-did-update-set-state - this.setState({ - isInitialDataLoad: false - }); +const SketchList = ({ + user, + getProjects, + sketches, + username, + loading, + toggleDirectionForField, + resetSorting, + sorting, + mobile, + t +}) => { + getProjects(username); + resetSorting(); + + const [isInitialDataLoad, setIsInitialDataLoad] = useState(true); + const [sketchToAddToCollection, setSketchToAddToCollection] = useState(null); + + React.useEffect(() => { + if (Array.isArray(sketches)) { + setIsInitialDataLoad(false); } - } + }, [sketches]); - getSketchesTitle() { - if (this.props.username === this.props.user.username) { - return this.props.t('SketchList.Title'); + const getSketchesTitle = () => { + if (username === user.username) { + return t('SketchList.Title'); } - return this.props.t('SketchList.AnothersTitle', { - anotheruser: this.props.username + return t('SketchList.AnothersTitle', { + anotheruser: username }); - } + }; - hasSketches() { - return !this.isLoading() && this.props.sketches.length > 0; - } + const isLoading = () => loading && isInitialDataLoad; - isLoading() { - return this.props.loading && this.state.isInitialDataLoad; - } + const hasSketches = () => !isLoading() && sketches.length > 0; - _renderLoader() { - if (this.isLoading()) return ; + const renderLoader = () => { + if (isLoading()) return ; return null; - } + }; - _renderEmptyTable() { - if (!this.isLoading() && this.props.sketches.length === 0) { + const renderEmptyTable = () => { + if (!isLoading() && sketches.length === 0) { return ( -

    - {this.props.t('SketchList.NoSketches')} -

    +

    {t('SketchList.NoSketches')}

    ); } return null; - } + }; - _getButtonLabel = (fieldName, displayName) => { - const { field, direction } = this.props.sorting; + const _getButtonLabel = (fieldName, displayName) => { + const { field, direction } = sorting; let buttonLabel; if (field !== fieldName) { if (field === 'name') { - buttonLabel = this.props.t('SketchList.ButtonLabelAscendingARIA', { + buttonLabel = t('SketchList.ButtonLabelAscendingARIA', { displayName }); } else { - buttonLabel = this.props.t('SketchList.ButtonLabelDescendingARIA', { + buttonLabel = t('SketchList.ButtonLabelDescendingARIA', { displayName }); } } else if (direction === SortingActions.DIRECTION.ASC) { - buttonLabel = this.props.t('SketchList.ButtonLabelDescendingARIA', { + buttonLabel = t('SketchList.ButtonLabelDescendingARIA', { displayName }); } else { - buttonLabel = this.props.t('SketchList.ButtonLabelAscendingARIA', { + buttonLabel = t('SketchList.ButtonLabelAscendingARIA', { displayName }); } return buttonLabel; }; - _renderFieldHeader = (fieldName, displayName) => { - const { field, direction } = this.props.sorting; + const _renderFieldHeader = (fieldName, displayName) => { + const { field, direction } = sorting; const headerClass = classNames({ 'sketches-table__header': true, 'sketches-table__header--selected': field === fieldName }); - const buttonLabel = this._getButtonLabel(fieldName, displayName); + const buttonLabel = _getButtonLabel(fieldName, displayName); return (