bookbrainz/bookbrainz-site

View on GitHub
src/client/helpers/entity.tsx

Summary

Maintainability
D
1 day
Test Coverage
/*
 * Copyright (C) 2016  Daniel Hsing
 *                  2019  Akhilesh Kumar (@akhilesh26)
 *
 * 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 React from 'react';

// eslint-disable-next-line import/named
import {FontAwesomeIconProps as FAProps, FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {get as _get, isNil as _isNil, kebabCase as _kebabCase, upperFirst} from 'lodash';
import {
    faBook, faGlobe, faGripVertical, faLayerGroup, faMagicWandSparkles, faPenNib, faUniversity, faUser, faUserCircle, faWindowRestore
} from '@fortawesome/free-solid-svg-icons';
import {format, isValid, parseISO} from 'date-fns';
import {dateObjectToISOString} from './utils';


export function extractAttribute(attr, path) {
    if (attr) {
        if (path) {
            return _get(attr, path, '?');
        }
        return attr;
    }
    return '?';
}

export function getLanguageAttribute(entity) {
    const languages = entity.languageSet && entity.languageSet.languages ?
        entity.languageSet.languages.map(
            (language) => language.name
        ).join(', ') : '?';
    return {data: languages, title: 'Languages'};
}

export function getTypeAttribute(entityType) {
    return {data: extractAttribute(entityType, 'label'), title: 'Type'};
}

/**
 * Transforms an extended ISO 8601-2004 string to a more human-firendly result
 * @function transformISODateForDisplay
 * @param {string} ISODateString - an extended ISO date string (±YYYYYY-MM-DD)
 * @returns {string} A date string with less padding zeros
 */
export function transformISODateForDisplay(ISODateString) {
    if (_isNil(ISODateString)) {
        return ISODateString;
    }
    const dateStringWithoutSign = ISODateString.slice(1);
    const parts = dateStringWithoutSign.split('-');
    let formatting;
    switch (parts.length) {
        case 1:
            formatting = 'uuuu';
            break;
        case 2:
            formatting = 'uuuu-MM';
            break;
        case 3:
            formatting = 'uuuu-MM-dd';
            break;
        default:
            return ISODateString;
    }
    const parsedDate = parseISO(ISODateString, {additionalDigits: 2});
    if (!isValid(parsedDate)) {
        return ISODateString;
    }
    return format(
        parsedDate,
        formatting
    );
}

/**
 * Transforms an extended ISO 8601-2004 date string to an option fit for react-select
 * @function transformISODateForSelect
 * @param {string|object} dateValue - an extended ISO date string (±YYYYYY-MM-DD) or date object {day,month,year}
 * @returns {object} - A {label,value} object for react-select option
 */
export function transformISODateForSelect(dateValue) {
    let dateString = dateValue;
    if (typeof dateValue !== 'string') {
        dateString = dateObjectToISOString(dateValue);
    }
    return {
        label: transformISODateForDisplay(dateString),
        value: dateString
    };
}

/**
 * Determines whether an entity provided to the EntitySearch component is an
 * Area, using the present attributes.
 *
 * @param {Object} entity the entity to test
 * @returns {boolean} true if the entity looks like an Area
 */
function isArea(entity) {
    if (entity.type === 'Area') {
        return true;
    }

    return Boolean(entity.gid);
}

/**
 * Transforms an Area entity to a react-select component option
 * @param {object} area - The Area entity to transfrom
 * @returns {object} option - A react-select option
 */
export function areaToOption(
    area: {comment: string, id: number, name: string} | null
) {
    if (!area) {
        return null;
    }
    const {id} = area;
    return {
        disambiguation: area.comment,
        id,
        text: area.name,
        type: 'area'
        // value: id
    };
}

/**
 * Transforms an entity to a react-select component option
 * @param {object} entity - The entity to transfrom
 * @returns {object} option - A react-select option
 */
export function entityToOption(entity) {
    if (_isNil(entity)) {
        return null;
    }
    if (isArea(entity)) {
        return areaToOption(entity);
    }

    return {
        disambiguation: entity.disambiguation ?
            entity.disambiguation.comment : null,
        id: entity.bbid,
        text: entity.defaultAlias ?
            entity.defaultAlias.name : '(unnamed)',
        type: entity.type
    };
}

export function getEntityLabel(entity, returnHTML = true) {
    if (entity.defaultAlias) {
        return `${entity.defaultAlias.name}`;
    }

    // Deleted entities
    if (!entity.dataId) {
        let deletedEntityName = `Deleted ${entity.type} ${entity.bbid}`;
        if (entity.parentAlias) {
            deletedEntityName = entity.parentAlias.name;
        }
        if (returnHTML) {
            return <span className="deleted"><span className="text-muted" title={`This ${entity.type} was deleted`}>{deletedEntityName}</span></span>;
        }
        return `${deletedEntityName}`;
    }
    if (returnHTML) {
        return <span title={`Unnamed ${entity.type} ${entity.bbid}`}>(unnamed)</span>;
    }
    return 'Unnamed';
}

export function getEditionReleaseDate(edition) {
    const hasReleaseEvents = edition.releaseEventSet &&
        edition.releaseEventSet.releaseEvents &&
        edition.releaseEventSet.releaseEvents.length;

    if (hasReleaseEvents) {
        return transformISODateForDisplay(edition.releaseEventSet.releaseEvents[0].date);
    }

    return '?';
}

export function getAuthorCreditNames(edition) {
    if (edition.authorCreditId === null) {
        return [];
    }
    return edition.authorCredit.names;
}

export function getEditionPublishers(edition) {
    const hasPublishers = edition.publisherSet &&
        edition.publisherSet.publishers.length > 0;

    if (hasPublishers) {
        return edition.publisherSet.publishers.map(
            (publisher, index) =>
                (
                    <span key={publisher.bbid}>
                        <a href={`/publisher/${publisher.bbid}`}>
                            {_get(publisher, 'defaultAlias.name', publisher.bbid)}
                        </a>{index < (edition.publisherSet.publishers.length - 1) ? ', ' : ''}
                    </span>
                )
        );
    }

    return '?';
}

export function authorCreditToString(authorCredit) {
    if (authorCredit) {
        const {names} = authorCredit;
        return names.map(acName => `${acName.name}${acName.joinPhrase}`);
    }

    return null;
}

export function getEntityDisambiguation(entity) {
    if (entity.disambiguation?.comment) {
        return <small>{` (${entity.disambiguation.comment})`}</small>;
    }
    else if (entity.disambiguation) {
        return <small>{` (${entity.disambiguation})`} </small>;
    }
    return null;
}

export function getEntitySecondaryAliases(entity) {
    if (entity.aliasSet && Array.isArray(entity.aliasSet.aliases) && entity.aliasSet.aliases.length > 1) {
        const aliases = entity.aliasSet.aliases
            .filter(item => item.id !== entity.defaultAlias.id)
            .map((item) => <li key={item.id}>{item.name}</li>);
        return <ul className="inline-aliases">{aliases}</ul>;
    }

    return null;
}

export function getEntityUrl(entity) {
    const entityType = _kebabCase(entity.type);
    const entityId = entity.bbid;

    return `/${entityType}/${entityId}`;
}

export const ENTITY_TYPE_ICONS = {
    Area: faGlobe,
    Author: faUser,
    Book: faMagicWandSparkles,
    Collection: faGripVertical,
    Edition: faBook,
    EditionGroup: faWindowRestore,
    Editor: faUserCircle,
    Publisher: faUniversity,
    Series: faLayerGroup,
    Work: faPenNib
};

export const ENTITY_TYPES = ['Author', 'Work', 'Series', 'Edition', 'EditionGroup', 'Publisher'];

type FASize = FAProps['size'];
export function genEntityIconHTMLElement(entityType: string, size: FASize = '1x', margin = true) {
    const correctCaseEntityType = upperFirst(entityType);
    if (!ENTITY_TYPE_ICONS[correctCaseEntityType]) { return null; }
    return (
        <FontAwesomeIcon
            className={margin ? 'margin-right-0-3' : ''}
            icon={ENTITY_TYPE_ICONS[correctCaseEntityType]}
            size={size}
            title={correctCaseEntityType}
        />);
}

export function getSortNameOfDefaultAlias(entity) {
    return entity.defaultAlias ? entity.defaultAlias.sortName : '?';
}

export function getISBNOfEdition(entity) {
    if (entity.identifierSet && entity.identifierSet.identifiers) {
        const {identifiers} = entity.identifierSet;
        return identifiers.find(
            identifier =>
                identifier.type.label === 'ISBN-13' || identifier.type.label === 'ISBN-10'
        );
    }
    return null;
}

export function getEditionFormat(entity) {
    return (entity.editionFormat && entity.editionFormat.label) || '?';
}


/**
 * Remove the all relationships which are belongs to given relationshipTypeId.
 *
 * @param {object} entity - Entity with all relationships
 * @param {number} relationshipTypeId - typeId of spacific relationshipType
 * @returns {array} retrun the all relationships after removing the relatioships for given relationshipTypeId
 */
export function filterOutRelationshipTypeById(entity, relationshipTypeId: number) {
    return (Array.isArray(entity.relationships) &&
                entity.relationships.filter((relation) => relation.typeId !== relationshipTypeId)) || [];
}


/**
 * Get an array of all targets from relationships of an entity belongs to given relationshipTypeId
 *
 * @param {object} entity - an entity with all relationships
 * @param {number} relationshipTypeId - typeId of spacific relationshipType
 * @returns {array} Return array of all the targets belongs to entity relationships for given relationshipTypeId
 */
export function getRelationshipTargetByTypeId(entity, relationshipTypeId: number) {
    let targets = [];
    if (Array.isArray(entity.relationships)) {
        targets = entity.relationships
            .filter(
                (relation) => relation.typeId === relationshipTypeId
            )
            .map((relation) => {
                const {target} = relation;
                return target;
            });
    }
    return targets;
}

/**
 * Get an array of works contained in an edition, along with the authorAlias of those works
 *
 * @param {object} authorsData - an object which contains the authorAlias and authorBBID with workBBIDs as keys
 * @param {array} works - the array containing all the works in an edition
 * @returns {array} - return the works array after adding authorsData to each work in the array
 */
export function addAuthorsDataToWorks(authorsData, works) {
    works.map((work) => {
        if (authorsData[work.bbid]) {
            work.authorsData = authorsData[work.bbid];
        }
        else {
            work.authorsData = [];
        }
        return work;
    });
    return works;
}

/**
 * Get an array of all target BBIDs from relationships of an entity belongs to given relationshipTypeId
 *
 * @param {object} entity - an entity with all relationships
 * @param {number} relationshipTypeId - typeId of spacific relationshipType
 * @returns {array} Return array of all the targetBBIDs belongs to entity relationships for given relationshipTypeId
 */
export function getRelationshipTargetBBIDByTypeId(entity, relationshipTypeId: number) {
    let targets = [];
    if (Array.isArray(entity.relationships)) {
        targets = entity.relationships
            .filter(
                (relation) => relation.typeId === relationshipTypeId
            )
            .map((relation) => {
                const {target} = relation;
                return target.bbid;
            });
    }
    return targets;
}

/**
 * Get an array of all sources from relationships of an entity belongs to given relationshipTypeId
 *
 * @param {object} entity - main entity
 * @param {number} relationshipTypeId - typeId of spacific relationshipType
 * @returns {array} Return array of all the sources belongs to entity relationships for given relationshipTypeId
 */
export function getRelationshipSourceByTypeId(entity, relationshipTypeId: number) {
    let sources = [];
    if (Array.isArray(entity.relationships)) {
        sources = entity.relationships
            .filter(
                (relation) => relation.typeId === relationshipTypeId
            )
            .map((relation) => {
                const {source} = relation;
                return source;
            });
    }
    return sources;
}

export const deletedEntityMessage = (
    <p>
        This entity has been deleted by an editor.
        This is most likely because it was added accidentally or incorrectly.
        <br/>The edit history has been preserved, and you can see the revisions by clicking the history button below.
        <br/>If you’re sure this entity should still exist, you will be able to
        restore it to a previous revision in a future version of BookBrainz, but that’s not quite ready yet.
    </p>
);