mirror of
https://github.com/ansible/awx.git
synced 2026-03-06 11:11:07 -03:30
Refactor kebab modal tracking logic in delete/cancel buttons
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React, { Fragment, useEffect, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
@@ -51,12 +51,6 @@ function DataListToolbar({
|
|||||||
setKebabIsOpen(false);
|
setKebabIsOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!kebabModalIsOpen) {
|
|
||||||
setKebabIsOpen(false);
|
|
||||||
}
|
|
||||||
}, [kebabModalIsOpen]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Toolbar
|
<Toolbar
|
||||||
id={`${qsConfig.namespace}-list-toolbar`}
|
id={`${qsConfig.namespace}-list-toolbar`}
|
||||||
@@ -98,7 +92,7 @@ function DataListToolbar({
|
|||||||
</ToolbarToggleGroup>
|
</ToolbarToggleGroup>
|
||||||
{showExpandCollapse && (
|
{showExpandCollapse && (
|
||||||
<ToolbarGroup>
|
<ToolbarGroup>
|
||||||
<Fragment>
|
<>
|
||||||
<ToolbarItem>
|
<ToolbarItem>
|
||||||
<ExpandCollapse
|
<ExpandCollapse
|
||||||
isCompact={isCompact}
|
isCompact={isCompact}
|
||||||
@@ -106,7 +100,7 @@ function DataListToolbar({
|
|||||||
onExpand={onExpand}
|
onExpand={onExpand}
|
||||||
/>
|
/>
|
||||||
</ToolbarItem>
|
</ToolbarItem>
|
||||||
</Fragment>
|
</>
|
||||||
</ToolbarGroup>
|
</ToolbarGroup>
|
||||||
)}
|
)}
|
||||||
{advancedSearchShown && (
|
{advancedSearchShown && (
|
||||||
@@ -114,7 +108,12 @@ function DataListToolbar({
|
|||||||
<KebabifiedProvider
|
<KebabifiedProvider
|
||||||
value={{
|
value={{
|
||||||
isKebabified: true,
|
isKebabified: true,
|
||||||
onKebabModalChange: isOpen => setKebabModalIsOpen(isOpen),
|
onKebabModalChange: isOpen => {
|
||||||
|
if (kebabIsOpen && kebabModalIsOpen && !isOpen) {
|
||||||
|
setKebabIsOpen(false);
|
||||||
|
}
|
||||||
|
setKebabModalIsOpen(isOpen);
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
@@ -127,7 +126,7 @@ function DataListToolbar({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
isOpen={kebabIsOpen}
|
isOpen={kebabIsOpen || kebabModalIsOpen}
|
||||||
isPlain
|
isPlain
|
||||||
dropdownItems={additionalControls}
|
dropdownItems={additionalControls}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { arrayOf, func } from 'prop-types';
|
import { arrayOf, func } from 'prop-types';
|
||||||
import { Button, DropdownItem, Tooltip } from '@patternfly/react-core';
|
import { Button, DropdownItem, Tooltip } from '@patternfly/react-core';
|
||||||
import { Kebabified } from '../../contexts/Kebabified';
|
import { KebabifiedContext } from '../../contexts/Kebabified';
|
||||||
import AlertModal from '../AlertModal';
|
import AlertModal from '../AlertModal';
|
||||||
import { Job } from '../../types';
|
import { Job } from '../../types';
|
||||||
|
|
||||||
@@ -12,13 +12,21 @@ function cannotCancel(job) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function JobListCancelButton({ i18n, jobsToCancel, onCancel }) {
|
function JobListCancelButton({ i18n, jobsToCancel, onCancel }) {
|
||||||
|
const { isKebabified, onKebabModalChange } = useContext(KebabifiedContext);
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const numJobsToCancel = jobsToCancel.length;
|
const numJobsToCancel = jobsToCancel.length;
|
||||||
const zeroOrOneJobSelected = numJobsToCancel < 2;
|
const zeroOrOneJobSelected = numJobsToCancel < 2;
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancelJob = () => {
|
||||||
onCancel();
|
onCancel();
|
||||||
setIsModalOpen(false);
|
toggleModal();
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleModal = () => {
|
||||||
|
if (isKebabified) {
|
||||||
|
onKebabModalChange(!isModalOpen);
|
||||||
|
}
|
||||||
|
setIsModalOpen(!isModalOpen);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderTooltip = () => {
|
const renderTooltip = () => {
|
||||||
@@ -61,96 +69,74 @@ function JobListCancelButton({ i18n, jobsToCancel, onCancel }) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Kebabified>
|
<>
|
||||||
{({ isKebabified, onKebabModalChange }) => (
|
{isKebabified ? (
|
||||||
<>
|
<DropdownItem
|
||||||
{isKebabified ? (
|
key="cancel-job"
|
||||||
<DropdownItem
|
isDisabled={isDisabled}
|
||||||
key="cancel-job"
|
component="button"
|
||||||
|
onClick={toggleModal}
|
||||||
|
>
|
||||||
|
{cancelJobText}
|
||||||
|
</DropdownItem>
|
||||||
|
) : (
|
||||||
|
<Tooltip content={renderTooltip()} position="top">
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
aria-label={cancelJobText}
|
||||||
|
onClick={toggleModal}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
component="button"
|
|
||||||
onClick={() => {
|
|
||||||
onKebabModalChange(true);
|
|
||||||
setIsModalOpen(true);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{cancelJobText}
|
{cancelJobText}
|
||||||
</DropdownItem>
|
</Button>
|
||||||
) : (
|
</div>
|
||||||
<Tooltip content={renderTooltip()} position="top">
|
</Tooltip>
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
aria-label={cancelJobText}
|
|
||||||
onClick={() => setIsModalOpen(true)}
|
|
||||||
isDisabled={isDisabled}
|
|
||||||
>
|
|
||||||
{cancelJobText}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
{isModalOpen && (
|
|
||||||
<AlertModal
|
|
||||||
variant="danger"
|
|
||||||
title={cancelJobText}
|
|
||||||
isOpen={isModalOpen}
|
|
||||||
onClose={() => {
|
|
||||||
if (isKebabified) {
|
|
||||||
onKebabModalChange(false);
|
|
||||||
}
|
|
||||||
setIsModalOpen(false);
|
|
||||||
}}
|
|
||||||
actions={[
|
|
||||||
<Button
|
|
||||||
id="cancel-job-confirm-button"
|
|
||||||
key="delete"
|
|
||||||
variant="danger"
|
|
||||||
aria-label={cancelJobText}
|
|
||||||
onClick={() => {
|
|
||||||
if (isKebabified) {
|
|
||||||
onKebabModalChange(false);
|
|
||||||
}
|
|
||||||
handleCancel();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{cancelJobText}
|
|
||||||
</Button>,
|
|
||||||
<Button
|
|
||||||
id="cancel-job-return-button"
|
|
||||||
key="cancel"
|
|
||||||
variant="secondary"
|
|
||||||
aria-label={i18n._(t`Return`)}
|
|
||||||
onClick={() => {
|
|
||||||
if (isKebabified) {
|
|
||||||
onKebabModalChange(false);
|
|
||||||
}
|
|
||||||
setIsModalOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{i18n._(t`Return`)}
|
|
||||||
</Button>,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
{i18n._(
|
|
||||||
'{numJobsToCancel, plural, one {This action will cancel the following job:} other {This action will cancel the following jobs:}}',
|
|
||||||
{
|
|
||||||
numJobsToCancel,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{jobsToCancel.map(job => (
|
|
||||||
<span key={job.id}>
|
|
||||||
<strong>{job.name}</strong>
|
|
||||||
<br />
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</AlertModal>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</Kebabified>
|
{isModalOpen && (
|
||||||
|
<AlertModal
|
||||||
|
variant="danger"
|
||||||
|
title={cancelJobText}
|
||||||
|
isOpen={isModalOpen}
|
||||||
|
onClose={toggleModal}
|
||||||
|
actions={[
|
||||||
|
<Button
|
||||||
|
id="cancel-job-confirm-button"
|
||||||
|
key="delete"
|
||||||
|
variant="danger"
|
||||||
|
aria-label={cancelJobText}
|
||||||
|
onClick={handleCancelJob}
|
||||||
|
>
|
||||||
|
{cancelJobText}
|
||||||
|
</Button>,
|
||||||
|
<Button
|
||||||
|
id="cancel-job-return-button"
|
||||||
|
key="cancel"
|
||||||
|
variant="secondary"
|
||||||
|
aria-label={i18n._(t`Return`)}
|
||||||
|
onClick={toggleModal}
|
||||||
|
>
|
||||||
|
{i18n._(t`Return`)}
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{i18n._(
|
||||||
|
'{numJobsToCancel, plural, one {This action will cancel the following job:} other {This action will cancel the following jobs:}}',
|
||||||
|
{
|
||||||
|
numJobsToCancel,
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{jobsToCancel.map(job => (
|
||||||
|
<span key={job.id}>
|
||||||
|
<strong>{job.name}</strong>
|
||||||
|
<br />
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</AlertModal>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { useContext, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
func,
|
func,
|
||||||
bool,
|
bool,
|
||||||
@@ -12,7 +12,7 @@ import { Button, DropdownItem, Tooltip } from '@patternfly/react-core';
|
|||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import AlertModal from '../AlertModal';
|
import AlertModal from '../AlertModal';
|
||||||
import { Kebabified } from '../../contexts/Kebabified';
|
import { KebabifiedContext } from '../../contexts/Kebabified';
|
||||||
|
|
||||||
const requireNameOrUsername = props => {
|
const requireNameOrUsername = props => {
|
||||||
const { name, username } = props;
|
const { name, username } = props;
|
||||||
@@ -59,53 +59,29 @@ function cannotDelete(item) {
|
|||||||
return !item.summary_fields.user_capabilities.delete;
|
return !item.summary_fields.user_capabilities.delete;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ToolbarDeleteButton extends React.Component {
|
function ToolbarDeleteButton({
|
||||||
static propTypes = {
|
itemsToDelete,
|
||||||
onDelete: func.isRequired,
|
pluralizedItemName,
|
||||||
itemsToDelete: arrayOf(ItemToDelete).isRequired,
|
errorMessage,
|
||||||
pluralizedItemName: string,
|
onDelete,
|
||||||
errorMessage: string,
|
i18n,
|
||||||
};
|
}) {
|
||||||
|
const { isKebabified, onKebabModalChange } = useContext(KebabifiedContext);
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
|
||||||
static defaultProps = {
|
const handleDelete = () => {
|
||||||
pluralizedItemName: 'Items',
|
|
||||||
errorMessage: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
isModalOpen: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.handleConfirmDelete = this.handleConfirmDelete.bind(this);
|
|
||||||
this.handleCancelDelete = this.handleCancelDelete.bind(this);
|
|
||||||
this.handleDelete = this.handleDelete.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleConfirmDelete() {
|
|
||||||
this.setState({ isModalOpen: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCancelDelete() {
|
|
||||||
this.setState({ isModalOpen: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDelete() {
|
|
||||||
const { onDelete } = this.props;
|
|
||||||
onDelete();
|
onDelete();
|
||||||
this.setState({ isModalOpen: false });
|
toggleModal();
|
||||||
}
|
};
|
||||||
|
|
||||||
renderTooltip() {
|
const toggleModal = () => {
|
||||||
const {
|
if (isKebabified) {
|
||||||
itemsToDelete,
|
onKebabModalChange(!isModalOpen);
|
||||||
pluralizedItemName,
|
}
|
||||||
errorMessage,
|
setIsModalOpen(!isModalOpen);
|
||||||
i18n,
|
};
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
|
const renderTooltip = () => {
|
||||||
const itemsUnableToDelete = itemsToDelete
|
const itemsUnableToDelete = itemsToDelete
|
||||||
.filter(cannotDelete)
|
.filter(cannotDelete)
|
||||||
.map(item => item.name)
|
.map(item => item.name)
|
||||||
@@ -125,103 +101,89 @@ class ToolbarDeleteButton extends React.Component {
|
|||||||
return i18n._(t`Delete`);
|
return i18n._(t`Delete`);
|
||||||
}
|
}
|
||||||
return i18n._(t`Select a row to delete`);
|
return i18n._(t`Select a row to delete`);
|
||||||
}
|
};
|
||||||
|
|
||||||
render() {
|
const modalTitle = i18n._(t`Delete ${pluralizedItemName}?`);
|
||||||
const { itemsToDelete, pluralizedItemName, i18n } = this.props;
|
|
||||||
const { isModalOpen } = this.state;
|
|
||||||
const modalTitle = i18n._(t`Delete ${pluralizedItemName}?`);
|
|
||||||
|
|
||||||
const isDisabled =
|
const isDisabled =
|
||||||
itemsToDelete.length === 0 || itemsToDelete.some(cannotDelete);
|
itemsToDelete.length === 0 || itemsToDelete.some(cannotDelete);
|
||||||
|
|
||||||
// NOTE: Once PF supports tooltips on disabled elements,
|
// NOTE: Once PF supports tooltips on disabled elements,
|
||||||
// we can delete the extra <div> around the <DeleteButton> below.
|
// we can delete the extra <div> around the <DeleteButton> below.
|
||||||
// See: https://github.com/patternfly/patternfly-react/issues/1894
|
// See: https://github.com/patternfly/patternfly-react/issues/1894
|
||||||
return (
|
return (
|
||||||
<Kebabified>
|
<>
|
||||||
{({ isKebabified, onKebabModalChange }) => (
|
{isKebabified ? (
|
||||||
<Fragment>
|
<DropdownItem
|
||||||
{isKebabified ? (
|
key="add"
|
||||||
<DropdownItem
|
isDisabled={isDisabled}
|
||||||
key="add"
|
component="button"
|
||||||
isDisabled={isDisabled}
|
onClick={toggleModal}
|
||||||
component="button"
|
>
|
||||||
onClick={() => {
|
{i18n._(t`Delete`)}
|
||||||
onKebabModalChange(true);
|
</DropdownItem>
|
||||||
this.handleConfirmDelete();
|
) : (
|
||||||
}}
|
<Tooltip content={renderTooltip()} position="top">
|
||||||
>
|
<div>
|
||||||
{i18n._(t`Delete`)}
|
<Button
|
||||||
</DropdownItem>
|
variant="secondary"
|
||||||
) : (
|
aria-label={i18n._(t`Delete`)}
|
||||||
<Tooltip content={this.renderTooltip()} position="top">
|
onClick={toggleModal}
|
||||||
<div>
|
isDisabled={isDisabled}
|
||||||
<Button
|
>
|
||||||
variant="secondary"
|
{i18n._(t`Delete`)}
|
||||||
aria-label={i18n._(t`Delete`)}
|
</Button>
|
||||||
onClick={this.handleConfirmDelete}
|
</div>
|
||||||
isDisabled={isDisabled}
|
</Tooltip>
|
||||||
>
|
)}
|
||||||
{i18n._(t`Delete`)}
|
{isModalOpen && (
|
||||||
</Button>
|
<AlertModal
|
||||||
</div>
|
variant="danger"
|
||||||
</Tooltip>
|
title={modalTitle}
|
||||||
)}
|
isOpen={isModalOpen}
|
||||||
{isModalOpen && (
|
onClose={toggleModal}
|
||||||
<AlertModal
|
actions={[
|
||||||
variant="danger"
|
<Button
|
||||||
title={modalTitle}
|
key="delete"
|
||||||
isOpen={isModalOpen}
|
variant="danger"
|
||||||
onClose={() => {
|
aria-label={i18n._(t`confirm delete`)}
|
||||||
if (isKebabified) {
|
onClick={handleDelete}
|
||||||
onKebabModalChange(false);
|
>
|
||||||
}
|
{i18n._(t`Delete`)}
|
||||||
this.handleCancelDelete();
|
</Button>,
|
||||||
}}
|
<Button
|
||||||
actions={[
|
key="cancel"
|
||||||
<Button
|
variant="secondary"
|
||||||
key="delete"
|
aria-label={i18n._(t`cancel delete`)}
|
||||||
variant="danger"
|
onClick={toggleModal}
|
||||||
aria-label={i18n._(t`confirm delete`)}
|
>
|
||||||
onClick={() => {
|
{i18n._(t`Cancel`)}
|
||||||
if (isKebabified) {
|
</Button>,
|
||||||
onKebabModalChange(false);
|
]}
|
||||||
}
|
>
|
||||||
this.handleDelete();
|
<div>{i18n._(t`This action will delete the following:`)}</div>
|
||||||
}}
|
{itemsToDelete.map(item => (
|
||||||
>
|
<span key={item.id}>
|
||||||
{i18n._(t`Delete`)}
|
<strong>{item.name || item.username}</strong>
|
||||||
</Button>,
|
<br />
|
||||||
<Button
|
</span>
|
||||||
key="cancel"
|
))}
|
||||||
variant="secondary"
|
</AlertModal>
|
||||||
aria-label={i18n._(t`cancel delete`)}
|
)}
|
||||||
onClick={() => {
|
</>
|
||||||
if (isKebabified) {
|
);
|
||||||
onKebabModalChange(false);
|
|
||||||
}
|
|
||||||
this.handleCancelDelete();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{i18n._(t`Cancel`)}
|
|
||||||
</Button>,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<div>{i18n._(t`This action will delete the following:`)}</div>
|
|
||||||
{itemsToDelete.map(item => (
|
|
||||||
<span key={item.id}>
|
|
||||||
<strong>{item.name || item.username}</strong>
|
|
||||||
<br />
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</AlertModal>
|
|
||||||
)}
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
</Kebabified>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ToolbarDeleteButton.propTypes = {
|
||||||
|
onDelete: func.isRequired,
|
||||||
|
itemsToDelete: arrayOf(ItemToDelete).isRequired,
|
||||||
|
pluralizedItemName: string,
|
||||||
|
errorMessage: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
ToolbarDeleteButton.defaultProps = {
|
||||||
|
pluralizedItemName: 'Items',
|
||||||
|
errorMessage: '',
|
||||||
|
};
|
||||||
|
|
||||||
export default withI18n()(ToolbarDeleteButton);
|
export default withI18n()(ToolbarDeleteButton);
|
||||||
|
|||||||
Reference in New Issue
Block a user