UNPKG

cspace-ui

Version:
392 lines (344 loc) 10.6 kB
import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { defineMessages, FormattedMessage, intlShape } from 'react-intl'; import Immutable from 'immutable'; import get from 'lodash/get'; import { Modal } from 'cspace-layout'; import { components as inputComponents } from 'cspace-input'; import tableRowStyles from 'cspace-layout/styles/cspace-layout/TableRow.css'; import CancelButton from '../navigation/CancelButton'; import searchTableStyles from '../../../styles/cspace-ui/SearchTable.css'; import styles from '../../../styles/cspace-ui/PinnedQueriesModal.css'; const { MiniButton } = inputComponents; const messages = defineMessages({ title: { id: 'pinnedQueriesModal.title', description: 'Title of the pinned queries modal.', defaultMessage: 'Pinned Queries', }, empty: { id: 'pinnedQueriesModal.empty', description: 'The message shown in the pinned queries modal when there are no pinned queries.', defaultMessage: 'No pinned queries.', }, nameColumn: { id: 'pinnedQueriesModal.column.name', description: 'Label of the name column in the pinned queries modal.', defaultMessage: 'Name', }, typeColumn: { id: 'pinnedQueriesModal.column.type', description: 'Label of the record type column in the pinned queries modal.', defaultMessage: 'Type', }, descriptionColumn: { id: 'pinnedQueriesModal.column.description', description: 'Label of the description column in the pinned queries modal.', defaultMessage: 'Description', }, delete: { id: 'pinnedQueriesModal.delete', description: 'Label of the delete button for a pinned query in the pinned queries modal.', defaultMessage: 'Delete', }, confirmDelete: { id: 'pinnedQueriesModal.confirmDelete', description: 'The prompt shown to confirm deletion of a pinned query in the pinned queries modal.', defaultMessage: 'Delete?', }, confirm: { id: 'pinnedQueriesModal.confirm', description: 'Label of the button confirming deletion of a pinned query in the pinned queries modal.', defaultMessage: 'Confirm', }, cancel: { id: 'pinnedQueriesModal.cancel', description: 'Label of the button canceling deletion of a pinned query in the pinned queries modal.', defaultMessage: 'Cancel', }, close: { id: 'pinnedQueriesModal.close', description: 'Label of the close button in the pinned queries modal.', defaultMessage: 'Close', }, execute: { id: 'pinnedQueriesModal.execute', description: 'The tooltip shown when hovering a pinned query row that can be loaded.', defaultMessage: 'Click to load', }, actionsColumn: { id: 'pinnedQueriesModal.column.actions', description: 'The accessible label of the (visually empty) actions column header in the pinned queries modal.', defaultMessage: 'Actions', }, deviceNote: { id: 'pinnedQueriesModal.deviceNote', description: 'The note shown in the pinned queries modal explaining that pinned queries are stored per device and browser.', defaultMessage: 'Currently, pinned queries are available from the same device and browser combination. Please see the {userManualLink} for additional information.', }, userManual: { id: 'pinnedQueriesModal.userManual', description: 'The text of the user manual link in the pinned queries modal.', defaultMessage: 'User Manual', }, userManualUrl: { id: 'pinnedQueriesModal.userManualUrl', description: 'The URL of the user manual page describing Pinned Queries.', defaultMessage: 'https://collectionspace.atlassian.net/wiki/spaces/CUD/pages/4613242886/Pinned+Queries', }, }); const propTypes = { config: PropTypes.shape({ recordTypes: PropTypes.object, }), isOpen: PropTypes.bool, pinnedQueries: PropTypes.instanceOf(Immutable.List), deleteQuery: PropTypes.func, onLoadQuery: PropTypes.func, onCloseButtonClick: PropTypes.func, }; const defaultProps = { pinnedQueries: Immutable.List(), }; const contextTypes = { intl: intlShape, }; const renderEmpty = () => ( <div className={styles.empty}> <FormattedMessage {...messages.empty} /> </div> ); export default class PinnedQueriesModal extends Component { constructor(props) { super(props); this.renderButtonBar = this.renderButtonBar.bind(this); this.state = { confirmDeleteId: null, }; } // eslint-disable-next-line camelcase UNSAFE_componentWillReceiveProps(nextProps) { const { isOpen, } = this.props; const { isOpen: nextIsOpen, } = nextProps; if (!isOpen && nextIsOpen) { this.setState({ confirmDeleteId: null, }); } } handleConfirmDeleteButtonClick(id) { const { deleteQuery, } = this.props; if (deleteQuery) { deleteQuery(id); } this.setState({ confirmDeleteId: null, }); } handleLoadQuery(query) { const { onLoadQuery, } = this.props; if (onLoadQuery && this.isRecordTypeKnown(query)) { onLoadQuery(query); } } handleRowKeyDown(event, query) { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); this.handleLoadQuery(query); } } isRecordTypeKnown(query) { const { config, } = this.props; return !!get(config, ['recordTypes', query.get('recordType')]); } formatRecordTypeLabel(recordType) { const { config, } = this.props; const { intl, } = this.context; const collectionNameMessage = get( config, ['recordTypes', recordType, 'messages', 'record', 'collectionName'], ); if (collectionNameMessage) { return intl.formatMessage(collectionNameMessage); } return recordType; } renderActionsCell(query) { const { confirmDeleteId, } = this.state; const { intl, } = this.context; const id = query.get('id'); if (confirmDeleteId === id) { return ( <div className={styles.actions}> <FormattedMessage {...messages.confirmDelete} /> <MiniButton aria-label={intl.formatMessage(messages.confirm)} name="confirmDeleteQuery" title={intl.formatMessage(messages.confirm)} onClick={(event) => { event.stopPropagation(); this.handleConfirmDeleteButtonClick(id); }} > ✓ </MiniButton> <MiniButton aria-label={intl.formatMessage(messages.cancel)} name="cancelDeleteQuery" title={intl.formatMessage(messages.cancel)} onClick={(event) => { event.stopPropagation(); this.setState({ confirmDeleteId: null }); }} > ✕ </MiniButton> </div> ); } return ( <div className={styles.actions}> <MiniButton aria-label={intl.formatMessage(messages.delete)} name="deleteQuery" title={intl.formatMessage(messages.delete)} onClick={(event) => { event.stopPropagation(); this.setState({ confirmDeleteId: id }); }} > − </MiniButton> </div> ); } renderQueryRow(query, index) { const { intl, } = this.context; const executable = this.isRecordTypeKnown(query); const classes = [ index % 2 === 0 ? searchTableStyles.even : searchTableStyles.odd, executable ? tableRowStyles.common : null, executable ? styles.executable : null, ].filter(Boolean).join(' '); /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ /* eslint-disable jsx-a11y/no-noninteractive-tabindex */ return ( <tr className={classes} key={query.get('id')} tabIndex={0} title={executable ? intl.formatMessage(messages.execute) : undefined} onClick={() => this.handleLoadQuery(query)} onKeyDown={(event) => this.handleRowKeyDown(event, query)} > <td>{query.get('name')}</td> <td>{this.formatRecordTypeLabel(query.get('recordType'))}</td> <td>{query.get('description')}</td> <td>{this.renderActionsCell(query)}</td> </tr> ); /* eslint-enable */ } renderQueryTable() { const { pinnedQueries, } = this.props; const { intl, } = this.context; if (pinnedQueries.isEmpty()) { return renderEmpty(); } return ( <div className={`${searchTableStyles.results} ${styles.tableContainer}`}> <table className={styles.queryTable}> <thead> <tr> <th scope="col">{intl.formatMessage(messages.nameColumn)}</th> <th scope="col">{intl.formatMessage(messages.typeColumn)}</th> <th scope="col">{intl.formatMessage(messages.descriptionColumn)}</th> <th scope="col" aria-label={intl.formatMessage(messages.actionsColumn)} /> </tr> </thead> <tbody> {pinnedQueries.map((query, index) => this.renderQueryRow(query, index)).toArray()} </tbody> </table> </div> ); } renderDeviceNote() { const { intl, } = this.context; return ( <p className={styles.note}> <FormattedMessage {...messages.deviceNote} values={{ userManualLink: ( <a href={intl.formatMessage(messages.userManualUrl)} rel="noreferrer" target="_blank"> <FormattedMessage {...messages.userManual} /> </a> ), }} /> </p> ); } renderButtonBar() { const { onCloseButtonClick, } = this.props; return ( <div> <CancelButton label={<FormattedMessage {...messages.close} />} onClick={onCloseButtonClick} /> </div> ); } render() { const { isOpen, onCloseButtonClick, } = this.props; return ( <Modal className={styles.common} isOpen={isOpen} title={<h1><FormattedMessage {...messages.title} /></h1>} closeButtonClassName="material-icons" closeButtonLabel="close" renderButtonBar={this.renderButtonBar} onCloseButtonClick={onCloseButtonClick} > {this.renderQueryTable()} {this.renderDeviceNote()} </Modal> ); } } PinnedQueriesModal.propTypes = propTypes; PinnedQueriesModal.defaultProps = defaultProps; PinnedQueriesModal.contextTypes = contextTypes;