cspace-ui
Version:
CollectionSpace user interface for browsers
392 lines (344 loc) • 10.6 kB
JSX
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;