import * as React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage, FormattedRelative } from 'react-intl'; import throttle from 'lodash/throttle'; import isEmpty from 'lodash/isEmpty'; import classnames from 'classnames'; import Button from '../../components/button'; import Link from '../../components/link/LinkBase'; import PresenceAvatar from './PresenceAvatar'; import { determineInteractionMessage } from './utils/presenceUtils'; import messages from './messages'; class PresenceDropdown extends React.Component { static propTypes = { collaborators: PropTypes.arrayOf( PropTypes.shape({ /** Url to avatar image. If passed in, component will render the avatar image instead of the initials */ avatarUrl: PropTypes.string, /** Users id */ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), isActive: PropTypes.bool, /** Unix timestamp of when the user last interacted with the document */ interactedAt: PropTypes.number, /** The type of interaction by the user */ interactionType: PropTypes.string, /** User's full name */ name: PropTypes.string.isRequired, /** Custom Profile URL */ profileUrl: PropTypes.string, }), ).isRequired, /* Get Link button callback. also controls visibility of button */ getLinkCallback: PropTypes.func, /* Show Invite button callback. also controls visibility of button */ inviteCallback: PropTypes.func, /* Callback for Dropdown onScroll event */ onScroll: PropTypes.func, }; state = { isScrollableAbove: false, isScrollableBelow: false, }; componentDidMount() { const overflow = this.calculateOverflow(this.elDropdownList); // eslint-disable-next-line react/no-did-mount-set-state this.setState(overflow); } componentDidUpdate() { const overflow = this.calculateOverflow(this.elDropdownList); /** * recalculate overflow when dropdown is visible and new collabs are added * This will not go into an infinite loop because we check for changes in local component state */ if ( overflow.isScrollableAbove !== this.state.isScrollableAbove || overflow.isScrollableBelow !== this.state.isScrollableBelow ) { // eslint-disable-next-line react/no-did-update-set-state this.setState(overflow); } } calculateOverflow = elem => { const isScrollableAbove = elem.scrollTop > 0; const isScrollableBelow = elem.scrollTop < elem.scrollHeight - elem.clientHeight; return { isScrollableAbove, isScrollableBelow, }; }; handleScroll = event => { const { onScroll } = this.props; if (this.elDropdownList) { this.setState(this.calculateOverflow(this.elDropdownList)); if (onScroll) { onScroll(event); } } }; throttledHandleScroll = throttle(this.handleScroll, 50, { leading: true, trailing: true, }); renderTitle = () => (
); renderTimestampMessage = (interactedAt, interactionType) => { const lastActionMessage = determineInteractionMessage(interactionType, interactedAt); if (lastActionMessage) { return ( , }} /> ); } return null; }; renderCollabList = () => { const { collaborators } = this.props; return collaborators.map(collaborator => { const { avatarUrl, id, isActive, interactedAt, interactionType, name, profileUrl } = collaborator; return (
{isEmpty(profileUrl) ? ( {name} ) : ( {name} )}
{isActive ? ( ) : ( this.renderTimestampMessage(interactedAt, interactionType) )}
); }); }; renderActions = () => { const { getLinkCallback, inviteCallback } = this.props; return ( (getLinkCallback || inviteCallback) && (
{getLinkCallback && ( )} {inviteCallback && ( )}
) ); }; render() { const { isScrollableAbove, isScrollableBelow } = this.state; const { getLinkCallback, inviteCallback } = this.props; const buttonsPresent = getLinkCallback || inviteCallback; const dropdownListClasses = classnames('presence-dropdown-list', { 'dropshadow-list': !buttonsPresent, 'dropshadow-list-with-buttons': buttonsPresent, 'is-scrollable-above': isScrollableAbove, 'is-scrollable-below': isScrollableBelow, }); const title = this.renderTitle(); const collabList = this.renderCollabList(); const actions = this.renderActions(); return (
{title}
{ this.elDropdownList = list; }} className={dropdownListClasses} onScroll={this.throttledHandleScroll} > {collabList}
{actions}
); } } export default PresenceDropdown;