diff --git a/package-lock.json b/package-lock.json index 340df233..afb34325 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "react-invenio-forms", - "version": "2.0.0", + "version": "2.4.0", "license": "MIT", "devDependencies": { "@babel/cli": "^7.5.0", diff --git a/src/lib/elements/bulk_actions/SearchResultsBulkActions.js b/src/lib/elements/bulk_actions/SearchResultsBulkActions.js new file mode 100644 index 00000000..ec773b58 --- /dev/null +++ b/src/lib/elements/bulk_actions/SearchResultsBulkActions.js @@ -0,0 +1,97 @@ +/* + * This file is part of Invenio. + * Copyright (C) 2022 CERN. + * + * Invenio is free software; you can redistribute it and/or modify it + * under the terms of the MIT License; see LICENSE file for more details. + */ + +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { Checkbox, Dropdown } from "semantic-ui-react"; +import { BulkActionsContext } from "./context"; +import _pickBy from "lodash/pickBy"; + +export class SearchResultsBulkActions extends Component { + constructor(props) { + super(props); + const { allSelected } = this.props; + this.state = { allSelectedChecked: allSelected }; + } + + componentDidMount() { + const { allSelected } = this.context; + // eslint-disable-next-line react/no-did-mount-set-state + this.setState({ allSelectedChecked: allSelected }); + } + + static contextType = BulkActionsContext; + + handleOnChange = () => { + const { setAllSelected, allSelected } = this.context; + this.setState({ allSelectedChecked: !allSelected }); + setAllSelected(!allSelected, true); + }; + + handleActionOnChange = (e, { value, ...props }) => { + if (!value) return; + + const { optionSelectionCallback } = this.props; + + const { selectedCount, bulkActionContext } = this.context; + const selected = _pickBy(bulkActionContext, ({ selected }) => selected === true); + optionSelectionCallback(value, selected, selectedCount); + }; + + render() { + const { bulkDropdownOptions } = this.props; + const { allSelectedChecked } = this.state; + const { allSelected, selectedCount } = this.context; + + const noneSelected = selectedCount === 0; + + const dropdownOptions = bulkDropdownOptions.map(({ key, value, text }) => ({ + key: key, + value: value, + text: text, + disabled: noneSelected, + })); + + return ( +
+ + +
+ ); + } +} + +SearchResultsBulkActions.propTypes = { + bulkDropdownOptions: PropTypes.array.isRequired, + allSelected: PropTypes.bool, + optionSelectionCallback: PropTypes.func.isRequired, +}; + +SearchResultsBulkActions.defaultProps = { + allSelected: false, +}; + +export default Overridable.component( + "SearchResultsBulkActions", + SearchResultsBulkActions +); diff --git a/src/lib/elements/bulk_actions/SearchResultsBulkActionsManager.js b/src/lib/elements/bulk_actions/SearchResultsBulkActionsManager.js new file mode 100644 index 00000000..508f7659 --- /dev/null +++ b/src/lib/elements/bulk_actions/SearchResultsBulkActionsManager.js @@ -0,0 +1,81 @@ +import { BulkActionsContext } from "./context"; +import React, { Component } from "react"; +import _hasIn from "lodash/hasIn"; +import PropTypes from "prop-types"; + +class SearchResultsBulkActionsManager extends Component { + constructor(props) { + super(props); + + this.selected = {}; + this.state = { allSelected: false, selectedCount: 0 }; + } + + addToSelected = (rowId, data) => { + const { selectedCount } = this.state; + if (_hasIn(this.selected, `${rowId}`)) { + this.selected[rowId].selected = !this.selected[rowId].selected; + } else { + this.selected[rowId].selected = true; + this.selected[rowId].data = data; + } + + if (!this.selected[rowId].selected) { + this.setAllSelected(false); + this.setSelectedCount(selectedCount - 1); + } else { + const updatedCount = selectedCount + 1; + this.setSelectedCount(updatedCount); + if (Object.keys(this.selected).length === updatedCount) { + this.setAllSelected(true); + } + } + }; + + setSelectedCount = (count) => { + this.setState({ selectedCount: count }); + }; + + setAllSelected = (val, global = false) => { + this.setState({ allSelected: val }); + if (global) { + for (const [key] of Object.entries(this.selected)) { + this.selected[key].selected = val; + } + if (val) { + this.setSelectedCount(Object.keys(this.selected).length); + } else { + this.setSelectedCount(0); + } + } + }; + + render() { + const { children } = this.props; + const { allSelected, selectedCount } = this.state; + return ( + + {children} + + ); + } +} + +SearchResultsBulkActionsManager.contextType = BulkActionsContext; + +SearchResultsBulkActionsManager.propTypes = { + children: PropTypes.node.isRequired, +}; + +export default Overridable.component( + "SearchResultsBulkActionsManager", + SearchResultsBulkActionsManager +); diff --git a/src/lib/elements/bulk_actions/SearchResultsRowCheckbox.js b/src/lib/elements/bulk_actions/SearchResultsRowCheckbox.js new file mode 100644 index 00000000..1237751b --- /dev/null +++ b/src/lib/elements/bulk_actions/SearchResultsRowCheckbox.js @@ -0,0 +1,63 @@ +import { BulkActionsContext } from "./context"; +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { Checkbox } from "semantic-ui-react"; +import _hasIn from "lodash/hasIn"; + +export class SearchResultsRowCheckbox extends Component { + constructor(props) { + super(props); + this.state = { isChecked: false }; + } + + componentDidMount() { + this.subscribeToContext(); + const { bulkActionContext, allSelected } = this.context; + // eslint-disable-next-line react/no-did-mount-set-state + this.setState({ + isChecked: this.isChecked(bulkActionContext, allSelected), + }); + } + + static contextType = BulkActionsContext; + + isChecked = (bulkActionContext, allSelected) => { + const { rowId } = this.props; + if (_hasIn(bulkActionContext, `${rowId}`) || allSelected) { + return bulkActionContext[rowId].selected; + } + return false; + }; + + subscribeToContext = () => { + const { rowId, data } = this.props; + const { allSelected, bulkActionContext } = this.context; + if (!_hasIn(bulkActionContext, `${rowId}`)) { + bulkActionContext[rowId] = { selected: allSelected, data: data }; + } + }; + + handleOnChange = () => { + const { addToSelected } = this.context; + const { rowId, data } = this.props; + const { isChecked } = this.state; + this.setState({ isChecked: !isChecked }); + addToSelected(rowId, data); + }; + + render() { + const { bulkActionContext, allSelected } = this.context; + return ( + + ); + } +} + +SearchResultsRowCheckbox.propTypes = { + rowId: PropTypes.string.isRequired, + data: PropTypes.object.isRequired, +}; diff --git a/src/lib/elements/bulk_actions/context.js b/src/lib/elements/bulk_actions/context.js new file mode 100644 index 00000000..4e7d7e68 --- /dev/null +++ b/src/lib/elements/bulk_actions/context.js @@ -0,0 +1,9 @@ +import React from "react"; + +export const BulkActionsContext = React.createContext({ + bulkActionContext: {}, + addToSelected: () => {}, + allSelected: false, + setAllSelected: () => {}, + selectedCount: 0, +}); diff --git a/src/lib/elements/bulk_actions/index.js b/src/lib/elements/bulk_actions/index.js new file mode 100644 index 00000000..2d0fbec4 --- /dev/null +++ b/src/lib/elements/bulk_actions/index.js @@ -0,0 +1,4 @@ +export { BulkActionsContext } from "./context"; +export { SearchResultsBulkActions } from "./SearchResultsBulkActions"; +export { default as SearchResultsBulkActionsManager } from "./SearchResultsBulkActionsManager"; +export { SearchResultsRowCheckbox } from "./SearchResultsRowCheckbox"; diff --git a/src/lib/elements/contrib/index.js b/src/lib/elements/contrib/index.js new file mode 100644 index 00000000..7b7e8980 --- /dev/null +++ b/src/lib/elements/contrib/index.js @@ -0,0 +1,9 @@ +/* + * // This file is part of invenio-app-rdm + * // Copyright (C) 2023 CERN. + * // + * // invenio-app-rdm is free software; you can redistribute it and/or modify it + * // under the terms of the MIT License; see LICENSE file for more details. + */ + +export * from "./invenioRDM"; diff --git a/src/lib/elements/contrib/invenioRDM/groups/index.js b/src/lib/elements/contrib/invenioRDM/groups/index.js new file mode 100644 index 00000000..f568af83 --- /dev/null +++ b/src/lib/elements/contrib/invenioRDM/groups/index.js @@ -0,0 +1,7 @@ +/* + * // This file is part of React-Invenio-Forms + * // Copyright (C) 2023 CERN. + * // + * // React-Invenio-Forms is free software; you can redistribute it and/or modify it + * // under the terms of the MIT License; see LICENSE file for more details. + */ diff --git a/src/lib/elements/contrib/invenioRDM/index.js b/src/lib/elements/contrib/invenioRDM/index.js new file mode 100644 index 00000000..2c6d0111 --- /dev/null +++ b/src/lib/elements/contrib/invenioRDM/index.js @@ -0,0 +1,10 @@ +/* + * // This file is part of React-Invenio-Forms + * // Copyright (C) 2023 CERN. + * // + * // React-Invenio-Forms is free software; you can redistribute it and/or modify it + * // under the terms of the MIT License; see LICENSE file for more details. + */ + +export * from "./users"; +export * from "./groups"; diff --git a/src/lib/elements/contrib/invenioRDM/users/UserListItemCompact.js b/src/lib/elements/contrib/invenioRDM/users/UserListItemCompact.js new file mode 100644 index 00000000..c1d94f90 --- /dev/null +++ b/src/lib/elements/contrib/invenioRDM/users/UserListItemCompact.js @@ -0,0 +1,46 @@ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { Image } from "../../../Image"; +import { Item, Label } from "semantic-ui-react"; + +export class UserListItemCompact extends Component { + render() { + const { id, user, linkToDetailView } = this.props; + const name = user.profile.full_name || user.profile.email || user.profile.username; + return ( + + + + + {linkToDetailView ? ( + + {name} + + ) : ( + {name} + )} + {user.type === "group" && } + {user.is_current_user && ( + + )} + + +
{user.profile.affiliations}
+
+
+
+ ); + } +} + +UserListItemCompact.propTypes = { + user: PropTypes.object.isRequired, + id: PropTypes.string.isRequired, + linkToDetailView: PropTypes.string, +}; + +UserListItemCompact.defaultProps = { + linkToDetailView: undefined, +}; diff --git a/src/lib/elements/contrib/invenioRDM/users/index.js b/src/lib/elements/contrib/invenioRDM/users/index.js new file mode 100644 index 00000000..846f8ce7 --- /dev/null +++ b/src/lib/elements/contrib/invenioRDM/users/index.js @@ -0,0 +1,9 @@ +/* + * // This file is part of React-Invenio-Forms + * // Copyright (C) 2023 CERN. + * // + * // React-Invenio-Forms is free software; you can redistribute it and/or modify it + * // under the terms of the MIT License; see LICENSE file for more details. + */ + +export { UserListItemCompact } from "./UserListItemCompact"; diff --git a/src/lib/elements/index.js b/src/lib/elements/index.js index 745dc16e..d01b8850 100644 --- a/src/lib/elements/index.js +++ b/src/lib/elements/index.js @@ -8,6 +8,8 @@ * This folder contains general purpose reusable components. */ export * from "./accessibility"; +export * from "./contrib"; +export * from "./bulk_actions"; export { Image } from "./Image"; export { GridResponsiveSidebarColumn } from "./GridResponsiveSidebarColumn"; export { ErrorMessage } from "./ErrorMessage";