2022-09-06 19:46:32 +00:00
|
|
|
/**
|
|
|
|
* External dependencies
|
|
|
|
*/
|
2022-10-05 19:07:03 +00:00
|
|
|
import {
|
|
|
|
createElement,
|
|
|
|
cloneElement,
|
|
|
|
useState,
|
|
|
|
useEffect,
|
|
|
|
} from '@wordpress/element';
|
2022-10-12 18:16:22 +00:00
|
|
|
import { DragEventHandler } from 'react';
|
2022-10-05 19:07:03 +00:00
|
|
|
import classnames from 'classnames';
|
2022-10-12 18:16:22 +00:00
|
|
|
import { MediaItem, MediaUpload } from '@wordpress/media-utils';
|
2022-09-06 19:46:32 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal dependencies
|
|
|
|
*/
|
2022-10-05 19:07:03 +00:00
|
|
|
import { Sortable, moveIndex } from '../sortable';
|
|
|
|
import { ImageGalleryToolbar } from './index';
|
|
|
|
import { ImageGalleryChild, MediaUploadComponentType } from './types';
|
|
|
|
import { removeItem, replaceItem } from './utils';
|
2022-09-06 19:46:32 +00:00
|
|
|
|
|
|
|
export type ImageGalleryProps = {
|
2022-10-05 19:07:03 +00:00
|
|
|
children: ImageGalleryChild | ImageGalleryChild[];
|
2022-09-06 19:46:32 +00:00
|
|
|
columns?: number;
|
2022-10-05 19:07:03 +00:00
|
|
|
onRemove?: ( props: {
|
|
|
|
removeIndex: number;
|
|
|
|
removedItem: ImageGalleryChild;
|
|
|
|
} ) => void;
|
|
|
|
onReplace?: ( props: {
|
|
|
|
replaceIndex: number;
|
2022-10-12 18:16:22 +00:00
|
|
|
media: { id: number } & MediaItem;
|
2022-10-05 19:07:03 +00:00
|
|
|
} ) => void;
|
2022-10-12 18:16:22 +00:00
|
|
|
onSelectAsCover?: ( itemId: string | null ) => void;
|
2022-10-05 19:07:03 +00:00
|
|
|
onOrderChange?: ( items: ImageGalleryChild[] ) => void;
|
|
|
|
MediaUploadComponent?: MediaUploadComponentType;
|
2022-10-12 18:16:22 +00:00
|
|
|
onDragStart?: DragEventHandler< HTMLDivElement >;
|
|
|
|
onDragEnd?: DragEventHandler< HTMLDivElement >;
|
|
|
|
onDragOver?: DragEventHandler< HTMLLIElement >;
|
2022-09-06 19:46:32 +00:00
|
|
|
} & React.HTMLAttributes< HTMLDivElement >;
|
|
|
|
|
|
|
|
export const ImageGallery: React.FC< ImageGalleryProps > = ( {
|
|
|
|
children,
|
|
|
|
columns = 4,
|
2022-10-12 18:16:22 +00:00
|
|
|
onSelectAsCover = () => null,
|
2022-10-05 19:07:03 +00:00
|
|
|
onOrderChange = () => null,
|
|
|
|
onRemove = () => null,
|
|
|
|
onReplace = () => null,
|
|
|
|
MediaUploadComponent = MediaUpload,
|
2022-10-12 18:16:22 +00:00
|
|
|
onDragStart = () => null,
|
|
|
|
onDragEnd = () => null,
|
|
|
|
onDragOver = () => null,
|
2022-09-06 19:46:32 +00:00
|
|
|
}: ImageGalleryProps ) => {
|
2022-10-05 19:07:03 +00:00
|
|
|
const [ activeToolbarKey, setActiveToolbarKey ] = useState< string | null >(
|
|
|
|
null
|
|
|
|
);
|
2022-10-12 18:16:22 +00:00
|
|
|
const [ isDragging, setIsDragging ] = useState< boolean >( false );
|
2022-10-05 19:07:03 +00:00
|
|
|
const [ orderedChildren, setOrderedChildren ] = useState<
|
|
|
|
ImageGalleryChild[]
|
|
|
|
>( [] );
|
|
|
|
|
|
|
|
useEffect( () => {
|
|
|
|
if ( ! children ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
setOrderedChildren(
|
|
|
|
( Array.isArray( children ) ? children : [ children ] ).map(
|
|
|
|
( child, index ) =>
|
|
|
|
cloneElement( child, { key: child.key || String( index ) } )
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}, [ children ] );
|
|
|
|
|
|
|
|
const updateOrderedChildren = ( items: ImageGalleryChild[] ) => {
|
|
|
|
setOrderedChildren( items );
|
2022-10-12 18:16:22 +00:00
|
|
|
onOrderChange( items );
|
2022-10-05 19:07:03 +00:00
|
|
|
};
|
|
|
|
|
2022-09-06 19:46:32 +00:00
|
|
|
return (
|
|
|
|
<div
|
|
|
|
className="woocommerce-image-gallery"
|
|
|
|
style={ {
|
|
|
|
gridTemplateColumns: 'min-content '.repeat( columns ),
|
|
|
|
} }
|
|
|
|
>
|
2022-10-05 19:07:03 +00:00
|
|
|
<Sortable
|
|
|
|
isHorizontal
|
|
|
|
onOrderChange={ ( items ) => {
|
|
|
|
updateOrderedChildren( items );
|
|
|
|
} }
|
2022-10-12 18:16:22 +00:00
|
|
|
onDragStart={ ( event ) => {
|
|
|
|
setIsDragging( true );
|
|
|
|
onDragStart( event );
|
|
|
|
} }
|
|
|
|
onDragEnd={ ( event ) => {
|
|
|
|
setIsDragging( false );
|
|
|
|
onDragEnd( event );
|
|
|
|
} }
|
|
|
|
onDragOver={ onDragOver }
|
2022-10-05 19:07:03 +00:00
|
|
|
>
|
|
|
|
{ orderedChildren.map( ( child, childIndex ) => {
|
|
|
|
const isToolbarVisible = child.key === activeToolbarKey;
|
2022-10-12 18:16:22 +00:00
|
|
|
const isCoverItem = ( childIndex === 0 ) as boolean;
|
2022-10-05 19:07:03 +00:00
|
|
|
|
|
|
|
return cloneElement(
|
|
|
|
child,
|
|
|
|
{
|
|
|
|
isCover: isCoverItem,
|
|
|
|
className: classnames( {
|
|
|
|
'is-toolbar-visible': isToolbarVisible,
|
|
|
|
} ),
|
|
|
|
onClick: () => {
|
|
|
|
setActiveToolbarKey(
|
|
|
|
isToolbarVisible
|
|
|
|
? null
|
|
|
|
: ( child.key as string )
|
|
|
|
);
|
|
|
|
},
|
|
|
|
onBlur: (
|
|
|
|
event: React.FocusEvent< HTMLDivElement >
|
|
|
|
) => {
|
|
|
|
if (
|
2022-10-12 18:16:22 +00:00
|
|
|
isDragging ||
|
2022-10-05 19:07:03 +00:00
|
|
|
event.currentTarget.contains(
|
|
|
|
event.relatedTarget
|
|
|
|
) ||
|
|
|
|
( event.relatedTarget &&
|
|
|
|
(
|
|
|
|
event.relatedTarget as Element
|
|
|
|
).closest(
|
2022-10-12 18:16:22 +00:00
|
|
|
'.media-modal, .components-modal__frame'
|
2022-10-05 19:07:03 +00:00
|
|
|
) )
|
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
setActiveToolbarKey( null );
|
|
|
|
},
|
|
|
|
},
|
|
|
|
isToolbarVisible ? (
|
|
|
|
<ImageGalleryToolbar
|
|
|
|
childIndex={ childIndex }
|
|
|
|
lastChild={
|
|
|
|
childIndex === orderedChildren.length - 1
|
|
|
|
}
|
|
|
|
moveItem={ (
|
|
|
|
fromIndex: number,
|
|
|
|
toIndex: number
|
|
|
|
) => {
|
|
|
|
updateOrderedChildren(
|
|
|
|
moveIndex< ImageGalleryChild >(
|
|
|
|
fromIndex,
|
|
|
|
toIndex,
|
|
|
|
orderedChildren
|
|
|
|
)
|
|
|
|
);
|
|
|
|
} }
|
|
|
|
removeItem={ ( removeIndex: number ) => {
|
|
|
|
onRemove( {
|
|
|
|
removeIndex,
|
|
|
|
removedItem:
|
|
|
|
orderedChildren[ removeIndex ],
|
|
|
|
} );
|
|
|
|
updateOrderedChildren(
|
|
|
|
removeItem(
|
|
|
|
orderedChildren,
|
|
|
|
removeIndex
|
|
|
|
)
|
|
|
|
);
|
|
|
|
} }
|
|
|
|
replaceItem={ (
|
|
|
|
replaceIndex: number,
|
2022-10-12 18:16:22 +00:00
|
|
|
media: { id: number } & MediaItem
|
2022-10-05 19:07:03 +00:00
|
|
|
) => {
|
2022-10-12 18:16:22 +00:00
|
|
|
onReplace( { replaceIndex, media } );
|
|
|
|
setOrderedChildren(
|
2022-10-05 19:07:03 +00:00
|
|
|
replaceItem< {
|
|
|
|
src: string;
|
|
|
|
alt: string;
|
|
|
|
} >( orderedChildren, replaceIndex, {
|
2022-10-12 18:16:22 +00:00
|
|
|
src: media.url as string,
|
|
|
|
alt: media.alt as string,
|
2022-10-05 19:07:03 +00:00
|
|
|
} )
|
|
|
|
);
|
|
|
|
} }
|
2022-10-12 18:16:22 +00:00
|
|
|
setToolBarItem={ ( toolBarItem ) => {
|
|
|
|
onSelectAsCover( activeToolbarKey );
|
|
|
|
setActiveToolbarKey( toolBarItem );
|
|
|
|
} }
|
2022-10-05 19:07:03 +00:00
|
|
|
MediaUploadComponent={ MediaUploadComponent }
|
|
|
|
/>
|
|
|
|
) : null
|
|
|
|
);
|
|
|
|
} ) }
|
2022-09-06 19:46:32 +00:00
|
|
|
</Sortable>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|