foreman_ansible 17.0.8 → 18.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b6cd8765eae0204e9dec617d7d3e267abfffc55d402d87026812deadcb720049
4
- data.tar.gz: 47d5ba9efc868bcf4164dc28106a67ee42010a74017bf70551e00b856a696b33
3
+ metadata.gz: '087d3ebdcf70f8891bf6de236413f46c971950589ecc7a9b32aa781b747f0d64'
4
+ data.tar.gz: 11d10eeff06402af8baaeda250d021c44db0fc21bd86450a1123f47d19083353
5
5
  SHA512:
6
- metadata.gz: 3aac0662ea3b05bcdae57b8aa80a5bc51b7008b775686c39f024e24c1c294ea51404965e5bf699b888a7a436862255162b9085e8fdd706e5297177e732ad13a9
7
- data.tar.gz: 4feef7c8a25d8688d24d5e05826d994050bfbfbc6407480ccd773eaae862fdde5a30ba899f0c9e85e51c5cd3a788313cf6802d39be1cdd225e5391247de3de3c
6
+ metadata.gz: 550e5075d68631d646f73b56b370d7816dc2f59a784cc1d828c04b453fde61871d14293aba62fbdca70aa063eec4d74cbe77d5b8d2b1eedd9bafd12c0f24d610
7
+ data.tar.gz: 4b459dcf5991a72248fe82f8bc348a28f10593834fb5c3b352fbebc55a22020efee2af4fe593cd2a67d161f7b197ef4fd0e96682b0e0f5a5de93184a9f0149da
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Foreman::Plugin.register :foreman_ansible do
4
- requires_foreman '>= 3.16.0'
4
+ requires_foreman '>= 5.0'
5
5
  register_gettext
6
6
 
7
7
  settings do
@@ -4,5 +4,5 @@
4
4
  # This way other parts of Foreman can just call ForemanAnsible::VERSION
5
5
  # and detect what version the plugin is running.
6
6
  module ForemanAnsible
7
- VERSION = '17.0.8'
7
+ VERSION = '18.0.0'
8
8
  end
@@ -9,6 +9,8 @@ import {
9
9
  ANSIBLE_ROLES_INDEX,
10
10
  } from './AnsibleRolesAndVariablesConstants';
11
11
 
12
+ const REDIRECT_DELAY_MS = 500;
13
+
12
14
  export const foremanUrl = path => `${window.URL_PREFIX}${path}`;
13
15
 
14
16
  export const onSubmit = (rows, proxy) => dispatch => {
@@ -19,7 +21,10 @@ export const onSubmit = (rows, proxy) => dispatch => {
19
21
  url: ANSIBLE_ROLE_CONFIRM_IMPORT_PATH,
20
22
  params: { changed: params, proxy },
21
23
  handleSuccess: () => {
22
- setTimeout(() => dispatch(push(ANSIBLE_ROLES_INDEX)), 500);
24
+ setTimeout(
25
+ () => dispatch(push(ANSIBLE_ROLES_INDEX)),
26
+ REDIRECT_DELAY_MS
27
+ );
23
28
  },
24
29
  successToast: response => (
25
30
  <span>
@@ -6,19 +6,12 @@
6
6
  }
7
7
 
8
8
  .ansible-inherited-roles {
9
- --pf-v5-c-dual-list-selector__title-text--FontWeight: var(
10
- --pf-v5-global--FontWeight--bold
11
- );
12
- --pf-v5-c-dual-list-selector__header--MarginBottom: var(
13
- --pf-v5-global--spacer--sm
14
- );
15
-
16
- .pf-v5-c-dual-list-selector__header {
17
- margin-block-end: var(--pf-v5-c-dual-list-selector__header--MarginBottom);
18
- }
19
-
20
- .pf-v5-c-dual-list-selector__title-text {
21
- font-weight: var(--pf-v5-c-dual-list-selector__title-text--FontWeight);
9
+ .ansible-inherited-roles-title {
10
+ margin: 0 0 var(--pf-v5-global--spacer--sm);
11
+ font-family: var(--pf-v5-global--FontFamily--text);
12
+ font-size: var(--pf-v5-global--FontSize--sm);
13
+ font-weight: var(--pf-v5-global--FontWeight--bold);
14
+ line-height: var(--pf-v5-global--LineHeight--md);
22
15
  }
23
16
 
24
17
  ul {
@@ -34,6 +34,7 @@ const ansibleRoles = (state = initialState, action) => {
34
34
  return state.merge({ error: payload.error, loading: false });
35
35
  case ANSIBLE_ROLES_DUAL_LIST_CHANGE: {
36
36
  const { chosenNames } = payload;
37
+ const chosenNamesUnique = [...new Set(chosenNames)];
37
38
  const inherited = state.assignedRoles.filter(role =>
38
39
  state.inheritedRoleIds.includes(role.id)
39
40
  );
@@ -42,20 +43,24 @@ const ansibleRoles = (state = initialState, action) => {
42
43
  );
43
44
  const roleByName = name =>
44
45
  currentOwn.find(role => role.name === name) ||
45
- state.results.find(role => role.name === name) ||
46
- state.toDestroyRoles.find(role => role.name === name);
46
+ state.toDestroyRoles.find(role => role.name === name) ||
47
+ state.results.find(role => role.name === name);
47
48
 
48
49
  const removed = currentOwn.filter(
49
- role => !chosenNames.includes(role.name)
50
+ role => !chosenNamesUnique.includes(role.name)
50
51
  );
51
- const newOwn = chosenNames
52
+ const newOwn = chosenNamesUnique
52
53
  .map(name => roleByName(name))
53
- .filter(role => role && !state.inheritedRoleIds.includes(role.id));
54
+ .filter(role => role && !state.inheritedRoleIds.includes(role.id))
55
+ .map(role => ({ ...role, destroy: false }));
56
+
57
+ const isSameRole = (left, right) =>
58
+ left.name === right.name || String(left.id) === String(right.id);
54
59
 
55
60
  return state.merge({
56
61
  assignedRoles: inherited.concat(newOwn),
57
62
  toDestroyRoles: state.toDestroyRoles
58
- .filter(item => !newOwn.some(role => role.id === item.id))
63
+ .filter(item => !newOwn.some(role => isSameRole(role, item)))
59
64
  .concat(removed.map(role => ({ ...role, destroy: true }))),
60
65
  });
61
66
  }
@@ -72,4 +72,45 @@ describe('AnsibleRolesSwitcherReducer', () => {
72
72
  expect(state.inheritedRoleIds).toEqual([4]);
73
73
  expect(state.results).toEqual(ansibleRolesLong);
74
74
  });
75
+
76
+ it('restores removed roles without duplicating destroy entries', () => {
77
+ const ownRole = {
78
+ ...ansibleRolesLong[4],
79
+ host_ansible_role_id: 42,
80
+ };
81
+ const stateAfterRemoveAll = reducer(
82
+ successState.set('assignedRoles', [
83
+ ansibleRolesLong[3],
84
+ ownRole,
85
+ ansibleRolesLong[5],
86
+ ]),
87
+ {
88
+ type: ANSIBLE_ROLES_DUAL_LIST_CHANGE,
89
+ payload: { chosenNames: [] },
90
+ }
91
+ );
92
+
93
+ expect(stateAfterRemoveAll.toDestroyRoles).toEqual([
94
+ { ...ownRole, destroy: true },
95
+ { ...ansibleRolesLong[5], destroy: true },
96
+ ]);
97
+
98
+ const stateAfterReAdd = reducer(stateAfterRemoveAll, {
99
+ type: ANSIBLE_ROLES_DUAL_LIST_CHANGE,
100
+ payload: {
101
+ chosenNames: [ownRole.name, ansibleRolesLong[5].name],
102
+ },
103
+ });
104
+
105
+ expect(stateAfterReAdd.assignedRoles.map(role => role.name)).toEqual([
106
+ ansibleRolesLong[3].name,
107
+ ownRole.name,
108
+ ansibleRolesLong[5].name,
109
+ ]);
110
+ expect(stateAfterReAdd.assignedRoles[1]).toEqual({
111
+ ...ownRole,
112
+ destroy: false,
113
+ });
114
+ expect(stateAfterReAdd.toDestroyRoles).toEqual([]);
115
+ });
75
116
  });
@@ -1,10 +1,8 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { Tooltip } from '@patternfly/react-core';
3
+ import { Title, Tooltip } from '@patternfly/react-core';
4
4
  import { translate as __ } from 'foremanReact/common/I18n';
5
5
 
6
- import ListHeader from '../../DualList/ListHeader';
7
-
8
6
  const InheritedRolesList = ({ roles, resourceName }) => {
9
7
  if (roles.length === 0) {
10
8
  return null;
@@ -17,7 +15,14 @@ const InheritedRolesList = ({ roles, resourceName }) => {
17
15
 
18
16
  return (
19
17
  <div className="ansible-inherited-roles">
20
- <ListHeader title={__('Inherited Ansible Roles')} />
18
+ <Title
19
+ headingLevel="h2"
20
+ size="md"
21
+ className="ansible-inherited-roles-title"
22
+ ouiaId="inherited-ansible-roles-title"
23
+ >
24
+ {__('Inherited Ansible Roles')}
25
+ </Title>
21
26
  <ul>
22
27
  {roles.map(role => (
23
28
  <li key={role.id}>
@@ -0,0 +1,101 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import '@testing-library/jest-dom';
5
+
6
+ import DualList from './index';
7
+
8
+ const defaultProps = {
9
+ availableOptions: ['role.a', 'role.b'],
10
+ chosenOptions: ['role.c'],
11
+ onListChange: jest.fn(),
12
+ };
13
+
14
+ describe('DualList', () => {
15
+ beforeEach(() => {
16
+ jest.clearAllMocks();
17
+ });
18
+
19
+ it('renders available and chosen options', () => {
20
+ render(<DualList {...defaultProps} />);
21
+
22
+ expect(screen.getByText('Available Ansible roles')).toBeInTheDocument();
23
+ expect(screen.getByText('Assigned Ansible roles')).toBeInTheDocument();
24
+ expect(screen.getByText('role.a')).toBeInTheDocument();
25
+ expect(screen.getByText('role.b')).toBeInTheDocument();
26
+ expect(screen.getByText('role.c')).toBeInTheDocument();
27
+ });
28
+
29
+ it('moves selected available options to chosen', () => {
30
+ render(<DualList {...defaultProps} />);
31
+
32
+ fireEvent.click(screen.getByRole('option', { name: 'role.a' }));
33
+ userEvent.click(screen.getByRole('button', { name: 'Add selected' }));
34
+
35
+ expect(defaultProps.onListChange).toHaveBeenCalledWith(
36
+ ['role.b'],
37
+ ['role.c', 'role.a']
38
+ );
39
+ });
40
+
41
+ it('moves selected chosen options to available', () => {
42
+ render(<DualList {...defaultProps} />);
43
+
44
+ fireEvent.click(screen.getByRole('option', { name: 'role.c' }));
45
+ userEvent.click(screen.getByRole('button', { name: 'Remove selected' }));
46
+
47
+ expect(defaultProps.onListChange).toHaveBeenCalledWith(
48
+ ['role.a', 'role.b', 'role.c'],
49
+ []
50
+ );
51
+ });
52
+
53
+ it('moves all available options to chosen', () => {
54
+ render(<DualList {...defaultProps} />);
55
+
56
+ userEvent.click(screen.getByRole('button', { name: 'Add all' }));
57
+
58
+ expect(defaultProps.onListChange).toHaveBeenCalledWith(
59
+ [],
60
+ ['role.c', 'role.a', 'role.b']
61
+ );
62
+ });
63
+
64
+ it('moves all chosen options to available', () => {
65
+ render(<DualList {...defaultProps} />);
66
+
67
+ userEvent.click(screen.getByRole('button', { name: 'Remove all' }));
68
+
69
+ expect(defaultProps.onListChange).toHaveBeenCalledWith(
70
+ ['role.a', 'role.b', 'role.c'],
71
+ []
72
+ );
73
+ });
74
+
75
+ it('disables move controls when nothing is selected or lists are empty', () => {
76
+ render(
77
+ <DualList
78
+ availableOptions={[]}
79
+ chosenOptions={[]}
80
+ onListChange={jest.fn()}
81
+ />
82
+ );
83
+
84
+ expect(screen.getByRole('button', { name: 'Add selected' })).toBeDisabled();
85
+ expect(screen.getByRole('button', { name: 'Add all' })).toBeDisabled();
86
+ expect(screen.getByRole('button', { name: 'Remove all' })).toBeDisabled();
87
+ expect(
88
+ screen.getByRole('button', { name: 'Remove selected' })
89
+ ).toBeDisabled();
90
+ });
91
+
92
+ it('shows selection status for each pane', () => {
93
+ render(<DualList {...defaultProps} />);
94
+
95
+ fireEvent.click(screen.getByRole('option', { name: 'role.a' }));
96
+ fireEvent.click(screen.getByRole('option', { name: 'role.c' }));
97
+
98
+ expect(screen.getByText('1 of 2 items selected')).toBeInTheDocument();
99
+ expect(screen.getByText('1 of 1 items selected')).toBeInTheDocument();
100
+ });
101
+ });
@@ -1,103 +1,47 @@
1
- import React, { useState } from 'react';
1
+ import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
+ import OrderableDualListSelector from 'foremanReact/components/common/OrderableDualListSelector/OrderableDualListSelector';
3
4
  import { translate as __ } from 'foremanReact/common/I18n';
4
5
 
5
- import ListControls from './ListControls';
6
- import ListPane from './ListPane';
7
- import './DualList.scss';
8
-
9
- const DualList = props => {
10
- const defaultSelectState = { availableSelected: [], chosenSelected: [] };
11
- const [selectState, setSelectState] = useState(defaultSelectState);
12
-
13
- const onItemClick = stateName => itemName => (...args) => {
14
- if (isItemSelected(stateName)(itemName)) {
15
- setSelectState({
16
- ...selectState,
17
- [stateName]: selectState[stateName].filter(item => item !== itemName),
18
- });
19
- } else {
20
- setSelectState({
21
- ...selectState,
22
- [stateName]: [...selectState[stateName], itemName],
23
- });
24
- }
25
- };
26
-
27
- const isItemSelected = stateName => item =>
28
- selectState[stateName].includes(item);
29
-
30
- const onAddAll = () =>
31
- props.onListChange([], [...props.chosenOptions, ...props.availableOptions]);
32
- const onRemoveAll = () =>
33
- props.onListChange([...props.availableOptions, ...props.chosenOptions], []);
34
-
35
- const onAddSelected = () => {
36
- const [newAvailable, newChosen] = moveItems(
37
- props.availableOptions,
38
- props.chosenOptions,
39
- selectState.availableSelected
40
- );
41
- setSelectState(defaultSelectState);
42
- props.onListChange(newAvailable, newChosen);
43
- };
44
-
45
- const onRemoveSelected = () => {
46
- const [newChosen, newAvailable] = moveItems(
47
- props.chosenOptions,
48
- props.availableOptions,
49
- selectState.chosenSelected
50
- );
51
- setSelectState(defaultSelectState);
52
- props.onListChange(newAvailable, newChosen);
53
- };
54
-
55
- const moveItems = (removeFrom, addTo, selected) => {
56
- const newRemoveFrom = removeFrom.filter(item => !selected.includes(item));
57
- return [newRemoveFrom, [...addTo, ...selected]];
58
- };
59
-
60
- const onChosenSetOrder = items =>
61
- props.onListChange(props.availableOptions, items);
62
-
63
- return (
64
- <div className="pf-v5-c-dual-list-selector">
65
- <ListPane
66
- title={__('Available Ansible roles')}
67
- items={props.availableOptions}
68
- paneClass="pf-m-available"
69
- onItemClick={onItemClick('availableSelected')}
70
- selected={selectState.availableSelected}
71
- isItemSelected={isItemSelected('availableSelected')}
72
- />
73
- <ListControls
74
- onAddSelected={onAddSelected}
75
- onRemoveSelected={onRemoveSelected}
76
- onRemoveAll={onRemoveAll}
77
- onAddAll={onAddAll}
78
- addAllDisabled={props.availableOptions.length === 0}
79
- removeAllDisabled={props.chosenOptions.length === 0}
80
- addSelectedDisabled={selectState.availableSelected.length === 0}
81
- removeSelectedDisabled={selectState.chosenSelected.length === 0}
82
- />
83
- <ListPane
84
- title={__('Assigned Ansible roles')}
85
- items={props.chosenOptions}
86
- paneClass="pf-m-chosen"
87
- draggable
88
- onItemClick={onItemClick('chosenSelected')}
89
- selected={selectState.chosenSelected}
90
- isItemSelected={isItemSelected('chosenSelected')}
91
- onSetOrderedItems={onChosenSetOrder}
92
- />
93
- </div>
94
- );
95
- };
6
+ const DualList = ({
7
+ availableOptions,
8
+ chosenOptions,
9
+ onListChange,
10
+ id,
11
+ availableOptionsTitle,
12
+ chosenOptionsTitle,
13
+ isDisabled,
14
+ showTooltips,
15
+ }) => (
16
+ <OrderableDualListSelector
17
+ id={id}
18
+ availableOptions={availableOptions}
19
+ chosenOptions={chosenOptions}
20
+ onListChange={onListChange}
21
+ availableOptionsTitle={availableOptionsTitle}
22
+ chosenOptionsTitle={chosenOptionsTitle}
23
+ isDisabled={isDisabled}
24
+ showTooltips={showTooltips}
25
+ />
26
+ );
96
27
 
97
28
  DualList.propTypes = {
98
29
  onListChange: PropTypes.func.isRequired,
99
30
  chosenOptions: PropTypes.array.isRequired,
100
31
  availableOptions: PropTypes.array.isRequired,
32
+ id: PropTypes.string,
33
+ availableOptionsTitle: PropTypes.string,
34
+ chosenOptionsTitle: PropTypes.string,
35
+ isDisabled: PropTypes.bool,
36
+ showTooltips: PropTypes.bool,
37
+ };
38
+
39
+ DualList.defaultProps = {
40
+ id: 'ansible-roles-dual-list',
41
+ availableOptionsTitle: __('Available Ansible roles'),
42
+ chosenOptionsTitle: __('Assigned Ansible roles'),
43
+ isDisabled: false,
44
+ showTooltips: true,
101
45
  };
102
46
 
103
47
  export default DualList;
@@ -8,11 +8,13 @@ import AnsibleHostDetail from './components/AnsibleHostDetail';
8
8
 
9
9
  import { ANSIBLE_KEY } from './components/AnsibleHostDetail/constants';
10
10
 
11
+ const ANSIBLE_TAB_WEIGHT = 500;
12
+
11
13
  addGlobalFill(
12
14
  'host-details-page-tabs',
13
15
  ANSIBLE_KEY,
14
16
  <AnsibleHostDetail key="ansible-host-detail" />,
15
- 500
17
+ ANSIBLE_TAB_WEIGHT
16
18
  );
17
19
 
18
20
  registerRoutes('foreman_ansible', routes);
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_ansible
3
3
  version: !ruby/object:Gem::Version
4
- version: 17.0.8
4
+ version: 18.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Lobato Garcia
@@ -64,9 +64,6 @@ dependencies:
64
64
  - - ">="
65
65
  - !ruby/object:Gem::Version
66
66
  version: '10.0'
67
- - - "<"
68
- - !ruby/object:Gem::Version
69
- version: '13'
70
67
  type: :runtime
71
68
  prerelease: false
72
69
  version_requirements: !ruby/object:Gem::Requirement
@@ -74,9 +71,6 @@ dependencies:
74
71
  - - ">="
75
72
  - !ruby/object:Gem::Version
76
73
  version: '10.0'
77
- - - "<"
78
- - !ruby/object:Gem::Version
79
- version: '13'
80
74
  description: Ansible integration with Foreman
81
75
  email:
82
76
  - elobatocs@gmail.com
@@ -422,12 +416,7 @@ files:
422
416
  - webpack/components/AnsibleRolesSwitcher/components/InheritedRolesList.test.js
423
417
  - webpack/components/AnsibleRolesSwitcher/components/withProtectedView.js
424
418
  - webpack/components/AnsibleRolesSwitcher/index.js
425
- - webpack/components/DualList/DualList.scss
426
- - webpack/components/DualList/ListControls.js
427
- - webpack/components/DualList/ListHeader.js
428
- - webpack/components/DualList/ListItem.js
429
- - webpack/components/DualList/ListPane.js
430
- - webpack/components/DualList/SelectedStatus.js
419
+ - webpack/components/DualList/DualList.test.js
431
420
  - webpack/components/DualList/index.js
432
421
  - webpack/components/ErrorState.js
433
422
  - webpack/components/ReportJsonViewer.js
@@ -476,7 +465,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
476
465
  - !ruby/object:Gem::Version
477
466
  version: '0'
478
467
  requirements: []
479
- rubygems_version: 4.0.10
468
+ rubygems_version: 4.0.16
480
469
  specification_version: 4
481
470
  summary: Ansible integration with Foreman (theforeman.org)
482
471
  test_files:
@@ -1,3 +0,0 @@
1
- .foreman-dual-list-order {
2
- padding-right: var(--pf-global--spacer--sm);
3
- }
@@ -1,65 +0,0 @@
1
- import React from 'react';
2
- import PropTypes from 'prop-types';
3
- import { translate as __ } from 'foremanReact/common/I18n';
4
-
5
- const ListControls = props => (
6
- <div className="pf-v5-c-dual-list-selector__controls">
7
- <div className="pf-v5-c-dual-list-selector__controls-item">
8
- <button
9
- className="pf-v5-c-button pf-m-plain"
10
- type="button"
11
- disabled={props.addSelectedDisabled}
12
- onClick={props.onAddSelected}
13
- aria-label={__('Add selected')}
14
- >
15
- <i className="fas fa-fw fa-angle-right" />
16
- </button>
17
- </div>
18
- <div className="pf-v5-c-dual-list-selector__controls-item">
19
- <button
20
- className="pf-v5-c-button pf-m-plain"
21
- type="button"
22
- disabled={props.addAllDisabled}
23
- onClick={props.onAddAll}
24
- aria-label={__('Add all')}
25
- >
26
- <i className="fas fa-fw fa-angle-double-right" />
27
- </button>
28
- </div>
29
- <div className="pf-v5-c-dual-list-selector__controls-item">
30
- <button
31
- className="pf-v5-c-button pf-m-plain"
32
- type="button"
33
- disabled={props.removeAllDisabled}
34
- onClick={props.onRemoveAll}
35
- aria-label={__('Remove all')}
36
- >
37
- <i className="fas fa-fw fa-angle-double-left" />
38
- </button>
39
- </div>
40
- <div className="pf-v5-c-dual-list-selector__controls-item">
41
- <button
42
- className="pf-v5-c-button pf-m-plain"
43
- type="button"
44
- disabled={props.removeSelectedDisabled}
45
- onClick={props.onRemoveSelected}
46
- aria-label={__('Remove selected')}
47
- >
48
- <i className="fas fa-fw fa-angle-left" />
49
- </button>
50
- </div>
51
- </div>
52
- );
53
-
54
- ListControls.propTypes = {
55
- addSelectedDisabled: PropTypes.bool.isRequired,
56
- onAddSelected: PropTypes.func.isRequired,
57
- addAllDisabled: PropTypes.bool.isRequired,
58
- onAddAll: PropTypes.func.isRequired,
59
- removeAllDisabled: PropTypes.bool.isRequired,
60
- onRemoveAll: PropTypes.func.isRequired,
61
- removeSelectedDisabled: PropTypes.bool.isRequired,
62
- onRemoveSelected: PropTypes.func.isRequired,
63
- };
64
-
65
- export default ListControls;
@@ -1,18 +0,0 @@
1
- import React from 'react';
2
- import PropTypes from 'prop-types';
3
-
4
- const ListHeader = props => (
5
- <div className="pf-v5-c-dual-list-selector__header">
6
- <div className="pf-v5-c-dual-list-selector__title">
7
- <div className="pf-v5-c-dual-list-selector__title-text">
8
- {props.title}
9
- </div>
10
- </div>
11
- </div>
12
- );
13
-
14
- ListHeader.propTypes = {
15
- title: PropTypes.string.isRequired,
16
- };
17
-
18
- export default ListHeader;
@@ -1,69 +0,0 @@
1
- import React from 'react';
2
- import classNames from 'classnames';
3
- import PropTypes from 'prop-types';
4
-
5
- const ListItem = props => {
6
- const draggableBtn = (
7
- <div className="pf-v5-c-dual-list-selector__draggable">
8
- <button
9
- className="pf-v5-c-button pf-m-plain"
10
- type="button"
11
- aria-pressed="false"
12
- aria-label="Reorder"
13
- id="draggable-list-item-2-draggable-button"
14
- aria-labelledby="draggable-list-item-2-draggable-button draggable-list-item-2-item-text"
15
- aria-describedby="draggable-help"
16
- >
17
- <i className="fas fa-grip-vertical" aria-hidden="true" />
18
- </button>
19
- </div>
20
- );
21
-
22
- const orderBtn = (
23
- <span className="foreman-dual-list-order">{`${props.index + 1}. `}</span>
24
- );
25
-
26
- return (
27
- <li className="pf-v5-c-dual-list-selector__list-item">
28
- <div
29
- className={classNames('pf-v5-c-dual-list-selector__list-item-row ', {
30
- 'pf-m-selected': props.selected,
31
- 'pf-m-ghost-row': props.dragging,
32
- })}
33
- >
34
- {props.draggable && draggableBtn}
35
- <button
36
- className="pf-v5-c-dual-list-selector__item"
37
- type="button"
38
- onClick={props.onClick}
39
- >
40
- <span className="pf-v5-c-dual-list-selector__item-main">
41
- <span className="pf-v5-c-dual-list-selector__item-text">
42
- {props.draggable && orderBtn}
43
- <span>{props.name}</span>
44
- </span>
45
- </span>
46
- <span className="pf-v5-c-dual-list-selector__item-count">
47
- <span className="pf-v5-c-badge pf-m-read" />
48
- </span>
49
- </button>
50
- </div>
51
- </li>
52
- );
53
- };
54
-
55
- ListItem.propTypes = {
56
- selected: PropTypes.bool.isRequired,
57
- dragging: PropTypes.bool,
58
- draggable: PropTypes.bool,
59
- onClick: PropTypes.func.isRequired,
60
- name: PropTypes.string.isRequired,
61
- index: PropTypes.number.isRequired,
62
- };
63
-
64
- ListItem.defaultProps = {
65
- draggable: false,
66
- dragging: false,
67
- };
68
-
69
- export default ListItem;
@@ -1,95 +0,0 @@
1
- import React from 'react';
2
- import { DndProvider } from 'react-dnd';
3
- import { HTML5Backend } from 'react-dnd-html5-backend';
4
- import PropTypes from 'prop-types';
5
-
6
- import {
7
- orderable,
8
- orderDragged,
9
- } from 'foremanReact/components/common/forms/OrderableSelect/helpers';
10
-
11
- import ListItem from './ListItem';
12
- import ListHeader from './ListHeader';
13
- import SelectedStatus from './SelectedStatus';
14
-
15
- const OrderableListItem = orderable(ListItem, {
16
- type: 'orderableItem',
17
- getItem: props => ({ id: props.name }),
18
- direction: 'vertical',
19
- });
20
-
21
- const ListPane = ({
22
- items,
23
- isItemSelected,
24
- draggable,
25
- onItemClick,
26
- paneClass,
27
- title,
28
- selected,
29
- onSetOrderedItems,
30
- }) => {
31
- const moveValue = (dragIndex, hoverIndex) => {
32
- onSetOrderedItems(orderDragged(items, dragIndex, hoverIndex));
33
- };
34
-
35
- const renderOrderableItem = (item, idx) => (
36
- <OrderableListItem
37
- key={item}
38
- name={item}
39
- index={idx}
40
- moveValue={moveValue}
41
- onClick={onItemClick(item)}
42
- selected={isItemSelected(item)}
43
- draggable={draggable}
44
- />
45
- );
46
-
47
- const renderListItem = (item, idx) => (
48
- <ListItem
49
- key={item}
50
- name={item}
51
- index={idx}
52
- onClick={onItemClick(item)}
53
- selected={isItemSelected(item)}
54
- />
55
- );
56
-
57
- const renderItem = draggable ? renderOrderableItem : renderListItem;
58
-
59
- return (
60
- <div
61
- className={`pf-v5-c-dual-list-selector__pane pf-m-available ${paneClass}`}
62
- >
63
- <ListHeader title={title} />
64
- <SelectedStatus
65
- selectedCount={selected.length}
66
- totalCount={items.length}
67
- />
68
- <div className="pf-v5-c-dual-list-selector__menu">
69
- <DndProvider backend={HTML5Backend}>
70
- <ul className="pf-v5-c-dual-list-selector__list">
71
- {items.map(renderItem)}
72
- </ul>
73
- </DndProvider>
74
- </div>
75
- </div>
76
- );
77
- };
78
-
79
- ListPane.propTypes = {
80
- items: PropTypes.array.isRequired,
81
- isItemSelected: PropTypes.func.isRequired,
82
- draggable: PropTypes.bool,
83
- onItemClick: PropTypes.func.isRequired,
84
- paneClass: PropTypes.string.isRequired,
85
- title: PropTypes.string.isRequired,
86
- selected: PropTypes.array.isRequired,
87
- onSetOrderedItems: PropTypes.func,
88
- };
89
-
90
- ListPane.defaultProps = {
91
- draggable: false,
92
- onSetOrderedItems: () => {},
93
- };
94
-
95
- export default ListPane;
@@ -1,21 +0,0 @@
1
- import React from 'react';
2
- import PropTypes from 'prop-types';
3
- import { translate as __, sprintf } from 'foremanReact/common/I18n';
4
-
5
- const SelectedStatus = ({ selectedCount, totalCount }) => (
6
- <div className="pf-v5-c-dual-list-selector__status">
7
- <span className="pf-v5-c-dual-list-selector__status-text">
8
- {sprintf(__('%(selectedCount)s of %(totalCount)s items selected'), {
9
- selectedCount,
10
- totalCount,
11
- })}
12
- </span>
13
- </div>
14
- );
15
-
16
- SelectedStatus.propTypes = {
17
- selectedCount: PropTypes.number.isRequired,
18
- totalCount: PropTypes.number.isRequired,
19
- };
20
-
21
- export default SelectedStatus;