Skip to content

Move CollectionMetadata into its own component. #2253

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Nov 17, 2023
192 changes: 4 additions & 188 deletions client/modules/User/components/Collection.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
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-dom';
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';
Expand All @@ -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 (
<div className="collection-share" ref={node}>
<Button
onClick={() => setShowURL(!showURL)}
iconAfter={<DropdownArrowIcon />}
>
{t('Collection.Share')}
</Button>
{showURL && (
<div className="collection__share-dropdown">
<CopyableInput value={value} label={t('Collection.URLLink')} />
</div>
)}
</div>
);
};

ShareURL.propTypes = {
value: PropTypes.string.isRequired
};
import CollectionMetadata from './CollectionMetadata';

const CollectionItemRowBase = ({
collection,
Expand Down Expand Up @@ -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() {
Expand All @@ -195,10 +138,6 @@ class Collection extends React.Component {
: this.props.user.username;
}

getCollectionName() {
return this.props.collection.name;
}

isOwner() {
let isOwner = false;

Expand Down Expand Up @@ -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 (
<header
className={`collection-metadata ${
this.isOwner() ? 'collection-metadata--is-owner' : ''
}`}
>
<div className="collection-metadata__columns">
<div className="collection-metadata__column--left">
<h2 className="collection-metadata__name">
{this.isOwner() ? (
<EditableInput
value={name}
onChange={handleEditCollectionName}
validate={(value) => value !== ''}
/>
) : (
name
)}
</h2>

<p className="collection-metadata__description">
{this.isOwner() ? (
<EditableInput
InputComponent="textarea"
value={description}
onChange={handleEditCollectionDescription}
emptyPlaceholder={this.props.t(
'Collection.DescriptionPlaceholder'
)}
/>
) : (
description
)}
</p>

<p className="collection-metadata__user">
{this.props.t('Collection.By')}
<Link to={`${hostname}/${username}/sketches`}>
{owner.username}
</Link>
</p>

<p className="collection-metadata__user">
{this.props.t('Collection.NumSketches', { count: items.length })}
</p>
</div>

<div className="collection-metadata__column--right">
<p className="collection-metadata__share">
<ShareURL value={`${baseURL}${id}`} />
</p>
{this.isOwner() && (
<Button onClick={this.showAddSketches}>
{this.props.t('Collection.AddSketch')}
</Button>
)}
</div>
</div>
</header>
);
}

showAddSketches() {
this.setState({
isAddingSketches: true
});
}

hideAddSketches() {
this.setState({
isAddingSketches: false
});
}

_renderEmptyTable() {
if (this.hasCollection() && !this.hasCollectionItems()) {
return (
Expand Down Expand Up @@ -408,7 +238,6 @@ class Collection extends React.Component {
}

render() {
const title = this.hasCollection() ? this.getCollectionName() : null;
const isOwner = this.isOwner();

return (
Expand All @@ -421,7 +250,7 @@ class Collection extends React.Component {
<title>{this.getTitle()}</title>
</Helmet>
{this._renderLoader()}
{this.hasCollection() && this._renderCollectionMetadata()}
<CollectionMetadata collectionId={this.props.collectionId} />
<article className="collection-content">
<div className="collection-table-wrapper">
{this._renderEmptyTable()}
Expand Down Expand Up @@ -461,19 +290,6 @@ class Collection extends React.Component {
</tbody>
</table>
)}
{this.state.isAddingSketches && (
<Overlay
title={this.props.t('Collection.AddSketch')}
actions={<SketchSearchbar />}
closeOverlay={this.hideAddSketches}
isFixedHeight
>
<AddToCollectionSketchList
username={this.props.username}
collection={this.props.collection}
/>
</Overlay>
)}
</div>
</article>
</article>
Expand All @@ -483,6 +299,7 @@ class Collection extends React.Component {
}

Collection.propTypes = {
collectionId: PropTypes.string.isRequired,
user: PropTypes.shape({
username: PropTypes.string,
authenticated: PropTypes.bool.isRequired
Expand All @@ -501,7 +318,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,
Expand Down
126 changes: 126 additions & 0 deletions client/modules/User/components/CollectionMetadata.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import classNames from 'classnames';
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-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 }) {
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 hostname = window.location.origin;

const handleEditCollectionName = (value) => {
if (value === name) {
return;
}
dispatch(editCollection(id, { name: value }));
};

const handleEditCollectionDescription = (value) => {
if (value === description) {
return;
}
dispatch(editCollection(id, { description: value }));
};

// TODO: Implement UI for editing slug

return (
<header
className={classNames(
'collection-metadata',
isOwner && 'collection-metadata--is-owner'
)}
>
<div className="collection-metadata__columns">
<div className="collection-metadata__column--left">
<h2 className="collection-metadata__name">
{isOwner ? (
<EditableInput
value={name}
onChange={handleEditCollectionName}
validate={(value) => value !== ''}
/>
) : (
name
)}
</h2>

<p className="collection-metadata__description">
{isOwner ? (
<EditableInput
InputComponent="textarea"
value={description}
onChange={handleEditCollectionDescription}
emptyPlaceholder={t('Collection.DescriptionPlaceholder')}
/>
) : (
description
)}
</p>

<p className="collection-metadata__user">
{t('Collection.By')}
<Link to={`/${username}/sketches`}>{username}</Link>
</p>

<p className="collection-metadata__user">
{t('Collection.NumSketches', { count: items.length })}
</p>
</div>

<div className="collection-metadata__column--right">
<ShareURL value={`${hostname}/${username}/collections/${id}`} />
{isOwner && (
<Button onClick={() => setIsAddingSketches(true)}>
{t('Collection.AddSketch')}
</Button>
)}
</div>
</div>
{isAddingSketches && (
<Overlay
title={t('Collection.AddSketch')}
actions={<SketchSearchbar />}
closeOverlay={() => setIsAddingSketches(false)}
isFixedHeight
>
<AddToCollectionSketchList
username={username}
collection={collection}
/>
</Overlay>
)}
</header>
);
}

CollectionMetadata.propTypes = {
collectionId: PropTypes.string.isRequired
};

export default CollectionMetadata;
Loading