From 086df2f6fcd4b116a03e3b739a137ba4b1eb6834 Mon Sep 17 00:00:00 2001 From: Linda Paiste Date: Mon, 7 Aug 2023 19:17:51 -0500 Subject: [PATCH] Convert `Collection` to a function component and connect to Redux. --- client/modules/User/components/Collection.jsx | 333 ++++++------------ .../User/components/CollectionMetadata.jsx | 31 +- 2 files changed, 125 insertions(+), 239 deletions(-) diff --git a/client/modules/User/components/Collection.jsx b/client/modules/User/components/Collection.jsx index ff84617ea8..b2135efd66 100644 --- a/client/modules/User/components/Collection.jsx +++ b/client/modules/User/components/Collection.jsx @@ -1,18 +1,19 @@ import PropTypes from 'prop-types'; -import React from 'react'; +import React, { useEffect } from 'react'; import { Helmet } from 'react-helmet'; -import { connect } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import { Link } from 'react-router-dom'; -import { bindActionCreators } from 'redux'; -import { useTranslation, withTranslation } from 'react-i18next'; +import { useTranslation } from 'react-i18next'; import classNames from 'classnames'; - -import * as ProjectActions from '../../IDE/actions/project'; -import * as ProjectsActions from '../../IDE/actions/projects'; -import * as CollectionsActions from '../../IDE/actions/collections'; -import * as ToastActions from '../../IDE/actions/toast'; +import { + getCollections, + removeFromCollection +} from '../../IDE/actions/collections'; +import { + resetSorting, + toggleDirectionForField +} from '../../IDE/actions/sorting'; import * as SortingActions from '../../IDE/actions/sorting'; -import * as IdeActions from '../../IDE/actions/ide'; import { getCollection } from '../../IDE/selectors/collections'; import Loader from '../../App/components/loader'; import dates from '../../../utils/formatDate'; @@ -22,14 +23,11 @@ import ArrowDownIcon from '../../../images/sort-arrow-down.svg'; import RemoveIcon from '../../../images/close.svg'; import CollectionMetadata from './CollectionMetadata'; -const CollectionItemRowBase = ({ - collection, - item, - isOwner, - removeFromCollection -}) => { +const CollectionItemRow = ({ collection, item, isOwner }) => { const { t } = useTranslation(); + const dispatch = useDispatch(); + const projectIsDeleted = item.isDeleted; const handleSketchRemove = () => { @@ -40,7 +38,7 @@ const CollectionItemRowBase = ({ t('Collection.DeleteFromCollection', { name_sketch: name }) ) ) { - removeFromCollection(collection.id, item.projectId); + dispatch(removeFromCollection(collection.id, item.projectId)); } }; @@ -78,7 +76,7 @@ const CollectionItemRowBase = ({ ); }; -CollectionItemRowBase.propTypes = { +CollectionItemRow.propTypes = { collection: PropTypes.shape({ id: PropTypes.string.isRequired, name: PropTypes.string.isRequired @@ -95,136 +93,80 @@ CollectionItemRowBase.propTypes = { }) }).isRequired }).isRequired, - isOwner: PropTypes.bool.isRequired, - user: PropTypes.shape({ - username: PropTypes.string, - authenticated: PropTypes.bool.isRequired - }).isRequired, - removeFromCollection: PropTypes.func.isRequired + isOwner: PropTypes.bool.isRequired }; -function mapDispatchToPropsSketchListRow(dispatch) { - return bindActionCreators( - Object.assign({}, CollectionsActions, ProjectActions, IdeActions), - dispatch - ); -} +const Collection = ({ username, collectionId }) => { + const { t } = useTranslation(); + const dispatch = useDispatch(); -const CollectionItemRow = connect( - null, - mapDispatchToPropsSketchListRow -)(CollectionItemRowBase); + const collection = useSelector((state) => getCollection(state, collectionId)); -class Collection extends React.Component { - constructor(props) { - super(props); - this.props.getCollections(this.props.username); - this.props.resetSorting(); - this._renderFieldHeader = this._renderFieldHeader.bind(this); - } + const hasCollectionItems = collection?.items.length > 0; + const hasZeroItems = collection?.items.length === 0; - getTitle() { - if (this.props.username === this.props.user.username) { - return this.props.t('Collection.Title'); - } - return this.props.t('Collection.AnothersTitle', { - anotheruser: this.props.username - }); - } + const loading = useSelector((state) => state.loading); + const showLoader = loading && !collection; - getUsername() { - return this.props.username !== undefined - ? this.props.username - : this.props.user.username; - } + const currentUsername = useSelector((state) => state.user.username); + const isOwner = username === currentUsername; - isOwner() { - let isOwner = false; + useEffect(() => { + dispatch(resetSorting()); + }, []); - if ( - this.props.user != null && - this.props.user.username && - this.props.collection.owner.username === this.props.user.username - ) { - isOwner = true; + useEffect(() => { + if (!collection) { + dispatch(getCollections(username)); } + }, [username, collection]); - return isOwner; - } - - hasCollection() { - return !this.props.loading && this.props.collection != null; - } - - hasCollectionItems() { - return this.hasCollection() && this.props.collection.items.length > 0; - } + const { field, direction } = useSelector((state) => state.sorting); - _renderLoader() { - if (this.props.loading) return ; - return null; - } + // TODO: clean this up into a generic table/list/sort component - _renderEmptyTable() { - const isLoading = this.props.loading; - const hasCollectionItems = - this.props.collection != null && this.props.collection.items.length > 0; - - if (!isLoading && !hasCollectionItems) { - return ( -

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

- ); - } - return null; - } - - _getButtonLabel = (fieldName, displayName) => { - const { field, direction } = this.props.sorting; + const _getButtonLabel = (fieldName, displayName) => { let buttonLabel; if (field !== fieldName) { if (field === 'name') { - buttonLabel = this.props.t('Collection.ButtonLabelAscendingARIA', { + buttonLabel = t('Collection.ButtonLabelAscendingARIA', { displayName }); } else { - buttonLabel = this.props.t('Collection.ButtonLabelDescendingARIA', { + buttonLabel = t('Collection.ButtonLabelDescendingARIA', { displayName }); } } else if (direction === SortingActions.DIRECTION.ASC) { - buttonLabel = this.props.t('Collection.ButtonLabelDescendingARIA', { + buttonLabel = t('Collection.ButtonLabelDescendingARIA', { displayName }); } else { - buttonLabel = this.props.t('Collection.ButtonLabelAscendingARIA', { + buttonLabel = t('Collection.ButtonLabelAscendingARIA', { displayName }); } return buttonLabel; }; - _renderFieldHeader(fieldName, displayName) { - const { field, direction } = this.props.sorting; + const _renderFieldHeader = (fieldName, displayName) => { const headerClass = classNames({ arrowDown: true, 'sketches-table__header--selected': field === fieldName }); - const buttonLabel = this._getButtonLabel(fieldName, displayName); return ( ); - } - - render() { - const isOwner = this.isOwner(); + }; - return ( -
-
- - {this.getTitle()} - - {this._renderLoader()} - -
-
- {this._renderEmptyTable()} - {this.hasCollectionItems() && ( - - - - {this._renderFieldHeader( - 'name', - this.props.t('Collection.HeaderName') - )} - {this._renderFieldHeader( - 'createdAt', - this.props.t('Collection.HeaderCreatedAt') - )} - {this._renderFieldHeader( - 'user', - this.props.t('Collection.HeaderUser') - )} - - - - - {this.props.collection.items.map((item) => ( - - ))} - -
- )} -
-
+ return ( +
+
+ + + {isOwner + ? t('Collection.Title') + : t('Collection.AnothersTitle', { + anotheruser: username + })} + + + {showLoader && } + {collection && ( + + )} +
+
+ {hasZeroItems && ( +

+ {t('Collection.NoSketches')} +

+ )} + {hasCollectionItems && ( + + + + {_renderFieldHeader('name', t('Collection.HeaderName'))} + {_renderFieldHeader( + 'createdAt', + t('Collection.HeaderCreatedAt') + )} + {_renderFieldHeader('user', t('Collection.HeaderUser'))} + + + + + {collection.items.map((item) => ( + + ))} + +
+ )} +
-
- ); - } -} +
+
+ ); +}; Collection.propTypes = { collectionId: PropTypes.string.isRequired, - user: PropTypes.shape({ - username: PropTypes.string, - authenticated: PropTypes.bool.isRequired - }).isRequired, - getCollections: PropTypes.func.isRequired, - collection: PropTypes.shape({ - id: PropTypes.string, - name: PropTypes.string, - slug: PropTypes.string, - description: PropTypes.string, - owner: PropTypes.shape({ - username: PropTypes.string - }).isRequired, - items: PropTypes.arrayOf(PropTypes.shape({})) - }), - username: PropTypes.string, - loading: PropTypes.bool.isRequired, - toggleDirectionForField: PropTypes.func.isRequired, - resetSorting: PropTypes.func.isRequired, - sorting: PropTypes.shape({ - field: PropTypes.string.isRequired, - direction: PropTypes.string.isRequired - }).isRequired, - t: PropTypes.func.isRequired -}; - -Collection.defaultProps = { - username: undefined, - collection: { - id: undefined, - items: [], - owner: { - username: undefined - } - } + username: PropTypes.string.isRequired }; -function mapStateToProps(state, ownProps) { - return { - user: state.user, - collection: getCollection(state, ownProps.collectionId), - sorting: state.sorting, - loading: state.loading, - project: state.project - }; -} - -function mapDispatchToProps(dispatch) { - return bindActionCreators( - Object.assign( - {}, - CollectionsActions, - ProjectsActions, - ToastActions, - SortingActions - ), - dispatch - ); -} - -export default withTranslation()( - connect(mapStateToProps, mapDispatchToProps)(Collection) -); +export default Collection; diff --git a/client/modules/User/components/CollectionMetadata.jsx b/client/modules/User/components/CollectionMetadata.jsx index 108a40850d..9d6df781d2 100644 --- a/client/modules/User/components/CollectionMetadata.jsx +++ b/client/modules/User/components/CollectionMetadata.jsx @@ -3,33 +3,26 @@ import PropTypes from 'prop-types'; import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useDispatch, useSelector } from 'react-redux'; -import { Link } from 'react-router'; +import { Link } from 'react-router-dom'; import Button from '../../../common/Button'; import Overlay from '../../App/components/Overlay'; import { editCollection } from '../../IDE/actions/collections'; import AddToCollectionSketchList from '../../IDE/components/AddToCollectionSketchList'; import EditableInput from '../../IDE/components/EditableInput'; import { SketchSearchbar } from '../../IDE/components/Searchbar'; -import { getCollection } from '../../IDE/selectors/collections'; import ShareURL from './CollectionShareButton'; -function CollectionMetadata({ collectionId }) { +function CollectionMetadata({ collection, isOwner }) { const { t } = useTranslation(); const dispatch = useDispatch(); - const collection = useSelector((state) => getCollection(state, collectionId)); const currentUsername = useSelector((state) => state.user.username); const [isAddingSketches, setIsAddingSketches] = useState(false); - if (!collection) { - return null; - } - const { id, name, description, items, owner } = collection; - const { username } = owner; - const isOwner = !!currentUsername && currentUsername === username; + const { username: ownerUsername } = owner; const hostname = window.location.origin; @@ -85,7 +78,7 @@ function CollectionMetadata({ collectionId }) {

{t('Collection.By')} - {username} + {ownerUsername}

@@ -94,7 +87,7 @@ function CollectionMetadata({ collectionId }) {

- + {isOwner && (