mirror of
https://github.com/strapi/strapi.git
synced 2025-09-26 00:39:49 +00:00
Merge pull request #11618 from strapi/dnd-preview-box
DnD SelectedStep in ML / CM
This commit is contained in:
commit
1a7f02a19d
@ -34,5 +34,5 @@ export const useSelectionState = (key, initialValue) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return [selections, { selectOne, selectAll, selectOnly }];
|
return [selections, { selectOne, selectAll, selectOnly, setSelections }];
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
<!--- useLockScroll.stories.mdx --->
|
||||||
|
|
||||||
|
import { Meta } from '@storybook/addon-docs';
|
||||||
|
|
||||||
|
<Meta title="hooks/useSelectionState" />
|
||||||
|
|
||||||
|
# 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 (
|
||||||
|
<div>
|
||||||
|
<button onClick={() => selectAll(assets)}>Select all</button>
|
||||||
|
{elements.map(el => (
|
||||||
|
<div key={el.id}>{el.name}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
@ -6,7 +6,7 @@ import { Typography } from '@strapi/design-system/Typography';
|
|||||||
import { AssetList } from '../../AssetList';
|
import { AssetList } from '../../AssetList';
|
||||||
import getTrad from '../../../utils/getTrad';
|
import getTrad from '../../../utils/getTrad';
|
||||||
|
|
||||||
export const SelectedStep = ({ selectedAssets, onSelectAsset }) => {
|
export const SelectedStep = ({ selectedAssets, onSelectAsset, onReorderAsset }) => {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -35,13 +35,18 @@ export const SelectedStep = ({ selectedAssets, onSelectAsset }) => {
|
|||||||
assets={selectedAssets}
|
assets={selectedAssets}
|
||||||
onSelectAsset={onSelectAsset}
|
onSelectAsset={onSelectAsset}
|
||||||
selectedAssets={selectedAssets}
|
selectedAssets={selectedAssets}
|
||||||
onEditAsset={() => {}}
|
onReorderAsset={onReorderAsset}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SelectedStep.defaultProps = {
|
||||||
|
onReorderAsset: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
SelectedStep.propTypes = {
|
SelectedStep.propTypes = {
|
||||||
onSelectAsset: PropTypes.func.isRequired,
|
onSelectAsset: PropTypes.func.isRequired,
|
||||||
selectedAssets: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
selectedAssets: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||||
|
onReorderAsset: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
@ -23,6 +23,7 @@ import { DialogTitle } from './DialogTitle';
|
|||||||
import { DialogFooter } from './DialogFooter';
|
import { DialogFooter } from './DialogFooter';
|
||||||
import { EditAssetDialog } from '../EditAssetDialog';
|
import { EditAssetDialog } from '../EditAssetDialog';
|
||||||
import { EmptyAssets } from '../EmptyAssets';
|
import { EmptyAssets } from '../EmptyAssets';
|
||||||
|
import { moveElement } from '../../utils/moveElement';
|
||||||
|
|
||||||
export const AssetDialog = ({
|
export const AssetDialog = ({
|
||||||
allowedTypes,
|
allowedTypes,
|
||||||
@ -49,7 +50,7 @@ export const AssetDialog = ({
|
|||||||
] = useModalQueryParams();
|
] = useModalQueryParams();
|
||||||
const { data, isLoading, error } = useModalAssets({ skipWhen: !canRead, rawQuery });
|
const { data, isLoading, error } = useModalAssets({ skipWhen: !canRead, rawQuery });
|
||||||
|
|
||||||
const [selectedAssets, { selectOne, selectAll, selectOnly }] = useSelectionState(
|
const [selectedAssets, { selectOne, selectAll, selectOnly, setSelections }] = useSelectionState(
|
||||||
'id',
|
'id',
|
||||||
initiallySelectedAssets
|
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 (
|
return (
|
||||||
<ModalLayout onClose={onClose} labelledBy="asset-dialog-title" aria-busy={loading}>
|
<ModalLayout onClose={onClose} labelledBy="asset-dialog-title" aria-busy={loading}>
|
||||||
<DialogTitle />
|
<DialogTitle />
|
||||||
@ -226,7 +235,11 @@ export const AssetDialog = ({
|
|||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<SelectedStep selectedAssets={selectedAssets} onSelectAsset={handleSelectAsset} />
|
<SelectedStep
|
||||||
|
selectedAssets={selectedAssets}
|
||||||
|
onSelectAsset={handleSelectAsset}
|
||||||
|
onReorderAsset={handleMoveItem}
|
||||||
|
/>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabPanels>
|
</TabPanels>
|
||||||
|
@ -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 (
|
||||||
|
<div ref={ref} style={{ opacity, cursor: 'move' }}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Draggable.propTypes = {
|
||||||
|
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
||||||
|
index: PropTypes.number.isRequired,
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
moveItem: PropTypes.func.isRequired,
|
||||||
|
};
|
@ -4,6 +4,7 @@ import styled from 'styled-components';
|
|||||||
import { Box } from '@strapi/design-system/Box';
|
import { Box } from '@strapi/design-system/Box';
|
||||||
import { KeyboardNavigable } from '@strapi/design-system/KeyboardNavigable';
|
import { KeyboardNavigable } from '@strapi/design-system/KeyboardNavigable';
|
||||||
import { AssetCard } from '../AssetCard/AssetCard';
|
import { AssetCard } from '../AssetCard/AssetCard';
|
||||||
|
import { Draggable } from './Draggable';
|
||||||
|
|
||||||
const GridColSize = {
|
const GridColSize = {
|
||||||
S: 180,
|
S: 180,
|
||||||
@ -23,19 +24,35 @@ export const AssetList = ({
|
|||||||
onSelectAsset,
|
onSelectAsset,
|
||||||
selectedAssets,
|
selectedAssets,
|
||||||
size,
|
size,
|
||||||
|
onReorderAsset,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<KeyboardNavigable tagName="article">
|
<KeyboardNavigable tagName="article">
|
||||||
<GridLayout size={size}>
|
<GridLayout size={size}>
|
||||||
{assets.map(asset => {
|
{assets.map((asset, index) => {
|
||||||
const isSelected = Boolean(
|
const isSelected = Boolean(
|
||||||
selectedAssets.find(currentAsset => currentAsset.id === asset.id)
|
selectedAssets.find(currentAsset => currentAsset.id === asset.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (onReorderAsset) {
|
||||||
|
return (
|
||||||
|
<Draggable key={asset.id} index={index} moveItem={onReorderAsset} id={asset.id}>
|
||||||
|
<AssetCard
|
||||||
|
allowedTypes={allowedTypes}
|
||||||
|
asset={asset}
|
||||||
|
isSelected={isSelected}
|
||||||
|
onEdit={onEditAsset ? () => onEditAsset(asset) : undefined}
|
||||||
|
onSelect={() => onSelectAsset(asset)}
|
||||||
|
size={size}
|
||||||
|
/>
|
||||||
|
</Draggable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AssetCard
|
<AssetCard
|
||||||
allowedTypes={allowedTypes}
|
|
||||||
key={asset.id}
|
key={asset.id}
|
||||||
|
allowedTypes={allowedTypes}
|
||||||
asset={asset}
|
asset={asset}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
onEdit={onEditAsset ? () => onEditAsset(asset) : undefined}
|
onEdit={onEditAsset ? () => onEditAsset(asset) : undefined}
|
||||||
@ -61,6 +78,7 @@ AssetList.defaultProps = {
|
|||||||
allowedTypes: ['images', 'files', 'videos'],
|
allowedTypes: ['images', 'files', 'videos'],
|
||||||
onEditAsset: undefined,
|
onEditAsset: undefined,
|
||||||
size: 'M',
|
size: 'M',
|
||||||
|
onReorderAsset: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
AssetList.propTypes = {
|
AssetList.propTypes = {
|
||||||
@ -70,4 +88,5 @@ AssetList.propTypes = {
|
|||||||
onSelectAsset: PropTypes.func.isRequired,
|
onSelectAsset: PropTypes.func.isRequired,
|
||||||
selectedAssets: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
selectedAssets: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||||
size: PropTypes.oneOf(['S', 'M']),
|
size: PropTypes.oneOf(['S', 'M']),
|
||||||
|
onReorderAsset: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
14
packages/core/upload/admin/src/utils/moveElement.js
Normal file
14
packages/core/upload/admin/src/utils/moveElement.js
Normal file
@ -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);
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user