From ac9d4d18b032b3c86cd4b4c576504fe76b6b0341 Mon Sep 17 00:00:00 2001 From: Linda Paiste Date: Sat, 17 Jun 2023 15:35:30 -0500 Subject: [PATCH 1/3] Duplicate files. --- .../User/components/CollectionMetadata.jsx | 553 ++++++++++++++++++ .../User/components/CollectionShareButton.jsx | 553 ++++++++++++++++++ 2 files changed, 1106 insertions(+) create mode 100644 client/modules/User/components/CollectionMetadata.jsx create mode 100644 client/modules/User/components/CollectionShareButton.jsx diff --git a/client/modules/User/components/CollectionMetadata.jsx b/client/modules/User/components/CollectionMetadata.jsx new file mode 100644 index 0000000000..563fd7d6ff --- /dev/null +++ b/client/modules/User/components/CollectionMetadata.jsx @@ -0,0 +1,553 @@ +import PropTypes from 'prop-types'; +import React, { useState, useRef, useEffect } from 'react'; +import { Helmet } from 'react-helmet'; +import { connect } from 'react-redux'; +import { Link } from 'react-router'; +import { bindActionCreators } from 'redux'; +import { useTranslation, withTranslation } from 'react-i18next'; +import classNames from 'classnames'; + +import Button from '../../../common/Button'; +import { DropdownArrowIcon } from '../../../common/icons'; +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 * 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 EditableInput from '../../IDE/components/EditableInput'; +import Overlay from '../../App/components/Overlay'; +import AddToCollectionSketchList from '../../IDE/components/AddToCollectionSketchList'; +import CopyableInput from '../../IDE/components/CopyableInput'; +import { SketchSearchbar } from '../../IDE/components/Searchbar'; +import dates from '../../../utils/formatDate'; + +import ArrowUpIcon from '../../../images/sort-arrow-up.svg'; +import ArrowDownIcon from '../../../images/sort-arrow-down.svg'; +import RemoveIcon from '../../../images/close.svg'; + +const ShareURL = ({ value }) => { + const [showURL, setShowURL] = useState(false); + const node = useRef(); + const { t } = useTranslation(); + + const handleClickOutside = (e) => { + if (node.current.contains(e.target)) { + return; + } + setShowURL(false); + }; + + useEffect(() => { + if (showURL) { + document.addEventListener('mousedown', handleClickOutside); + } else { + document.removeEventListener('mousedown', handleClickOutside); + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [showURL]); + + return ( +
+ + {showURL && ( +
+ +
+ )} +
+ ); +}; + +ShareURL.propTypes = { + value: PropTypes.string.isRequired +}; + +const CollectionItemRowBase = ({ + collection, + item, + isOwner, + removeFromCollection +}) => { + const { t } = useTranslation(); + + const projectIsDeleted = item.isDeleted; + + const handleSketchRemove = () => { + const name = projectIsDeleted ? 'deleted sketch' : item.project.name; + + if ( + window.confirm( + t('Collection.DeleteFromCollection', { name_sketch: name }) + ) + ) { + removeFromCollection(collection.id, item.projectId); + } + }; + + const name = projectIsDeleted ? ( + {t('Collection.SketchDeleted')} + ) : ( + + {item.project.name} + + ); + + const sketchOwnerUsername = projectIsDeleted + ? null + : item.project.user.username; + + return ( + + {name} + {dates.format(item.createdAt)} + {sketchOwnerUsername} + + {isOwner && ( + + )} + + + ); +}; + +CollectionItemRowBase.propTypes = { + collection: PropTypes.shape({ + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired + }).isRequired, + item: PropTypes.shape({ + createdAt: PropTypes.string.isRequired, + projectId: PropTypes.string.isRequired, + isDeleted: PropTypes.bool.isRequired, + project: PropTypes.shape({ + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + user: PropTypes.shape({ + username: PropTypes.string.isRequired + }) + }).isRequired + }).isRequired, + isOwner: PropTypes.bool.isRequired, + user: PropTypes.shape({ + username: PropTypes.string, + authenticated: PropTypes.bool.isRequired + }).isRequired, + removeFromCollection: PropTypes.func.isRequired +}; + +function mapDispatchToPropsSketchListRow(dispatch) { + return bindActionCreators( + Object.assign({}, CollectionsActions, ProjectActions, IdeActions), + dispatch + ); +} + +const CollectionItemRow = connect( + null, + mapDispatchToPropsSketchListRow +)(CollectionItemRowBase); + +class Collection extends React.Component { + constructor(props) { + super(props); + this.props.getCollections(this.props.username); + this.props.resetSorting(); + this._renderFieldHeader = this._renderFieldHeader.bind(this); + this.showAddSketches = this.showAddSketches.bind(this); + this.hideAddSketches = this.hideAddSketches.bind(this); + + this.state = { + isAddingSketches: false + }; + } + + 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 + }); + } + + getUsername() { + return this.props.username !== undefined + ? this.props.username + : this.props.user.username; + } + + getCollectionName() { + return this.props.collection.name; + } + + isOwner() { + let isOwner = false; + + if ( + this.props.user != null && + this.props.user.username && + this.props.collection.owner.username === this.props.user.username + ) { + isOwner = true; + } + + return isOwner; + } + + hasCollection() { + return !this.props.loading && this.props.collection != null; + } + + hasCollectionItems() { + return this.hasCollection() && this.props.collection.items.length > 0; + } + + _renderLoader() { + if (this.props.loading) return ; + return null; + } + + _renderCollectionMetadata() { + const { id, name, description, items, owner } = this.props.collection; + + const hostname = window.location.origin; + const { username } = this.props; + + const baseURL = `${hostname}/${username}/collections/`; + + const handleEditCollectionName = (value) => { + if (value === name) { + return; + } + + this.props.editCollection(id, { name: value }); + }; + + const handleEditCollectionDescription = (value) => { + if (value === description) { + return; + } + + this.props.editCollection(id, { description: value }); + }; + + // + // TODO: Implement UI for editing slug + // + // const handleEditCollectionSlug = (value) => { + // if (value === slug) { + // return; + // } + // + // this.props.editCollection(id, { slug: value }); + // }; + + return ( +
+
+
+

+ {this.isOwner() ? ( + value !== ''} + /> + ) : ( + name + )} +

+ +

+ {this.isOwner() ? ( + + ) : ( + description + )} +

+ +

+ {this.props.t('Collection.By')} + + {owner.username} + +

+ +

+ {this.props.t('Collection.NumSketches', { count: items.length })} +

+
+ +
+

+ +

+ {this.isOwner() && ( + + )} +
+
+
+ ); + } + + showAddSketches() { + this.setState({ + isAddingSketches: true + }); + } + + hideAddSketches() { + this.setState({ + isAddingSketches: false + }); + } + + _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; + let buttonLabel; + if (field !== fieldName) { + if (field === 'name') { + buttonLabel = this.props.t('Collection.ButtonLabelAscendingARIA', { + displayName + }); + } else { + buttonLabel = this.props.t('Collection.ButtonLabelDescendingARIA', { + displayName + }); + } + } else if (direction === SortingActions.DIRECTION.ASC) { + buttonLabel = this.props.t('Collection.ButtonLabelDescendingARIA', { + displayName + }); + } else { + buttonLabel = this.props.t('Collection.ButtonLabelAscendingARIA', { + displayName + }); + } + return buttonLabel; + }; + + _renderFieldHeader(fieldName, displayName) { + const { field, direction } = this.props.sorting; + const headerClass = classNames({ + arrowDown: true, + 'sketches-table__header--selected': field === fieldName + }); + const buttonLabel = this._getButtonLabel(fieldName, displayName); + return ( + + + + ); + } + + render() { + const title = this.hasCollection() ? this.getCollectionName() : null; + const isOwner = this.isOwner(); + + return ( +
+
+ + {this.getTitle()} + + {this._renderLoader()} + {this.hasCollection() && this._renderCollectionMetadata()} +
+
+ {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) => ( + + ))} + +
+ )} + {this.state.isAddingSketches && ( + } + closeOverlay={this.hideAddSketches} + isFixedHeight + > + + + )} +
+
+
+
+ ); + } +} + +Collection.propTypes = { + 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, + editCollection: 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 + } + } +}; + +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) +); diff --git a/client/modules/User/components/CollectionShareButton.jsx b/client/modules/User/components/CollectionShareButton.jsx new file mode 100644 index 0000000000..563fd7d6ff --- /dev/null +++ b/client/modules/User/components/CollectionShareButton.jsx @@ -0,0 +1,553 @@ +import PropTypes from 'prop-types'; +import React, { useState, useRef, useEffect } from 'react'; +import { Helmet } from 'react-helmet'; +import { connect } from 'react-redux'; +import { Link } from 'react-router'; +import { bindActionCreators } from 'redux'; +import { useTranslation, withTranslation } from 'react-i18next'; +import classNames from 'classnames'; + +import Button from '../../../common/Button'; +import { DropdownArrowIcon } from '../../../common/icons'; +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 * 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 EditableInput from '../../IDE/components/EditableInput'; +import Overlay from '../../App/components/Overlay'; +import AddToCollectionSketchList from '../../IDE/components/AddToCollectionSketchList'; +import CopyableInput from '../../IDE/components/CopyableInput'; +import { SketchSearchbar } from '../../IDE/components/Searchbar'; +import dates from '../../../utils/formatDate'; + +import ArrowUpIcon from '../../../images/sort-arrow-up.svg'; +import ArrowDownIcon from '../../../images/sort-arrow-down.svg'; +import RemoveIcon from '../../../images/close.svg'; + +const ShareURL = ({ value }) => { + const [showURL, setShowURL] = useState(false); + const node = useRef(); + const { t } = useTranslation(); + + const handleClickOutside = (e) => { + if (node.current.contains(e.target)) { + return; + } + setShowURL(false); + }; + + useEffect(() => { + if (showURL) { + document.addEventListener('mousedown', handleClickOutside); + } else { + document.removeEventListener('mousedown', handleClickOutside); + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [showURL]); + + return ( +
+ + {showURL && ( +
+ +
+ )} +
+ ); +}; + +ShareURL.propTypes = { + value: PropTypes.string.isRequired +}; + +const CollectionItemRowBase = ({ + collection, + item, + isOwner, + removeFromCollection +}) => { + const { t } = useTranslation(); + + const projectIsDeleted = item.isDeleted; + + const handleSketchRemove = () => { + const name = projectIsDeleted ? 'deleted sketch' : item.project.name; + + if ( + window.confirm( + t('Collection.DeleteFromCollection', { name_sketch: name }) + ) + ) { + removeFromCollection(collection.id, item.projectId); + } + }; + + const name = projectIsDeleted ? ( + {t('Collection.SketchDeleted')} + ) : ( + + {item.project.name} + + ); + + const sketchOwnerUsername = projectIsDeleted + ? null + : item.project.user.username; + + return ( + + {name} + {dates.format(item.createdAt)} + {sketchOwnerUsername} + + {isOwner && ( + + )} + + + ); +}; + +CollectionItemRowBase.propTypes = { + collection: PropTypes.shape({ + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired + }).isRequired, + item: PropTypes.shape({ + createdAt: PropTypes.string.isRequired, + projectId: PropTypes.string.isRequired, + isDeleted: PropTypes.bool.isRequired, + project: PropTypes.shape({ + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + user: PropTypes.shape({ + username: PropTypes.string.isRequired + }) + }).isRequired + }).isRequired, + isOwner: PropTypes.bool.isRequired, + user: PropTypes.shape({ + username: PropTypes.string, + authenticated: PropTypes.bool.isRequired + }).isRequired, + removeFromCollection: PropTypes.func.isRequired +}; + +function mapDispatchToPropsSketchListRow(dispatch) { + return bindActionCreators( + Object.assign({}, CollectionsActions, ProjectActions, IdeActions), + dispatch + ); +} + +const CollectionItemRow = connect( + null, + mapDispatchToPropsSketchListRow +)(CollectionItemRowBase); + +class Collection extends React.Component { + constructor(props) { + super(props); + this.props.getCollections(this.props.username); + this.props.resetSorting(); + this._renderFieldHeader = this._renderFieldHeader.bind(this); + this.showAddSketches = this.showAddSketches.bind(this); + this.hideAddSketches = this.hideAddSketches.bind(this); + + this.state = { + isAddingSketches: false + }; + } + + 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 + }); + } + + getUsername() { + return this.props.username !== undefined + ? this.props.username + : this.props.user.username; + } + + getCollectionName() { + return this.props.collection.name; + } + + isOwner() { + let isOwner = false; + + if ( + this.props.user != null && + this.props.user.username && + this.props.collection.owner.username === this.props.user.username + ) { + isOwner = true; + } + + return isOwner; + } + + hasCollection() { + return !this.props.loading && this.props.collection != null; + } + + hasCollectionItems() { + return this.hasCollection() && this.props.collection.items.length > 0; + } + + _renderLoader() { + if (this.props.loading) return ; + return null; + } + + _renderCollectionMetadata() { + const { id, name, description, items, owner } = this.props.collection; + + const hostname = window.location.origin; + const { username } = this.props; + + const baseURL = `${hostname}/${username}/collections/`; + + const handleEditCollectionName = (value) => { + if (value === name) { + return; + } + + this.props.editCollection(id, { name: value }); + }; + + const handleEditCollectionDescription = (value) => { + if (value === description) { + return; + } + + this.props.editCollection(id, { description: value }); + }; + + // + // TODO: Implement UI for editing slug + // + // const handleEditCollectionSlug = (value) => { + // if (value === slug) { + // return; + // } + // + // this.props.editCollection(id, { slug: value }); + // }; + + return ( +
+
+
+

+ {this.isOwner() ? ( + value !== ''} + /> + ) : ( + name + )} +

+ +

+ {this.isOwner() ? ( + + ) : ( + description + )} +

+ +

+ {this.props.t('Collection.By')} + + {owner.username} + +

+ +

+ {this.props.t('Collection.NumSketches', { count: items.length })} +

+
+ +
+

+ +

+ {this.isOwner() && ( + + )} +
+
+
+ ); + } + + showAddSketches() { + this.setState({ + isAddingSketches: true + }); + } + + hideAddSketches() { + this.setState({ + isAddingSketches: false + }); + } + + _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; + let buttonLabel; + if (field !== fieldName) { + if (field === 'name') { + buttonLabel = this.props.t('Collection.ButtonLabelAscendingARIA', { + displayName + }); + } else { + buttonLabel = this.props.t('Collection.ButtonLabelDescendingARIA', { + displayName + }); + } + } else if (direction === SortingActions.DIRECTION.ASC) { + buttonLabel = this.props.t('Collection.ButtonLabelDescendingARIA', { + displayName + }); + } else { + buttonLabel = this.props.t('Collection.ButtonLabelAscendingARIA', { + displayName + }); + } + return buttonLabel; + }; + + _renderFieldHeader(fieldName, displayName) { + const { field, direction } = this.props.sorting; + const headerClass = classNames({ + arrowDown: true, + 'sketches-table__header--selected': field === fieldName + }); + const buttonLabel = this._getButtonLabel(fieldName, displayName); + return ( + + + + ); + } + + render() { + const title = this.hasCollection() ? this.getCollectionName() : null; + const isOwner = this.isOwner(); + + return ( +
+
+ + {this.getTitle()} + + {this._renderLoader()} + {this.hasCollection() && this._renderCollectionMetadata()} +
+
+ {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) => ( + + ))} + +
+ )} + {this.state.isAddingSketches && ( + } + closeOverlay={this.hideAddSketches} + isFixedHeight + > + + + )} +
+
+
+
+ ); + } +} + +Collection.propTypes = { + 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, + editCollection: 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 + } + } +}; + +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) +); From ccdae49558bdec6e31fdf3412ba3d84eaa05a00f Mon Sep 17 00:00:00 2001 From: Linda Paiste Date: Sat, 17 Jun 2023 18:16:20 -0500 Subject: [PATCH 2/3] Extract `CollectionMetadata` into its own component and file. --- client/modules/User/components/Collection.jsx | 192 +----- .../User/components/CollectionMetadata.jsx | 615 +++--------------- .../User/components/CollectionShareButton.jsx | 507 +-------------- 3 files changed, 102 insertions(+), 1212 deletions(-) diff --git a/client/modules/User/components/Collection.jsx b/client/modules/User/components/Collection.jsx index 563fd7d6ff..0db510cfec 100644 --- a/client/modules/User/components/Collection.jsx +++ b/client/modules/User/components/Collection.jsx @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import React, { useState, useRef, useEffect } from 'react'; +import React from 'react'; import { Helmet } from 'react-helmet'; import { connect } from 'react-redux'; import { Link } from 'react-router'; @@ -7,8 +7,6 @@ import { bindActionCreators } from 'redux'; import { useTranslation, withTranslation } from 'react-i18next'; import classNames from 'classnames'; -import Button from '../../../common/Button'; -import { DropdownArrowIcon } from '../../../common/icons'; import * as ProjectActions from '../../IDE/actions/project'; import * as ProjectsActions from '../../IDE/actions/projects'; import * as CollectionsActions from '../../IDE/actions/collections'; @@ -17,61 +15,12 @@ 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 EditableInput from '../../IDE/components/EditableInput'; -import Overlay from '../../App/components/Overlay'; -import AddToCollectionSketchList from '../../IDE/components/AddToCollectionSketchList'; -import CopyableInput from '../../IDE/components/CopyableInput'; -import { SketchSearchbar } from '../../IDE/components/Searchbar'; import dates from '../../../utils/formatDate'; import ArrowUpIcon from '../../../images/sort-arrow-up.svg'; import ArrowDownIcon from '../../../images/sort-arrow-down.svg'; import RemoveIcon from '../../../images/close.svg'; - -const ShareURL = ({ value }) => { - const [showURL, setShowURL] = useState(false); - const node = useRef(); - const { t } = useTranslation(); - - const handleClickOutside = (e) => { - if (node.current.contains(e.target)) { - return; - } - setShowURL(false); - }; - - useEffect(() => { - if (showURL) { - document.addEventListener('mousedown', handleClickOutside); - } else { - document.removeEventListener('mousedown', handleClickOutside); - } - - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [showURL]); - - return ( -
- - {showURL && ( -
- -
- )} -
- ); -}; - -ShareURL.propTypes = { - value: PropTypes.string.isRequired -}; +import CollectionMetadata from './CollectionMetadata'; const CollectionItemRowBase = ({ collection, @@ -172,12 +121,6 @@ class Collection extends React.Component { this.props.getCollections(this.props.username); this.props.resetSorting(); this._renderFieldHeader = this._renderFieldHeader.bind(this); - this.showAddSketches = this.showAddSketches.bind(this); - this.hideAddSketches = this.hideAddSketches.bind(this); - - this.state = { - isAddingSketches: false - }; } getTitle() { @@ -195,10 +138,6 @@ class Collection extends React.Component { : this.props.user.username; } - getCollectionName() { - return this.props.collection.name; - } - isOwner() { let isOwner = false; @@ -226,115 +165,6 @@ class Collection extends React.Component { return null; } - _renderCollectionMetadata() { - const { id, name, description, items, owner } = this.props.collection; - - const hostname = window.location.origin; - const { username } = this.props; - - const baseURL = `${hostname}/${username}/collections/`; - - const handleEditCollectionName = (value) => { - if (value === name) { - return; - } - - this.props.editCollection(id, { name: value }); - }; - - const handleEditCollectionDescription = (value) => { - if (value === description) { - return; - } - - this.props.editCollection(id, { description: value }); - }; - - // - // TODO: Implement UI for editing slug - // - // const handleEditCollectionSlug = (value) => { - // if (value === slug) { - // return; - // } - // - // this.props.editCollection(id, { slug: value }); - // }; - - return ( -
-
-
-

- {this.isOwner() ? ( - value !== ''} - /> - ) : ( - name - )} -

- -

- {this.isOwner() ? ( - - ) : ( - description - )} -

- -

- {this.props.t('Collection.By')} - - {owner.username} - -

- -

- {this.props.t('Collection.NumSketches', { count: items.length })} -

-
- -
-

- -

- {this.isOwner() && ( - - )} -
-
-
- ); - } - - showAddSketches() { - this.setState({ - isAddingSketches: true - }); - } - - hideAddSketches() { - this.setState({ - isAddingSketches: false - }); - } - _renderEmptyTable() { const isLoading = this.props.loading; const hasCollectionItems = @@ -412,7 +242,6 @@ class Collection extends React.Component { } render() { - const title = this.hasCollection() ? this.getCollectionName() : null; const isOwner = this.isOwner(); return ( @@ -425,7 +254,7 @@ class Collection extends React.Component { {this.getTitle()} {this._renderLoader()} - {this.hasCollection() && this._renderCollectionMetadata()} +
{this._renderEmptyTable()} @@ -465,19 +294,6 @@ class Collection extends React.Component { )} - {this.state.isAddingSketches && ( - } - closeOverlay={this.hideAddSketches} - isFixedHeight - > - - - )}
@@ -487,6 +303,7 @@ class Collection extends React.Component { } Collection.propTypes = { + collectionId: PropTypes.string.isRequired, user: PropTypes.shape({ username: PropTypes.string, authenticated: PropTypes.bool.isRequired @@ -505,7 +322,6 @@ Collection.propTypes = { username: PropTypes.string, loading: PropTypes.bool.isRequired, toggleDirectionForField: PropTypes.func.isRequired, - editCollection: PropTypes.func.isRequired, resetSorting: PropTypes.func.isRequired, sorting: PropTypes.shape({ field: PropTypes.string.isRequired, diff --git a/client/modules/User/components/CollectionMetadata.jsx b/client/modules/User/components/CollectionMetadata.jsx index 563fd7d6ff..108a40850d 100644 --- a/client/modules/User/components/CollectionMetadata.jsx +++ b/client/modules/User/components/CollectionMetadata.jsx @@ -1,553 +1,126 @@ +import classNames from 'classnames'; import PropTypes from 'prop-types'; -import React, { useState, useRef, useEffect } from 'react'; -import { Helmet } from 'react-helmet'; -import { connect } from 'react-redux'; +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useDispatch, useSelector } from 'react-redux'; import { Link } from 'react-router'; -import { bindActionCreators } from 'redux'; -import { useTranslation, withTranslation } from 'react-i18next'; -import classNames from 'classnames'; - import Button from '../../../common/Button'; -import { DropdownArrowIcon } from '../../../common/icons'; -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 * 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 EditableInput from '../../IDE/components/EditableInput'; import Overlay from '../../App/components/Overlay'; +import { editCollection } from '../../IDE/actions/collections'; import AddToCollectionSketchList from '../../IDE/components/AddToCollectionSketchList'; -import CopyableInput from '../../IDE/components/CopyableInput'; +import EditableInput from '../../IDE/components/EditableInput'; import { SketchSearchbar } from '../../IDE/components/Searchbar'; -import dates from '../../../utils/formatDate'; - -import ArrowUpIcon from '../../../images/sort-arrow-up.svg'; -import ArrowDownIcon from '../../../images/sort-arrow-down.svg'; -import RemoveIcon from '../../../images/close.svg'; - -const ShareURL = ({ value }) => { - const [showURL, setShowURL] = useState(false); - const node = useRef(); - const { t } = useTranslation(); - - const handleClickOutside = (e) => { - if (node.current.contains(e.target)) { - return; - } - setShowURL(false); - }; - - useEffect(() => { - if (showURL) { - document.addEventListener('mousedown', handleClickOutside); - } else { - document.removeEventListener('mousedown', handleClickOutside); - } - - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [showURL]); - - return ( -
- - {showURL && ( -
- -
- )} -
- ); -}; - -ShareURL.propTypes = { - value: PropTypes.string.isRequired -}; +import { getCollection } from '../../IDE/selectors/collections'; +import ShareURL from './CollectionShareButton'; -const CollectionItemRowBase = ({ - collection, - item, - isOwner, - removeFromCollection -}) => { +function CollectionMetadata({ collectionId }) { const { t } = useTranslation(); - const projectIsDeleted = item.isDeleted; - - const handleSketchRemove = () => { - const name = projectIsDeleted ? 'deleted sketch' : item.project.name; - - if ( - window.confirm( - t('Collection.DeleteFromCollection', { name_sketch: name }) - ) - ) { - removeFromCollection(collection.id, item.projectId); - } - }; - - const name = projectIsDeleted ? ( - {t('Collection.SketchDeleted')} - ) : ( - - {item.project.name} - - ); - - const sketchOwnerUsername = projectIsDeleted - ? null - : item.project.user.username; - - return ( - - {name} - {dates.format(item.createdAt)} - {sketchOwnerUsername} - - {isOwner && ( - - )} - - - ); -}; + const dispatch = useDispatch(); -CollectionItemRowBase.propTypes = { - collection: PropTypes.shape({ - id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired - }).isRequired, - item: PropTypes.shape({ - createdAt: PropTypes.string.isRequired, - projectId: PropTypes.string.isRequired, - isDeleted: PropTypes.bool.isRequired, - project: PropTypes.shape({ - id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - user: PropTypes.shape({ - username: PropTypes.string.isRequired - }) - }).isRequired - }).isRequired, - isOwner: PropTypes.bool.isRequired, - user: PropTypes.shape({ - username: PropTypes.string, - authenticated: PropTypes.bool.isRequired - }).isRequired, - removeFromCollection: PropTypes.func.isRequired -}; - -function mapDispatchToPropsSketchListRow(dispatch) { - return bindActionCreators( - Object.assign({}, CollectionsActions, ProjectActions, IdeActions), - dispatch - ); -} + const collection = useSelector((state) => getCollection(state, collectionId)); + const currentUsername = useSelector((state) => state.user.username); -const CollectionItemRow = connect( - null, - mapDispatchToPropsSketchListRow -)(CollectionItemRowBase); - -class Collection extends React.Component { - constructor(props) { - super(props); - this.props.getCollections(this.props.username); - this.props.resetSorting(); - this._renderFieldHeader = this._renderFieldHeader.bind(this); - this.showAddSketches = this.showAddSketches.bind(this); - this.hideAddSketches = this.hideAddSketches.bind(this); - - this.state = { - isAddingSketches: false - }; - } - - 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 - }); - } - - getUsername() { - return this.props.username !== undefined - ? this.props.username - : this.props.user.username; - } - - getCollectionName() { - return this.props.collection.name; - } - - isOwner() { - let isOwner = false; - - if ( - this.props.user != null && - this.props.user.username && - this.props.collection.owner.username === this.props.user.username - ) { - isOwner = true; - } + const [isAddingSketches, setIsAddingSketches] = useState(false); - return isOwner; - } - - hasCollection() { - return !this.props.loading && this.props.collection != null; - } - - hasCollectionItems() { - return this.hasCollection() && this.props.collection.items.length > 0; - } - - _renderLoader() { - if (this.props.loading) return ; + if (!collection) { return null; } - _renderCollectionMetadata() { - const { id, name, description, items, owner } = this.props.collection; - - const hostname = window.location.origin; - const { username } = this.props; - - const baseURL = `${hostname}/${username}/collections/`; - - const handleEditCollectionName = (value) => { - if (value === name) { - return; - } - - this.props.editCollection(id, { name: value }); - }; - - const handleEditCollectionDescription = (value) => { - if (value === description) { - return; - } - - this.props.editCollection(id, { description: value }); - }; - - // - // TODO: Implement UI for editing slug - // - // const handleEditCollectionSlug = (value) => { - // if (value === slug) { - // return; - // } - // - // this.props.editCollection(id, { slug: value }); - // }; - - return ( -
-
-
-

- {this.isOwner() ? ( - value !== ''} - /> - ) : ( - name - )} -

+ const { id, name, description, items, owner } = collection; + const { username } = owner; + const isOwner = !!currentUsername && currentUsername === username; -

- {this.isOwner() ? ( - - ) : ( - description - )} -

+ const hostname = window.location.origin; -

- {this.props.t('Collection.By')} - - {owner.username} - -

- -

- {this.props.t('Collection.NumSketches', { count: items.length })} -

-
- -
-

- -

- {this.isOwner() && ( - - )} -
-
-
- ); - } - - showAddSketches() { - this.setState({ - isAddingSketches: true - }); - } - - hideAddSketches() { - this.setState({ - isAddingSketches: false - }); - } - - _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')} -

- ); + const handleEditCollectionName = (value) => { + if (value === name) { + return; } - return null; - } + dispatch(editCollection(id, { name: value })); + }; - _getButtonLabel = (fieldName, displayName) => { - const { field, direction } = this.props.sorting; - let buttonLabel; - if (field !== fieldName) { - if (field === 'name') { - buttonLabel = this.props.t('Collection.ButtonLabelAscendingARIA', { - displayName - }); - } else { - buttonLabel = this.props.t('Collection.ButtonLabelDescendingARIA', { - displayName - }); - } - } else if (direction === SortingActions.DIRECTION.ASC) { - buttonLabel = this.props.t('Collection.ButtonLabelDescendingARIA', { - displayName - }); - } else { - buttonLabel = this.props.t('Collection.ButtonLabelAscendingARIA', { - displayName - }); + const handleEditCollectionDescription = (value) => { + if (value === description) { + return; } - return buttonLabel; + dispatch(editCollection(id, { description: value })); }; - _renderFieldHeader(fieldName, displayName) { - const { field, direction } = this.props.sorting; - const headerClass = classNames({ - arrowDown: true, - 'sketches-table__header--selected': field === fieldName - }); - const buttonLabel = this._getButtonLabel(fieldName, displayName); - return ( - - - - ); - } - - render() { - const title = this.hasCollection() ? this.getCollectionName() : null; - const isOwner = this.isOwner(); - - return ( -
-
- - {this.getTitle()} - - {this._renderLoader()} - {this.hasCollection() && this._renderCollectionMetadata()} -
-
- {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) => ( - - ))} - -
- )} - {this.state.isAddingSketches && ( - } - closeOverlay={this.hideAddSketches} - isFixedHeight - > - - - )} -
-
-
-
- ); - } -} +

-Collection.propTypes = { - 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, - editCollection: PropTypes.func.isRequired, - resetSorting: PropTypes.func.isRequired, - sorting: PropTypes.shape({ - field: PropTypes.string.isRequired, - direction: PropTypes.string.isRequired - }).isRequired, - t: PropTypes.func.isRequired -}; +

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

-Collection.defaultProps = { - username: undefined, - collection: { - id: undefined, - items: [], - owner: { - username: undefined - } - } -}; - -function mapStateToProps(state, ownProps) { - return { - user: state.user, - collection: getCollection(state, ownProps.collectionId), - sorting: state.sorting, - loading: state.loading, - project: state.project - }; -} +

+ {t('Collection.NumSketches', { count: items.length })} +

+ -function mapDispatchToProps(dispatch) { - return bindActionCreators( - Object.assign( - {}, - CollectionsActions, - ProjectsActions, - ToastActions, - SortingActions - ), - dispatch +
+ + {isOwner && ( + + )} +
+ + {isAddingSketches && ( + } + closeOverlay={() => setIsAddingSketches(false)} + isFixedHeight + > + + + )} + ); } -export default withTranslation()( - connect(mapStateToProps, mapDispatchToProps)(Collection) -); +CollectionMetadata.propTypes = { + collectionId: PropTypes.string.isRequired +}; + +export default CollectionMetadata; diff --git a/client/modules/User/components/CollectionShareButton.jsx b/client/modules/User/components/CollectionShareButton.jsx index 563fd7d6ff..c4e0bba915 100644 --- a/client/modules/User/components/CollectionShareButton.jsx +++ b/client/modules/User/components/CollectionShareButton.jsx @@ -1,32 +1,10 @@ import PropTypes from 'prop-types'; -import React, { useState, useRef, useEffect } from 'react'; -import { Helmet } from 'react-helmet'; -import { connect } from 'react-redux'; -import { Link } from 'react-router'; -import { bindActionCreators } from 'redux'; -import { useTranslation, withTranslation } from 'react-i18next'; -import classNames from 'classnames'; +import React, { useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import Button from '../../../common/Button'; import { DropdownArrowIcon } from '../../../common/icons'; -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 * 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 EditableInput from '../../IDE/components/EditableInput'; -import Overlay from '../../App/components/Overlay'; -import AddToCollectionSketchList from '../../IDE/components/AddToCollectionSketchList'; import CopyableInput from '../../IDE/components/CopyableInput'; -import { SketchSearchbar } from '../../IDE/components/Searchbar'; -import dates from '../../../utils/formatDate'; - -import ArrowUpIcon from '../../../images/sort-arrow-up.svg'; -import ArrowDownIcon from '../../../images/sort-arrow-down.svg'; -import RemoveIcon from '../../../images/close.svg'; const ShareURL = ({ value }) => { const [showURL, setShowURL] = useState(false); @@ -34,7 +12,7 @@ const ShareURL = ({ value }) => { const { t } = useTranslation(); const handleClickOutside = (e) => { - if (node.current.contains(e.target)) { + if (node.current?.contains(e.target)) { return; } setShowURL(false); @@ -73,481 +51,4 @@ ShareURL.propTypes = { value: PropTypes.string.isRequired }; -const CollectionItemRowBase = ({ - collection, - item, - isOwner, - removeFromCollection -}) => { - const { t } = useTranslation(); - - const projectIsDeleted = item.isDeleted; - - const handleSketchRemove = () => { - const name = projectIsDeleted ? 'deleted sketch' : item.project.name; - - if ( - window.confirm( - t('Collection.DeleteFromCollection', { name_sketch: name }) - ) - ) { - removeFromCollection(collection.id, item.projectId); - } - }; - - const name = projectIsDeleted ? ( - {t('Collection.SketchDeleted')} - ) : ( - - {item.project.name} - - ); - - const sketchOwnerUsername = projectIsDeleted - ? null - : item.project.user.username; - - return ( - - {name} - {dates.format(item.createdAt)} - {sketchOwnerUsername} - - {isOwner && ( - - )} - - - ); -}; - -CollectionItemRowBase.propTypes = { - collection: PropTypes.shape({ - id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired - }).isRequired, - item: PropTypes.shape({ - createdAt: PropTypes.string.isRequired, - projectId: PropTypes.string.isRequired, - isDeleted: PropTypes.bool.isRequired, - project: PropTypes.shape({ - id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - user: PropTypes.shape({ - username: PropTypes.string.isRequired - }) - }).isRequired - }).isRequired, - isOwner: PropTypes.bool.isRequired, - user: PropTypes.shape({ - username: PropTypes.string, - authenticated: PropTypes.bool.isRequired - }).isRequired, - removeFromCollection: PropTypes.func.isRequired -}; - -function mapDispatchToPropsSketchListRow(dispatch) { - return bindActionCreators( - Object.assign({}, CollectionsActions, ProjectActions, IdeActions), - dispatch - ); -} - -const CollectionItemRow = connect( - null, - mapDispatchToPropsSketchListRow -)(CollectionItemRowBase); - -class Collection extends React.Component { - constructor(props) { - super(props); - this.props.getCollections(this.props.username); - this.props.resetSorting(); - this._renderFieldHeader = this._renderFieldHeader.bind(this); - this.showAddSketches = this.showAddSketches.bind(this); - this.hideAddSketches = this.hideAddSketches.bind(this); - - this.state = { - isAddingSketches: false - }; - } - - 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 - }); - } - - getUsername() { - return this.props.username !== undefined - ? this.props.username - : this.props.user.username; - } - - getCollectionName() { - return this.props.collection.name; - } - - isOwner() { - let isOwner = false; - - if ( - this.props.user != null && - this.props.user.username && - this.props.collection.owner.username === this.props.user.username - ) { - isOwner = true; - } - - return isOwner; - } - - hasCollection() { - return !this.props.loading && this.props.collection != null; - } - - hasCollectionItems() { - return this.hasCollection() && this.props.collection.items.length > 0; - } - - _renderLoader() { - if (this.props.loading) return ; - return null; - } - - _renderCollectionMetadata() { - const { id, name, description, items, owner } = this.props.collection; - - const hostname = window.location.origin; - const { username } = this.props; - - const baseURL = `${hostname}/${username}/collections/`; - - const handleEditCollectionName = (value) => { - if (value === name) { - return; - } - - this.props.editCollection(id, { name: value }); - }; - - const handleEditCollectionDescription = (value) => { - if (value === description) { - return; - } - - this.props.editCollection(id, { description: value }); - }; - - // - // TODO: Implement UI for editing slug - // - // const handleEditCollectionSlug = (value) => { - // if (value === slug) { - // return; - // } - // - // this.props.editCollection(id, { slug: value }); - // }; - - return ( -
-
-
-

- {this.isOwner() ? ( - value !== ''} - /> - ) : ( - name - )} -

- -

- {this.isOwner() ? ( - - ) : ( - description - )} -

- -

- {this.props.t('Collection.By')} - - {owner.username} - -

- -

- {this.props.t('Collection.NumSketches', { count: items.length })} -

-
- -
-

- -

- {this.isOwner() && ( - - )} -
-
-
- ); - } - - showAddSketches() { - this.setState({ - isAddingSketches: true - }); - } - - hideAddSketches() { - this.setState({ - isAddingSketches: false - }); - } - - _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; - let buttonLabel; - if (field !== fieldName) { - if (field === 'name') { - buttonLabel = this.props.t('Collection.ButtonLabelAscendingARIA', { - displayName - }); - } else { - buttonLabel = this.props.t('Collection.ButtonLabelDescendingARIA', { - displayName - }); - } - } else if (direction === SortingActions.DIRECTION.ASC) { - buttonLabel = this.props.t('Collection.ButtonLabelDescendingARIA', { - displayName - }); - } else { - buttonLabel = this.props.t('Collection.ButtonLabelAscendingARIA', { - displayName - }); - } - return buttonLabel; - }; - - _renderFieldHeader(fieldName, displayName) { - const { field, direction } = this.props.sorting; - const headerClass = classNames({ - arrowDown: true, - 'sketches-table__header--selected': field === fieldName - }); - const buttonLabel = this._getButtonLabel(fieldName, displayName); - return ( - - - - ); - } - - render() { - const title = this.hasCollection() ? this.getCollectionName() : null; - const isOwner = this.isOwner(); - - return ( -
-
- - {this.getTitle()} - - {this._renderLoader()} - {this.hasCollection() && this._renderCollectionMetadata()} -
-
- {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) => ( - - ))} - -
- )} - {this.state.isAddingSketches && ( - } - closeOverlay={this.hideAddSketches} - isFixedHeight - > - - - )} -
-
-
-
- ); - } -} - -Collection.propTypes = { - 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, - editCollection: 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 - } - } -}; - -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 ShareURL; From 17376571d337babdeaaf4b74a99b54802769ddbe Mon Sep 17 00:00:00 2001 From: Linda Paiste Date: Mon, 7 Aug 2023 19:21:32 -0500 Subject: [PATCH 3/3] Update Link import for react-router v5. --- client/modules/User/components/CollectionMetadata.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/modules/User/components/CollectionMetadata.jsx b/client/modules/User/components/CollectionMetadata.jsx index 108a40850d..4b1be7ff5f 100644 --- a/client/modules/User/components/CollectionMetadata.jsx +++ b/client/modules/User/components/CollectionMetadata.jsx @@ -3,7 +3,7 @@ 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';