bookbrainz/bookbrainz-site

View on GitHub
src/client/components/pages/collection.js

Summary

Maintainability
F
3 days
Test Coverage
/*
 * Copyright (C) 2020 Prabal Singh
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

import * as bootstrap from 'react-bootstrap';
import {faPencilAlt, faPlus, faTimesCircle, faTrashAlt} from '@fortawesome/free-solid-svg-icons';
import {formatDate, getEntityKey, getEntityTable} from '../../helpers/utils';
import AddEntityToCollectionModal from './parts/add-entity-to-collection-modal';
import DeleteOrRemoveCollaborationModal from './parts/delete-or-remove-collaboration-modal';
import {ENTITY_TYPE_ICONS} from '../../helpers/entity';
import EntityImage from './entities/image';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import PagerElement from './parts/pager';
import PropTypes from 'prop-types';
import React from 'react';
import _ from 'lodash';
import request from 'superagent';


const {Alert, Badge, Button, Col, Row} = bootstrap;

function CollectionAttributes({collection}) {
    return (
        <div>
            {
                collection.description.length ?
                    <Row>
                        <Col lg={12}>
                            <dt>Description</dt>
                            <dd>{collection.description}</dd>
                        </Col>
                    </Row> : null
            }
            <Row>
                <Col lg={3}>
                    <dt>Owner</dt>
                    <dd><a href={`/editor/${collection.ownerId}`}>{collection.owner.name}</a></dd>
                </Col>
                {
                    collection.collaborators.length ?
                        <Col lg={3}>
                            <dt>Collaborator{collection.collaborators.length > 1 ? 's' : null}</dt>
                            <dd>
                                {
                                    collection.collaborators.map((collaborator, id) =>
                                        (
                                            <a href={`/editor/${collaborator.id}`} key={collaborator.id}>
                                                {collaborator.text}{id === collection.collaborators.length - 1 ? null : ', '}
                                            </a>
                                        ))
                                }
                            </dd>
                        </Col> : null
                }
                <Col lg={3}>
                    <dt>Privacy</dt>
                    <dd>{collection.public ? 'Public' : 'Private'}</dd>
                </Col>
                <Col lg={3}>
                    <dt>Collection type</dt>
                    <dd>{collection.entityType}</dd>
                </Col>
                <Col lg={3}>
                    <dt>Number of {_.kebabCase(collection.entityType)}s</dt>
                    <dd>{collection.items.length}</dd>
                </Col>
                <Col lg={3}>
                    <dt>Created At</dt>
                    <dd>{formatDate(new Date(collection.createdAt), true)}</dd>
                </Col>
                <Col lg={3}>
                    <dt>Last Modified</dt>
                    <dd>{formatDate(new Date(collection.lastModified), true)}</dd>
                </Col>
            </Row>
        </div>
    );
}
CollectionAttributes.displayName = 'CollectionAttributes';
CollectionAttributes.propTypes = {
    collection: PropTypes.object.isRequired
};

class CollectionPage extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            entities: this.props.entities,
            message: {
                text: null,
                type: null
            },
            selectedEntities: [],
            showAddEntityModal: false,
            showDeleteModal: false
        };

        this.entityKey = getEntityKey(this.props.collection.entityType);
        this.paginationUrl = `/collection/${this.props.collection.id}/paginate`;
        this.toggleRow = this.toggleRow.bind(this);
        this.handleRemoveEntities = this.handleRemoveEntities.bind(this);
        this.handleShowDeleteModal = this.handleShowDeleteModal.bind(this);
        this.handleCloseDeleteModal = this.handleCloseDeleteModal.bind(this);
        this.handleShowAddEntityModal = this.handleShowAddEntityModal.bind(this);
        this.handleCloseAddEntityModal = this.handleCloseAddEntityModal.bind(this);
        this.handleAlertDismiss = this.handleAlertDismiss.bind(this);
        this.searchResultsCallback = this.searchResultsCallback.bind(this);
        this.closeAddEntityModalShowMessageAndRefreshTable = this.closeAddEntityModalShowMessageAndRefreshTable.bind(this);
    }

    searchResultsCallback(newResults) {
        this.setState({entities: newResults});
    }

    toggleRow(bbid) {
        const oldSelected = this.state.selectedEntities;
        let newSelected;
        if (oldSelected.find(selectedBBID => selectedBBID === bbid)) {
            newSelected = oldSelected.filter(selectedBBID => selectedBBID !== bbid);
        }
        else {
            newSelected = [...oldSelected, bbid];
        }
        this.setState({
            selectedEntities: newSelected
        });
    }

    handleRemoveEntities() {
        if (this.state.selectedEntities.length) {
            const bbids = this.state.selectedEntities;
            const submissionUrl = `/collection/${this.props.collection.id}/remove`;
            request.post(submissionUrl)
                .send({bbids})
                .then(() => {
                    this.setState({
                        message: {
                            text: `Removed ${bbids.length} ${_.kebabCase(this.props.collection.entityType)}${bbids.length > 1 ? 's' : ''}`,
                            type: 'success'
                        },
                        selectedEntities: []
                    }, this.pagerElementRef.triggerSearch);
                }, () => {
                    this.setState({
                        message: {
                            text: 'Something went wrong! Please try again later',
                            type: 'danger'
                        }
                    });
                });
        }
        else {
            this.setState({
                message: {
                    text: `No ${_.kebabCase(this.props.collection.entityType)} selected`,
                    type: 'danger'
                }
            });
        }
    }

    handleShowDeleteModal() {
        this.setState({showDeleteModal: true});
    }

    handleCloseDeleteModal() {
        this.setState({showDeleteModal: false});
    }

    handleShowAddEntityModal() {
        this.setState({showAddEntityModal: true});
    }

    handleCloseAddEntityModal() {
        this.setState({showAddEntityModal: false});
    }

    handleAlertDismiss() {
        this.setState({message: {}});
    }

    closeAddEntityModalShowMessageAndRefreshTable(message) {
        this.setState({
            message,
            showAddEntityModal: false
        }, this.pagerElementRef.triggerSearch);
    }

    render() {
        const messageComponent =
            this.state.message.text ? (
                <Alert
                    className="margin-top-1"
                    variant={this.state.message.type}
                    onDismiss={this.handleAlertDismiss}
                >
                     {this.state.message.text}
                </Alert>
            ) : null;
        const EntityTable = getEntityTable(this.props.collection.entityType);
        const propsForTable = {
            [this.entityKey]: this.state.entities,
            onToggleRow: this.toggleRow,
            selectedEntities: this.state.selectedEntities,
            showAdd: false,
            showAddedAtColumn: true,
            showCheckboxes: Boolean(this.props.isOwner) || Boolean(this.props.isCollaborator)
        };
        return (
            <div>
                <DeleteOrRemoveCollaborationModal
                    collection={this.props.collection}
                    isDelete={this.props.isOwner}
                    show={this.state.showDeleteModal}
                    userId={this.props.userId}
                    onCloseModal={this.handleCloseDeleteModal}
                />
                <AddEntityToCollectionModal
                    closeModalAndShowMessage={this.closeAddEntityModalShowMessageAndRefreshTable}
                    collectionId={this.props.collection.id}
                    collectionType={this.props.collection.entityType}
                    show={this.state.showAddEntityModal}
                    onCloseModal={this.handleCloseAddEntityModal}
                />
                <Row className="entity-display-background">
                    <Col className="entity-display-image-box text-center" lg={2}>
                        <EntityImage
                            backupIcon={ENTITY_TYPE_ICONS[this.props.collection.entityType]}
                        />
                    </Col>
                    <Col lg={10}>
                        <h1>{this.props.collection.name}</h1>
                        <hr/>
                        <CollectionAttributes collection={this.props.collection}/>
                    </Col>
                </Row>
                <EntityTable {...propsForTable}/>
                {messageComponent}
                <div className="margin-top-1 text-left">
                    {
                        this.props.isCollaborator || this.props.isOwner ?
                            <Button
                                className="margin-bottom-d5"
                                size="sm"
                                title={`Add ${this.props.collection.entityType}`}
                                variant="success"
                                onClick={this.handleShowAddEntityModal}
                            >
                                <FontAwesomeIcon icon={faPlus}/>
                                &nbsp;Add {_.lowerCase(this.props.collection.entityType)}
                            </Button> : null
                    }
                    {
                        (this.props.isCollaborator || this.props.isOwner) && this.state.entities.length ?
                            <Button
                                className="margin-bottom-d5"
                                disabled={!this.state.selectedEntities.length}
                                size="sm"
                                title={`Remove selected ${_.kebabCase(this.props.collection.entityType)}s`}
                                variant="danger"
                                onClick={this.handleRemoveEntities}
                            >
                                <FontAwesomeIcon icon={faTimesCircle}/>
                                &nbsp;Remove <Badge pill>{this.state.selectedEntities.length}</Badge> selected&nbsp;
                                {_.kebabCase(this.props.collection.entityType)}{this.state.selectedEntities.length > 1 ? 's' : null}
                            </Button> : null
                    }
                    {
                        this.props.isOwner ?
                            <Button
                                className="margin-bottom-d5"
                                href={`/collection/${this.props.collection.id}/edit`}
                                size="sm"
                                title="Edit Collection"
                                variant="warning"
                            >
                                <FontAwesomeIcon icon={faPencilAlt}/>&nbsp;Edit collection
                            </Button> : null
                    }
                    {
                        this.props.isOwner ?
                            <Button
                                className="margin-bottom-d5"
                                size="sm"
                                title="Delete Collection"
                                variant="danger"
                                onClick={this.handleShowDeleteModal}
                            >
                                <FontAwesomeIcon icon={faTrashAlt}/>&nbsp;Delete collection
                            </Button> : null
                    }
                    {
                        this.props.isCollaborator ?
                            <Button
                                className="margin-bottom-d5"
                                size="sm"
                                title="Remove yourself as a collaborator"
                                variant="warning"
                                onClick={this.handleShowDeleteModal}
                            >
                                <FontAwesomeIcon icon={faTimesCircle}/>&nbsp;Stop collaboration
                            </Button> : null
                    }
                </div>
                <div id="pageWithPagination">
                    <PagerElement
                        from={this.props.from}
                        nextEnabled={this.props.nextEnabled}
                        paginationUrl={this.paginationUrl}
                        ref={(ref) => this.pagerElementRef = ref}
                        results={this.state.entities}
                        searchResultsCallback={this.searchResultsCallback}
                        size={this.props.size}
                    />
                </div>
            </div>
        );
    }
}


CollectionPage.displayName = 'CollectionPage';
CollectionPage.propTypes = {
    collection: PropTypes.object.isRequired,
    entities: PropTypes.array,
    from: PropTypes.number,
    isCollaborator: PropTypes.bool,
    isOwner: PropTypes.bool,
    nextEnabled: PropTypes.bool.isRequired,
    showCheckboxes: PropTypes.bool,
    size: PropTypes.number,
    userId: PropTypes.number
};
CollectionPage.defaultProps = {
    entities: [],
    from: 0,
    isCollaborator: false,
    isOwner: false,
    showCheckboxes: false,
    size: 20,
    userId: null
};

export default CollectionPage;