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);
+};