diff --git a/packages/core/helper-plugin/lib/src/hooks/useSelectionState/index.js b/packages/core/helper-plugin/lib/src/hooks/useSelectionState/index.js index 18871d4173..928c68769f 100644 --- a/packages/core/helper-plugin/lib/src/hooks/useSelectionState/index.js +++ b/packages/core/helper-plugin/lib/src/hooks/useSelectionState/index.js @@ -34,5 +34,5 @@ export const useSelectionState = (key, initialValue) => { } }; - return [selections, { selectOne, selectAll, selectOnly }]; + return [selections, { selectOne, selectAll, selectOnly, setSelections }]; }; diff --git a/packages/core/helper-plugin/lib/src/hooks/useSelectionState/useSelectionState.stories.mdx b/packages/core/helper-plugin/lib/src/hooks/useSelectionState/useSelectionState.stories.mdx new file mode 100644 index 0000000000..8fc4c09d90 --- /dev/null +++ b/packages/core/helper-plugin/lib/src/hooks/useSelectionState/useSelectionState.stories.mdx @@ -0,0 +1,38 @@ + + +import { Meta } from '@storybook/addon-docs'; + + + +# useSelectionState + +This hook is used in order to facilitate the select / partial selection of a global set + +## Usage + +```js +import { useSelectionState } from '@strapi/helper-plugin'; + +const Modal = ({ onToggle, isOpen }) => { + const [selectedAssets, { selectOne, selectAll, selectOnly, setSelections }] = useSelectionState( + 'id', // This is the comparaison attribute name + [] + ); + + const elements = [{ id: 1, name: 'Hello' }, { id: 2, name: 'World' }]; + + // selectOne({ id: 1 name: 'Hello' }) add the object to the selection list + // selectOnly({ id: 1 name: 'Hello' }) add the object to the selection list and remove every others in the list + // selectAll(assets) select all or remove all + // setSelections(): regular state used in react + + return ( +
+ + {elements.map(el => ( +
{el.name}
+ ))} +
+ ); +}; +``` diff --git a/packages/core/upload/admin/src/components/AssetDialog/SelectedStep/index.js b/packages/core/upload/admin/src/components/AssetDialog/SelectedStep/index.js index 037d4dc231..058975ff45 100644 --- a/packages/core/upload/admin/src/components/AssetDialog/SelectedStep/index.js +++ b/packages/core/upload/admin/src/components/AssetDialog/SelectedStep/index.js @@ -6,7 +6,7 @@ import { Typography } from '@strapi/design-system/Typography'; import { AssetList } from '../../AssetList'; import getTrad from '../../../utils/getTrad'; -export const SelectedStep = ({ selectedAssets, onSelectAsset }) => { +export const SelectedStep = ({ selectedAssets, onSelectAsset, onReorderAsset }) => { const { formatMessage } = useIntl(); return ( @@ -35,13 +35,18 @@ export const SelectedStep = ({ selectedAssets, onSelectAsset }) => { assets={selectedAssets} onSelectAsset={onSelectAsset} selectedAssets={selectedAssets} - onEditAsset={() => {}} + onReorderAsset={onReorderAsset} /> ); }; +SelectedStep.defaultProps = { + onReorderAsset: undefined, +}; + SelectedStep.propTypes = { onSelectAsset: PropTypes.func.isRequired, selectedAssets: PropTypes.arrayOf(PropTypes.shape({})).isRequired, + onReorderAsset: PropTypes.func, }; diff --git a/packages/core/upload/admin/src/components/AssetDialog/index.js b/packages/core/upload/admin/src/components/AssetDialog/index.js index fa9bfdc118..a8b34b1d0f 100644 --- a/packages/core/upload/admin/src/components/AssetDialog/index.js +++ b/packages/core/upload/admin/src/components/AssetDialog/index.js @@ -23,6 +23,7 @@ import { DialogTitle } from './DialogTitle'; import { DialogFooter } from './DialogFooter'; import { EditAssetDialog } from '../EditAssetDialog'; import { EmptyAssets } from '../EmptyAssets'; +import { moveElement } from '../../utils/moveElement'; export const AssetDialog = ({ allowedTypes, @@ -49,7 +50,7 @@ export const AssetDialog = ({ ] = useModalQueryParams(); const { data, isLoading, error } = useModalAssets({ skipWhen: !canRead, rawQuery }); - const [selectedAssets, { selectOne, selectAll, selectOnly }] = useSelectionState( + const [selectedAssets, { selectOne, selectAll, selectOnly, setSelections }] = useSelectionState( 'id', initiallySelectedAssets ); @@ -165,6 +166,14 @@ export const AssetDialog = ({ ); } + const handleMoveItem = (hoverIndex, destIndex) => { + const offset = destIndex - hoverIndex; + const orderedAssetsClone = selectedAssets.slice(); + const nextAssets = moveElement(orderedAssetsClone, hoverIndex, offset); + + setSelections(nextAssets); + }; + return ( @@ -226,7 +235,11 @@ export const AssetDialog = ({ - + diff --git a/packages/core/upload/admin/src/components/AssetList/Draggable.js b/packages/core/upload/admin/src/components/AssetList/Draggable.js new file mode 100644 index 0000000000..1e1a135643 --- /dev/null +++ b/packages/core/upload/admin/src/components/AssetList/Draggable.js @@ -0,0 +1,49 @@ +import React, { useRef } from 'react'; +import PropTypes from 'prop-types'; +import { useDrag, useDrop } from 'react-dnd'; + +export const Draggable = ({ children, id, index, moveItem }) => { + const ref = useRef(null); + + const [, drop] = useDrop({ + accept: 'draggable', + hover(hoveredOverItem) { + if (!ref.current) { + return; + } + + if (hoveredOverItem.id !== id) { + moveItem(hoveredOverItem.index, index); + + hoveredOverItem.index = index; + } + }, + }); + + const [{ isDragging }, drag] = useDrag({ + type: 'draggable', + item: () => { + return { index, id }; + }, + collect: monitor => ({ + isDragging: monitor.isDragging(), + }), + }); + + const opacity = isDragging ? 0.2 : 1; + + drag(drop(ref)); + + return ( +
+ {children} +
+ ); +}; + +Draggable.propTypes = { + id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + index: PropTypes.number.isRequired, + children: PropTypes.node.isRequired, + moveItem: PropTypes.func.isRequired, +}; diff --git a/packages/core/upload/admin/src/components/AssetList/index.js b/packages/core/upload/admin/src/components/AssetList/index.js index fdb43c7825..7bcb90f612 100644 --- a/packages/core/upload/admin/src/components/AssetList/index.js +++ b/packages/core/upload/admin/src/components/AssetList/index.js @@ -4,6 +4,7 @@ import styled from 'styled-components'; import { Box } from '@strapi/design-system/Box'; import { KeyboardNavigable } from '@strapi/design-system/KeyboardNavigable'; import { AssetCard } from '../AssetCard/AssetCard'; +import { Draggable } from './Draggable'; const GridColSize = { S: 180, @@ -23,19 +24,35 @@ export const AssetList = ({ onSelectAsset, selectedAssets, size, + onReorderAsset, }) => { return ( - {assets.map(asset => { + {assets.map((asset, index) => { const isSelected = Boolean( selectedAssets.find(currentAsset => currentAsset.id === asset.id) ); + if (onReorderAsset) { + return ( + + onEditAsset(asset) : undefined} + onSelect={() => onSelectAsset(asset)} + size={size} + /> + + ); + } + return ( onEditAsset(asset) : undefined} @@ -61,6 +78,7 @@ AssetList.defaultProps = { allowedTypes: ['images', 'files', 'videos'], onEditAsset: undefined, size: 'M', + onReorderAsset: undefined, }; AssetList.propTypes = { @@ -70,4 +88,5 @@ AssetList.propTypes = { onSelectAsset: PropTypes.func.isRequired, selectedAssets: PropTypes.arrayOf(PropTypes.shape({})).isRequired, size: PropTypes.oneOf(['S', 'M']), + onReorderAsset: PropTypes.func, }; diff --git a/packages/core/upload/admin/src/utils/moveElement.js b/packages/core/upload/admin/src/utils/moveElement.js new file mode 100644 index 0000000000..4c6c2a2c27 --- /dev/null +++ b/packages/core/upload/admin/src/utils/moveElement.js @@ -0,0 +1,14 @@ +const move = (array, oldIndex, newIndex) => { + if (newIndex >= array.length) { + newIndex = array.length - 1; + } + array.splice(newIndex, 0, array.splice(oldIndex, 1)[0]); + + return array; +}; + +export const moveElement = (array, index, offset) => { + const newIndex = index + offset; + + return move(array, index, newIndex); +};