Images Product management MVP 1.0 (#34769)
* Add image section # Conflicts: # plugins/woocommerce-admin/client/products/add-product-page.tsx * Add `keepSpaceWhenDragging` to sortable # Conflicts: # packages/js/components/src/sortable/sortable-item.tsx # packages/js/components/src/sortable/sortable.tsx # Conflicts: # packages/js/components/src/sortable/sortable.tsx * Export ImageGalleryItem * Add props to `image-gallery` # Conflicts: # packages/js/components/src/image-gallery/image-gallery-item.tsx # packages/js/components/src/image-gallery/image-gallery.tsx * Changed `media-uploader` label * Add changelogs * Fix image-gallery and sortable components # Conflicts: # packages/js/components/src/sortable/sortable-item.tsx # packages/js/components/src/sortable/sortable.tsx # Conflicts: # packages/js/components/src/image-gallery/image-gallery.tsx # packages/js/components/src/sortable/sortable.tsx * Set gallery min-height * Add onOrderChange * Show images section edit-product # Conflicts: # plugins/woocommerce-admin/client/products/edit-product-page.tsx # Conflicts: # plugins/woocommerce-admin/client/products/edit-product-page.tsx # Conflicts: # plugins/woocommerce-admin/client/products/edit-product-page.tsx * Fix styles * Fix styles * Fix image alt * Fix TS any * Add prop `onDragOver` # Conflicts: # packages/js/components/src/image-gallery/image-gallery.tsx * Fix styles * Fix padding * Fix image area min-height * Fix subtitle copy * Fix image margin * Change `draggedImageId` * Fix `setDraggedImageId` reset * Rename `isRemoveZoneVisible` * Add `CardBody` and remove redundant `setValue` * Fix card styles * Remove `getUniqueImages` * Use url as a key when there is no image id * Fix `orderedImages` * Add hover to gallery images * Fix gallery arrows and set cover problems * Altering blur handler to prevent toolbar closure on media modal * Fix toolbar drag and drop * Add replace fn * Restoring modal class for blur function * Fix storybook * Ensuring onBlur doesn't happen while dragging, resolving issue in Firefox * Adding expected event object to drag callbacks * Fix image size * Fix lint * Another fix lint * Update plugins/woocommerce-admin/client/products/sections/images-section.tsx Co-authored-by: Joshua T Flowers <joshuatf@gmail.com> * Fix `draggedImageId` default value * Fix toolbar icon style * Rename consts * Update pnpm-lock.yaml Co-authored-by: Fernando Marichal <contacto@fernandomarichal.com> Co-authored-by: Joel <dygerati@gmail.com> Co-authored-by: Joshua T Flowers <joshuatf@gmail.com>
This commit is contained in:
parent
1d187b70ca
commit
98162b9d42
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Images Product management
|
|
@ -3,17 +3,25 @@
|
|||
width: 146px;
|
||||
position: relative;
|
||||
|
||||
&.is-toolbar-visible{
|
||||
&.is-toolbar-visible {
|
||||
img {
|
||||
border: 2px solid #007cba;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.is-toolbar-visible){
|
||||
img:hover {
|
||||
border: 1.5px solid #007cba;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
border: 1px solid $gray-200;
|
||||
border-radius: 3px;
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 146px;
|
||||
}
|
||||
|
||||
.woocommerce-pill {
|
||||
|
@ -33,6 +41,7 @@
|
|||
|
||||
svg {
|
||||
width: 24px;
|
||||
margin-top: $gap-smallest;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import { SortableHandle, NonSortableItem } from '../sortable';
|
|||
import { ConditionalWrapper } from '../util/conditional-wrapper';
|
||||
|
||||
export type ImageGalleryItemProps = {
|
||||
id?: string;
|
||||
alt: string;
|
||||
isCover?: boolean;
|
||||
src: string;
|
||||
|
@ -22,6 +23,7 @@ export type ImageGalleryItemProps = {
|
|||
} & React.HTMLAttributes< HTMLDivElement >;
|
||||
|
||||
export const ImageGalleryItem: React.FC< ImageGalleryItemProps > = ( {
|
||||
id,
|
||||
alt,
|
||||
isCover = false,
|
||||
src,
|
||||
|
@ -49,11 +51,11 @@ export const ImageGalleryItem: React.FC< ImageGalleryItemProps > = ( {
|
|||
{ isCover ? (
|
||||
<>
|
||||
<Pill>{ __( 'Cover', 'woocommerce' ) }</Pill>
|
||||
<img alt={ alt } src={ src } />
|
||||
<img alt={ alt } src={ src } id={ id } />
|
||||
</>
|
||||
) : (
|
||||
<SortableHandle>
|
||||
<img alt={ alt } src={ src } />
|
||||
<img alt={ alt } src={ src } id={ id } />
|
||||
</SortableHandle>
|
||||
) }
|
||||
</div>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import { createElement } from '@wordpress/element';
|
||||
import { Toolbar, ToolbarButton, ToolbarGroup } from '@wordpress/components';
|
||||
import { chevronRight, chevronLeft, trash } from '@wordpress/icons';
|
||||
import { MediaUpload } from '@wordpress/media-utils';
|
||||
import { MediaItem, MediaUpload } from '@wordpress/media-utils';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
/**
|
||||
|
@ -20,8 +20,7 @@ export type ImageGalleryToolbarProps = {
|
|||
removeItem: ( removeIndex: number ) => void;
|
||||
replaceItem: (
|
||||
replaceIndex: number,
|
||||
newSrc: string,
|
||||
newAlt: string
|
||||
media: { id: number } & MediaItem
|
||||
) => void;
|
||||
setToolBarItem: ( key: string | null ) => void;
|
||||
lastChild: boolean;
|
||||
|
@ -62,7 +61,9 @@ export const ImageGalleryToolbar: React.FC< ImageGalleryToolbarProps > = ( {
|
|||
{ ! isCoverItem && (
|
||||
<ToolbarGroup>
|
||||
<ToolbarButton
|
||||
icon={ () => <SortableHandle /> }
|
||||
icon={ () => (
|
||||
<SortableHandle itemIndex={ childIndex } />
|
||||
) }
|
||||
label={ __( 'Drag', 'woocommerce' ) }
|
||||
/>
|
||||
<ToolbarButton
|
||||
|
@ -91,7 +92,7 @@ export const ImageGalleryToolbar: React.FC< ImageGalleryToolbarProps > = ( {
|
|||
<ToolbarGroup className="woocommerce-image-gallery__toolbar-media">
|
||||
<MediaUploadComponent
|
||||
onSelect={ ( media ) =>
|
||||
replaceItem( childIndex, media.url, media.alt )
|
||||
replaceItem( childIndex, media as MediaItem )
|
||||
}
|
||||
allowedTypes={ [ 'image' ] }
|
||||
render={ ( { open } ) => (
|
||||
|
|
|
@ -7,8 +7,9 @@ import {
|
|||
useState,
|
||||
useEffect,
|
||||
} from '@wordpress/element';
|
||||
import { DragEventHandler } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { MediaUpload } from '@wordpress/media-utils';
|
||||
import { MediaItem, MediaUpload } from '@wordpress/media-utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -27,23 +28,32 @@ export type ImageGalleryProps = {
|
|||
} ) => void;
|
||||
onReplace?: ( props: {
|
||||
replaceIndex: number;
|
||||
previousItem: ImageGalleryChild;
|
||||
media: { id: number } & MediaItem;
|
||||
} ) => void;
|
||||
onSelectAsCover?: ( itemId: string | null ) => void;
|
||||
onOrderChange?: ( items: ImageGalleryChild[] ) => void;
|
||||
MediaUploadComponent?: MediaUploadComponentType;
|
||||
onDragStart?: DragEventHandler< HTMLDivElement >;
|
||||
onDragEnd?: DragEventHandler< HTMLDivElement >;
|
||||
onDragOver?: DragEventHandler< HTMLLIElement >;
|
||||
} & React.HTMLAttributes< HTMLDivElement >;
|
||||
|
||||
export const ImageGallery: React.FC< ImageGalleryProps > = ( {
|
||||
children,
|
||||
columns = 4,
|
||||
onSelectAsCover = () => null,
|
||||
onOrderChange = () => null,
|
||||
onRemove = () => null,
|
||||
onReplace = () => null,
|
||||
MediaUploadComponent = MediaUpload,
|
||||
onDragStart = () => null,
|
||||
onDragEnd = () => null,
|
||||
onDragOver = () => null,
|
||||
}: ImageGalleryProps ) => {
|
||||
const [ activeToolbarKey, setActiveToolbarKey ] = useState< string | null >(
|
||||
null
|
||||
);
|
||||
const [ isDragging, setIsDragging ] = useState< boolean >( false );
|
||||
const [ orderedChildren, setOrderedChildren ] = useState<
|
||||
ImageGalleryChild[]
|
||||
>( [] );
|
||||
|
@ -62,7 +72,7 @@ export const ImageGallery: React.FC< ImageGalleryProps > = ( {
|
|||
|
||||
const updateOrderedChildren = ( items: ImageGalleryChild[] ) => {
|
||||
setOrderedChildren( items );
|
||||
onOrderChange( orderedChildren );
|
||||
onOrderChange( items );
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -77,10 +87,19 @@ export const ImageGallery: React.FC< ImageGalleryProps > = ( {
|
|||
onOrderChange={ ( items ) => {
|
||||
updateOrderedChildren( items );
|
||||
} }
|
||||
onDragStart={ ( event ) => {
|
||||
setIsDragging( true );
|
||||
onDragStart( event );
|
||||
} }
|
||||
onDragEnd={ ( event ) => {
|
||||
setIsDragging( false );
|
||||
onDragEnd( event );
|
||||
} }
|
||||
onDragOver={ onDragOver }
|
||||
>
|
||||
{ orderedChildren.map( ( child, childIndex ) => {
|
||||
const isToolbarVisible = child.key === activeToolbarKey;
|
||||
const isCoverItem = childIndex === 0;
|
||||
const isCoverItem = ( childIndex === 0 ) as boolean;
|
||||
|
||||
return cloneElement(
|
||||
child,
|
||||
|
@ -100,6 +119,7 @@ export const ImageGallery: React.FC< ImageGalleryProps > = ( {
|
|||
event: React.FocusEvent< HTMLDivElement >
|
||||
) => {
|
||||
if (
|
||||
isDragging ||
|
||||
event.currentTarget.contains(
|
||||
event.relatedTarget
|
||||
) ||
|
||||
|
@ -107,7 +127,7 @@ export const ImageGallery: React.FC< ImageGalleryProps > = ( {
|
|||
(
|
||||
event.relatedTarget as Element
|
||||
).closest(
|
||||
'.components-modal__frame'
|
||||
'.media-modal, .components-modal__frame'
|
||||
) )
|
||||
) {
|
||||
return;
|
||||
|
@ -148,25 +168,23 @@ export const ImageGallery: React.FC< ImageGalleryProps > = ( {
|
|||
} }
|
||||
replaceItem={ (
|
||||
replaceIndex: number,
|
||||
newSrc: string,
|
||||
newAlt: string
|
||||
media: { id: number } & MediaItem
|
||||
) => {
|
||||
onReplace( {
|
||||
replaceIndex,
|
||||
previousItem:
|
||||
orderedChildren[ replaceIndex ],
|
||||
} );
|
||||
updateOrderedChildren(
|
||||
onReplace( { replaceIndex, media } );
|
||||
setOrderedChildren(
|
||||
replaceItem< {
|
||||
src: string;
|
||||
alt: string;
|
||||
} >( orderedChildren, replaceIndex, {
|
||||
src: newSrc,
|
||||
alt: newAlt,
|
||||
src: media.url as string,
|
||||
alt: media.alt as string,
|
||||
} )
|
||||
);
|
||||
} }
|
||||
setToolBarItem={ setActiveToolbarKey }
|
||||
setToolBarItem={ ( toolBarItem ) => {
|
||||
onSelectAsCover( activeToolbarKey );
|
||||
setActiveToolbarKey( toolBarItem );
|
||||
} }
|
||||
MediaUploadComponent={ MediaUploadComponent }
|
||||
/>
|
||||
) : null
|
||||
|
|
|
@ -18,7 +18,7 @@ export { FormSection } from './form-section';
|
|||
export type { FormContext, FormRef, FormErrors } from './form';
|
||||
export { default as FilterPicker } from './filter-picker';
|
||||
export { H, Section } from './section';
|
||||
export { ImageGallery } from './image-gallery';
|
||||
export { ImageGallery, ImageGalleryItem } from './image-gallery';
|
||||
export { default as ImageUpload } from './image-upload';
|
||||
export { default as Link } from './link';
|
||||
export { default as List } from './list';
|
||||
|
|
|
@ -19,7 +19,7 @@ type MediaUploaderProps = {
|
|||
buttonText?: string;
|
||||
hasDropZone?: boolean;
|
||||
icon?: JSX.Element;
|
||||
label?: string;
|
||||
label?: string | JSX.Element;
|
||||
maxUploadFileSize?: number;
|
||||
MediaUploadComponent?: < T extends boolean = false >(
|
||||
props: MediaUpload.Props< T >
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
.woocommerce-media-uploader {
|
||||
text-align: center;
|
||||
.woocommerce-media-uploader__label {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin-bottom: $gap-large;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,9 +13,13 @@ import { SortableContextType } from './types';
|
|||
|
||||
type SortableHandleProps = {
|
||||
children?: React.ReactNode;
|
||||
itemIndex?: number;
|
||||
};
|
||||
|
||||
export const SortableHandle = ( { children }: SortableHandleProps ) => {
|
||||
export const SortableHandle = ( {
|
||||
children,
|
||||
itemIndex,
|
||||
}: SortableHandleProps ) => {
|
||||
const { onDragStart, onDragEnd }: SortableContextType =
|
||||
useContext( SortableContext );
|
||||
|
||||
|
@ -25,6 +29,7 @@ export const SortableHandle = ( { children }: SortableHandleProps ) => {
|
|||
draggable
|
||||
onDragStart={ onDragStart }
|
||||
onDragEnd={ onDragEnd }
|
||||
data-index={ itemIndex }
|
||||
>
|
||||
{ children ? children : <DraggableIcon /> }
|
||||
</div>
|
||||
|
|
|
@ -14,6 +14,7 @@ import { ProductFormActions } from './product-form-actions';
|
|||
import { ProductDetailsSection } from './sections/product-details-section';
|
||||
import { PricingSection } from './sections/pricing-section';
|
||||
import { ProductShippingSection } from './sections/product-shipping-section';
|
||||
import { ImagesSection } from './sections/images-section';
|
||||
import './product-page.scss';
|
||||
import { validate } from './product-validation';
|
||||
import { AttributesSection } from './sections/attributes-section';
|
||||
|
@ -33,6 +34,7 @@ const AddProductPage: React.FC = () => {
|
|||
<ProductFormLayout>
|
||||
<ProductDetailsSection />
|
||||
<PricingSection />
|
||||
<ImagesSection />
|
||||
<ProductShippingSection />
|
||||
<AttributesSection />
|
||||
<ProductFormActions />
|
||||
|
|
|
@ -22,6 +22,7 @@ import { ProductFormActions } from './product-form-actions';
|
|||
import { ProductDetailsSection } from './sections/product-details-section';
|
||||
import { PricingSection } from './sections/pricing-section';
|
||||
import { ProductShippingSection } from './sections/product-shipping-section';
|
||||
import { ImagesSection } from './sections/images-section';
|
||||
import './product-page.scss';
|
||||
import { validate } from './product-validation';
|
||||
import { AttributesSection } from './sections/attributes-section';
|
||||
|
@ -127,6 +128,7 @@ const EditProductPage: React.FC = () => {
|
|||
<ProductFormLayout>
|
||||
<ProductDetailsSection />
|
||||
<PricingSection />
|
||||
<ImagesSection />
|
||||
<ProductShippingSection product={ product } />
|
||||
<AttributesSection />
|
||||
<ProductFormActions />
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<svg width="72" height="74" viewBox="0 0 72 74" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="20.71" y="1.5" width="49.6511" height="49.6511" rx="2.5" fill="#F6F7F7" stroke="#E0E0E0" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="4 9"/>
|
||||
<rect x="1.83381" y="18.0188" width="49.6511" height="49.6511" rx="2.5" transform="rotate(-8 1.83381 18.0188)" fill="white" stroke="#CCCCCC" stroke-width="3"/>
|
||||
<path d="M7.13392 54.1105L19.6924 42.4672L31.04 47.458L41.6316 34.4447L55.3999 44.0344" stroke="#CCCCCC" stroke-width="3" stroke-linejoin="round"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.1426 55.7073L65.1762 61.7426L59.1883 65.6305L48.1426 55.7073ZM48.1425 55.7077L55.9617 71.9997L59.1882 65.6309L48.1425 55.7077Z" fill="#BBBBBB"/>
|
||||
<path d="M65.1762 61.7426L65.7879 62.6847C66.1466 62.4518 66.342 62.0354 66.2917 61.6107C66.2415 61.186 65.9545 60.8266 65.5514 60.6838L65.1762 61.7426ZM48.1426 55.7073L48.5178 54.6485C48.0124 54.4694 47.4509 54.6725 47.177 55.1334C46.903 55.5944 46.993 56.1846 47.3919 56.5429L48.1426 55.7073ZM59.1883 65.6305L58.4376 66.4662C58.8154 66.8056 59.374 66.8493 59.8 66.5727L59.1883 65.6305ZM55.9617 71.9997L54.949 72.4857C55.134 72.8713 55.522 73.1183 55.9496 73.1229C56.3773 73.1275 56.7704 72.8888 56.9637 72.5073L55.9617 71.9997ZM48.1425 55.7077L48.8932 54.8721C48.4943 54.5137 47.8978 54.4873 47.4688 54.8088C47.0397 55.1304 46.8978 55.7103 47.1298 56.1937L48.1425 55.7077ZM59.1882 65.6309L60.1902 66.1386C60.4197 65.6855 60.3167 65.1347 59.9389 64.7953L59.1882 65.6309ZM65.5514 60.6838L48.5178 54.6485L47.7674 56.7661L64.8011 62.8014L65.5514 60.6838ZM59.8 66.5727L65.7879 62.6847L64.5645 60.8005L58.5766 64.6884L59.8 66.5727ZM47.3919 56.5429L58.4376 66.4662L59.939 64.7949L48.8933 54.8717L47.3919 56.5429ZM56.9744 71.5136L49.1552 55.2216L47.1298 56.1937L54.949 72.4857L56.9744 71.5136ZM58.1861 65.1233L54.9596 71.492L56.9637 72.5073L60.1902 66.1386L58.1861 65.1233ZM47.3918 56.5433L58.4375 66.4665L59.9389 64.7953L48.8932 54.8721L47.3918 56.5433Z" fill="#BBBBBB"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
|
@ -0,0 +1,77 @@
|
|||
.woocommerce-product-form {
|
||||
&__images {
|
||||
box-shadow: unset;
|
||||
.woocommerce-media-uploader {
|
||||
img {
|
||||
margin-bottom: $gap-small;
|
||||
width: $gap-larger + $gap-larger;
|
||||
}
|
||||
}
|
||||
.woocommerce-product-form__image-drop-zone {
|
||||
> .components-card__body {
|
||||
padding: 0 0 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
&__images.has-images {
|
||||
margin-bottom: 0;
|
||||
.woocommerce-media-uploader {
|
||||
border: 1px dashed $gray-400;
|
||||
border-radius: 2px;
|
||||
padding: 32px 0;
|
||||
img {
|
||||
display: none;
|
||||
}
|
||||
span {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
.woocommerce-sortable {
|
||||
min-height: 146px;
|
||||
margin-top: 0;
|
||||
margin-left: 0;
|
||||
@media ( max-width: 1034px ) {
|
||||
grid-template-columns: auto auto;
|
||||
}
|
||||
@media ( min-width: 1035px ) and ( max-width: 1280px ) {
|
||||
grid-template-columns: auto auto auto;
|
||||
}
|
||||
}
|
||||
.woocommerce-product-form__image-drop-zone {
|
||||
> .components-card__body {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
&__image-drop-zone {
|
||||
span {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
&__remove-image-drop-zone {
|
||||
color: $error-red;
|
||||
border: 1px dashed $error-red;
|
||||
border-radius: 2px;
|
||||
height: 146px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
.components-drop-zone__content {
|
||||
background-color: $error-red;
|
||||
}
|
||||
span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.icon-control {
|
||||
margin-right: $gap-smaller;
|
||||
fill: $error-red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import {
|
||||
Link,
|
||||
useFormContext,
|
||||
MediaUploader,
|
||||
ImageGallery,
|
||||
ImageGalleryItem,
|
||||
} from '@woocommerce/components';
|
||||
import { Card, CardBody, DropZone } from '@wordpress/components';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { Product } from '@woocommerce/data';
|
||||
import classnames from 'classnames';
|
||||
import { Icon, trash } from '@wordpress/icons';
|
||||
import { MediaItem } from '@wordpress/media-utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ProductSectionLayout } from '../layout/product-section-layout';
|
||||
import DragAndDrop from '../images/drag-and-drop.svg';
|
||||
import './images-section.scss';
|
||||
|
||||
type Image = MediaItem & {
|
||||
src: string;
|
||||
};
|
||||
|
||||
export const ImagesSection: React.FC = () => {
|
||||
const { getInputProps, setValue } = useFormContext< Product >();
|
||||
const images = ( getInputProps( 'images' ).value as Image[] ) || [];
|
||||
const [ isRemovingZoneVisible, setIsRemovingZoneVisible ] =
|
||||
useState< boolean >( false );
|
||||
const [ isRemoving, setIsRemoving ] = useState< boolean >( false );
|
||||
const [ draggedImageId, setDraggedImageId ] = useState< number | null >(
|
||||
null
|
||||
);
|
||||
|
||||
const toggleRemoveZone = () => {
|
||||
setIsRemovingZoneVisible( ! isRemovingZoneVisible );
|
||||
};
|
||||
|
||||
const orderImages = ( newOrder: JSX.Element[] ) => {
|
||||
const orderedImages = newOrder.map( ( image ) => {
|
||||
return images.find(
|
||||
( file ) => file.id === parseInt( image?.props?.id, 10 )
|
||||
);
|
||||
} );
|
||||
setValue( 'images', orderedImages );
|
||||
};
|
||||
|
||||
return (
|
||||
<ProductSectionLayout
|
||||
title={ __( 'Images', 'woocommerce' ) }
|
||||
description={
|
||||
<>
|
||||
<span>
|
||||
{ __(
|
||||
'For best results, use JPEG files that are 1000 by 1000 pixels or larger.',
|
||||
'woocommerce'
|
||||
) }
|
||||
</span>
|
||||
<Link
|
||||
className="woocommerce-form-section__header-link"
|
||||
href="https://woocommerce.com/posts/fast-high-quality-product-photos/"
|
||||
target="_blank"
|
||||
type="external"
|
||||
onClick={ () => {
|
||||
recordEvent( 'prepare_images_help' );
|
||||
} }
|
||||
>
|
||||
{ __( 'How should I prepare images?', 'woocommerce' ) }
|
||||
</Link>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Card
|
||||
className={ classnames( 'woocommerce-product-form__images', {
|
||||
'has-images': images.length > 0,
|
||||
} ) }
|
||||
>
|
||||
<CardBody>
|
||||
<ImageGallery
|
||||
onDragStart={ ( event ) => {
|
||||
const { id: imageId, dataset } =
|
||||
event.target as HTMLElement;
|
||||
if ( imageId ) {
|
||||
setDraggedImageId( parseInt( imageId, 10 ) );
|
||||
} else {
|
||||
const index = dataset?.index;
|
||||
if ( index ) {
|
||||
setDraggedImageId(
|
||||
images[ parseInt( index, 10 ) ]?.id
|
||||
);
|
||||
}
|
||||
}
|
||||
toggleRemoveZone();
|
||||
} }
|
||||
onDragEnd={ () => {
|
||||
if ( isRemoving && draggedImageId ) {
|
||||
setValue(
|
||||
'images',
|
||||
images.filter(
|
||||
( img ) => img.id !== draggedImageId
|
||||
)
|
||||
);
|
||||
setIsRemoving( false );
|
||||
setDraggedImageId( null );
|
||||
}
|
||||
toggleRemoveZone();
|
||||
} }
|
||||
onOrderChange={ orderImages }
|
||||
onReplace={ ( { replaceIndex, media } ) => {
|
||||
if (
|
||||
images.find(
|
||||
( img ) => media.id === img.id
|
||||
) === undefined
|
||||
) {
|
||||
images[ replaceIndex ] = media as Image;
|
||||
setValue( 'images', images );
|
||||
}
|
||||
} }
|
||||
>
|
||||
{ images.map( ( image ) => (
|
||||
<ImageGalleryItem
|
||||
key={ image.id || image.url }
|
||||
alt={ image.alt }
|
||||
src={ image.url || image.src }
|
||||
id={ `${ image.id }` }
|
||||
/>
|
||||
) ) }
|
||||
</ImageGallery>
|
||||
<div className="woocommerce-product-form__image-drop-zone">
|
||||
{ isRemovingZoneVisible ? (
|
||||
<CardBody>
|
||||
<div className="woocommerce-product-form__remove-image-drop-zone">
|
||||
<span>
|
||||
<Icon
|
||||
icon={ trash }
|
||||
size={ 20 }
|
||||
className="icon-control"
|
||||
/>
|
||||
{ __(
|
||||
'Drop here to remove',
|
||||
'woocommerce'
|
||||
) }
|
||||
</span>
|
||||
<DropZone
|
||||
onHTMLDrop={ () =>
|
||||
setIsRemoving( true )
|
||||
}
|
||||
onDrop={ () => setIsRemoving( true ) }
|
||||
label={ __(
|
||||
'Drop here to remove',
|
||||
'woocommerce'
|
||||
) }
|
||||
/>
|
||||
</div>
|
||||
</CardBody>
|
||||
) : (
|
||||
<CardBody>
|
||||
<MediaUploader
|
||||
onError={ () => null }
|
||||
onSelect={ ( file ) => {
|
||||
if (
|
||||
images.find(
|
||||
( img ) => file.id === img.id
|
||||
) === undefined
|
||||
) {
|
||||
setValue( 'images', [
|
||||
...images,
|
||||
file,
|
||||
] );
|
||||
}
|
||||
} }
|
||||
onUpload={ ( files ) => {
|
||||
if ( files[ 0 ].id ) {
|
||||
setValue( 'images', [
|
||||
...images,
|
||||
...files,
|
||||
] );
|
||||
}
|
||||
} }
|
||||
label={
|
||||
<>
|
||||
<img
|
||||
src={ DragAndDrop }
|
||||
alt="Completed"
|
||||
className="woocommerce-product-form__drag-and-drop-image"
|
||||
/>
|
||||
<span>
|
||||
{ __(
|
||||
'Drag images here or click to upload',
|
||||
'woocommerce'
|
||||
) }
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</CardBody>
|
||||
) }
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</ProductSectionLayout>
|
||||
);
|
||||
};
|
|
@ -69,6 +69,7 @@
|
|||
"@wordpress/i18n": "^4.3.1",
|
||||
"@wordpress/icons": "^8.1.0",
|
||||
"@wordpress/keycodes": "^3.3.1",
|
||||
"@wordpress/media-utils": "^4.6.0",
|
||||
"@wordpress/notices": "^3.3.2",
|
||||
"@wordpress/plugins": "^4.1.3",
|
||||
"@wordpress/primitives": "^3.1.1",
|
||||
|
@ -125,6 +126,7 @@
|
|||
"@types/wordpress__compose": "^4.0.1",
|
||||
"@types/wordpress__data": "^6.0.0",
|
||||
"@types/wordpress__data-controls": "^2.2.0",
|
||||
"@types/wordpress__media-utils": "^3.0.0",
|
||||
"@types/wordpress__notices": "^3.3.0",
|
||||
"@types/wordpress__plugins": "^3.0.0",
|
||||
"@types/qs": "^6.9.7",
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Images Product management
|
102
pnpm-lock.yaml
102
pnpm-lock.yaml
|
@ -1409,6 +1409,7 @@ importers:
|
|||
'@types/wordpress__compose': ^4.0.1
|
||||
'@types/wordpress__data': ^6.0.0
|
||||
'@types/wordpress__data-controls': ^2.2.0
|
||||
'@types/wordpress__media-utils': ^3.0.0
|
||||
'@types/wordpress__notices': ^3.3.0
|
||||
'@types/wordpress__plugins': ^3.0.0
|
||||
'@typescript-eslint/eslint-plugin': ^5.14.0
|
||||
|
@ -1455,6 +1456,7 @@ importers:
|
|||
'@wordpress/icons': ^8.1.0
|
||||
'@wordpress/jest-preset-default': ^8.0.1
|
||||
'@wordpress/keycodes': ^3.3.1
|
||||
'@wordpress/media-utils': ^4.6.0
|
||||
'@wordpress/notices': ^3.3.2
|
||||
'@wordpress/plugins': ^4.1.3
|
||||
'@wordpress/postcss-plugins-preset': ^1.6.0
|
||||
|
@ -1563,6 +1565,7 @@ importers:
|
|||
'@wordpress/i18n': 4.4.1
|
||||
'@wordpress/icons': 8.1.0
|
||||
'@wordpress/keycodes': 3.4.1
|
||||
'@wordpress/media-utils': 4.6.0
|
||||
'@wordpress/notices': 3.4.1_react@17.0.2
|
||||
'@wordpress/plugins': 4.2.1_react@17.0.2
|
||||
'@wordpress/primitives': 3.2.1
|
||||
|
@ -1619,6 +1622,7 @@ importers:
|
|||
'@types/wordpress__compose': 4.0.1
|
||||
'@types/wordpress__data': 6.0.0
|
||||
'@types/wordpress__data-controls': 2.2.0
|
||||
'@types/wordpress__media-utils': 3.0.0_sfoxds7t5ydpegc3knd667wn6m
|
||||
'@types/wordpress__notices': 3.3.0
|
||||
'@types/wordpress__plugins': 3.0.0_sfoxds7t5ydpegc3knd667wn6m
|
||||
'@typescript-eslint/eslint-plugin': 5.15.0_kjpgj5mwuhqsafyl367g3mx6ni
|
||||
|
@ -2353,7 +2357,7 @@ packages:
|
|||
/@babel/code-frame/7.12.11:
|
||||
resolution: {integrity: sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==}
|
||||
dependencies:
|
||||
'@babel/highlight': 7.16.10
|
||||
'@babel/highlight': 7.18.6
|
||||
dev: true
|
||||
|
||||
/@babel/code-frame/7.16.7:
|
||||
|
@ -2849,42 +2853,6 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@babel/helper-define-polyfill-provider/0.3.0_@babel+core@7.16.12:
|
||||
resolution: {integrity: sha512-7hfT8lUljl/tM3h+izTX/pO3W3frz2ok6Pk+gzys8iJqDfZrZy2pXjRTZAvG2YmfHun1X4q8/UZRLatMfqc5Tg==}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.4.0-0
|
||||
dependencies:
|
||||
'@babel/core': 7.16.12
|
||||
'@babel/helper-compilation-targets': 7.19.3_@babel+core@7.16.12
|
||||
'@babel/helper-module-imports': 7.18.6
|
||||
'@babel/helper-plugin-utils': 7.19.0
|
||||
'@babel/traverse': 7.19.3
|
||||
debug: 4.3.4
|
||||
lodash.debounce: 4.0.8
|
||||
resolve: 1.22.1
|
||||
semver: 6.3.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@babel/helper-define-polyfill-provider/0.3.0_@babel+core@7.17.8:
|
||||
resolution: {integrity: sha512-7hfT8lUljl/tM3h+izTX/pO3W3frz2ok6Pk+gzys8iJqDfZrZy2pXjRTZAvG2YmfHun1X4q8/UZRLatMfqc5Tg==}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.4.0-0
|
||||
dependencies:
|
||||
'@babel/core': 7.17.8
|
||||
'@babel/helper-compilation-targets': 7.19.3_@babel+core@7.17.8
|
||||
'@babel/helper-module-imports': 7.18.6
|
||||
'@babel/helper-plugin-utils': 7.19.0
|
||||
'@babel/traverse': 7.19.3
|
||||
debug: 4.3.4
|
||||
lodash.debounce: 4.0.8
|
||||
resolve: 1.22.1
|
||||
semver: 6.3.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@babel/helper-define-polyfill-provider/0.3.1_@babel+core@7.12.9:
|
||||
resolution: {integrity: sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==}
|
||||
peerDependencies:
|
||||
|
@ -3529,8 +3497,8 @@ packages:
|
|||
'@babel/core': ^7.0.0-0
|
||||
dependencies:
|
||||
'@babel/core': 7.12.9
|
||||
'@babel/helper-create-class-features-plugin': 7.19.0_@babel+core@7.12.9
|
||||
'@babel/helper-plugin-utils': 7.19.0
|
||||
'@babel/helper-create-class-features-plugin': 7.17.6_@babel+core@7.12.9
|
||||
'@babel/helper-plugin-utils': 7.18.9
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
@ -3542,8 +3510,8 @@ packages:
|
|||
'@babel/core': ^7.0.0-0
|
||||
dependencies:
|
||||
'@babel/core': 7.16.12
|
||||
'@babel/helper-create-class-features-plugin': 7.19.0_@babel+core@7.16.12
|
||||
'@babel/helper-plugin-utils': 7.19.0
|
||||
'@babel/helper-create-class-features-plugin': 7.17.6_@babel+core@7.16.12
|
||||
'@babel/helper-plugin-utils': 7.18.9
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
@ -7378,8 +7346,8 @@ packages:
|
|||
'@babel/core': ^7.0.0-0
|
||||
dependencies:
|
||||
'@babel/core': 7.16.12
|
||||
'@babel/helper-plugin-utils': 7.19.0
|
||||
'@babel/helper-validator-option': 7.18.6
|
||||
'@babel/helper-plugin-utils': 7.18.9
|
||||
'@babel/helper-validator-option': 7.16.7
|
||||
'@babel/plugin-transform-typescript': 7.16.8_@babel+core@7.16.12
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
@ -13573,7 +13541,7 @@ packages:
|
|||
resolution: {integrity: sha512-YYknwy0D0iOwKQgz9v8nOzt2J6l4gouBmDnWqUUznltOTaon+r8US8ky8HvN0tXvc38U9m6z/t2RsVsnd1zM0g==}
|
||||
dependencies:
|
||||
'@types/history': 4.7.11
|
||||
'@types/react': 17.0.40
|
||||
'@types/react': 17.0.50
|
||||
dev: true
|
||||
|
||||
/@types/react-syntax-highlighter/11.0.5:
|
||||
|
@ -13716,12 +13684,12 @@ packages:
|
|||
/@types/wordpress__block-editor/7.0.0_sfoxds7t5ydpegc3knd667wn6m:
|
||||
resolution: {integrity: sha512-JERpxKAQ7J07C2wtKxr+5ZE9NETIcpu0EiXuXka6Qmrq74oOypdy9jYdhMIYBDMOx4ptR3ne7edaFb2+1SBcqA==}
|
||||
dependencies:
|
||||
'@types/react': 17.0.40
|
||||
'@types/react': 17.0.50
|
||||
'@types/wordpress__blocks': 11.0.6_sfoxds7t5ydpegc3knd667wn6m
|
||||
'@types/wordpress__components': 19.10.1_sfoxds7t5ydpegc3knd667wn6m
|
||||
'@types/wordpress__data': 6.0.0
|
||||
'@types/wordpress__keycodes': 2.3.1
|
||||
'@wordpress/element': 4.14.0
|
||||
'@wordpress/element': 4.17.0
|
||||
react-autosize-textarea: 7.1.0_sfoxds7t5ydpegc3knd667wn6m
|
||||
transitivePeerDependencies:
|
||||
- react
|
||||
|
@ -13836,7 +13804,7 @@ packages:
|
|||
/@types/wordpress__rich-text/3.4.6:
|
||||
resolution: {integrity: sha512-MeLSATBHrcN3fp8cVylbpx+BKRJ1aootPNtbTblcUAHcuRo6avKu1kaDLxIZb/8YbsD+/3Wm8d1uldeNz9/lhw==}
|
||||
dependencies:
|
||||
'@types/react': 17.0.40
|
||||
'@types/react': 17.0.50
|
||||
'@types/wordpress__data': 6.0.0
|
||||
dev: true
|
||||
|
||||
|
@ -15295,13 +15263,6 @@ packages:
|
|||
'@babel/runtime': 7.19.0
|
||||
dev: false
|
||||
|
||||
/@wordpress/blob/3.4.1:
|
||||
resolution: {integrity: sha512-rGm7nXaxnsXStIu9v9IjbUOKtE9UzkvgYiJMX5SyVyzAGLOo2Aq759+JNRDLRR0RDkS6igH/G7qBXS6xSgLFgA==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.19.0
|
||||
dev: false
|
||||
|
||||
/@wordpress/block-serialization-default-parser/4.4.1:
|
||||
resolution: {integrity: sha512-0KGj9pG6rmZlOWtu0wbPNvCesGD9vj4k8+HiKbkYFu/GHSg10mfinTLZ638tByPLM6Q5OY3G3fNXGairGNpnSw==}
|
||||
engines: {node: '>=12'}
|
||||
|
@ -15317,13 +15278,13 @@ packages:
|
|||
dependencies:
|
||||
'@babel/runtime': 7.19.0
|
||||
'@wordpress/autop': 3.4.1
|
||||
'@wordpress/blob': 3.4.1
|
||||
'@wordpress/blob': 3.15.0
|
||||
'@wordpress/block-serialization-default-parser': 4.4.1
|
||||
'@wordpress/compose': 5.2.1_react@17.0.2
|
||||
'@wordpress/data': 6.15.0_react@17.0.2
|
||||
'@wordpress/deprecated': 3.16.0
|
||||
'@wordpress/dom': 3.4.1
|
||||
'@wordpress/element': 4.14.0
|
||||
'@wordpress/element': 4.17.0
|
||||
'@wordpress/hooks': 3.16.0
|
||||
'@wordpress/html-entities': 3.4.1
|
||||
'@wordpress/i18n': 4.16.0
|
||||
|
@ -15681,7 +15642,7 @@ packages:
|
|||
'@types/mousetrap': 1.6.9
|
||||
'@wordpress/deprecated': 3.16.0
|
||||
'@wordpress/dom': 3.16.0
|
||||
'@wordpress/element': 4.14.0
|
||||
'@wordpress/element': 4.17.0
|
||||
'@wordpress/is-shallow-equal': 4.16.0
|
||||
'@wordpress/keycodes': 3.16.0
|
||||
'@wordpress/priority-queue': 2.16.0
|
||||
|
@ -15703,7 +15664,7 @@ packages:
|
|||
'@types/mousetrap': 1.6.9
|
||||
'@wordpress/deprecated': 3.4.1
|
||||
'@wordpress/dom': 3.4.1
|
||||
'@wordpress/element': 4.8.0
|
||||
'@wordpress/element': 4.17.0
|
||||
'@wordpress/is-shallow-equal': 4.4.1
|
||||
'@wordpress/keycodes': 3.16.0
|
||||
'@wordpress/priority-queue': 2.4.1
|
||||
|
@ -15894,7 +15855,7 @@ packages:
|
|||
'@babel/runtime': 7.19.0
|
||||
'@wordpress/compose': 5.2.1_react@17.0.2
|
||||
'@wordpress/deprecated': 3.4.1
|
||||
'@wordpress/element': 4.14.0
|
||||
'@wordpress/element': 4.17.0
|
||||
'@wordpress/is-shallow-equal': 4.4.1
|
||||
'@wordpress/priority-queue': 2.4.1
|
||||
'@wordpress/redux-routine': 4.4.1_redux@4.2.0
|
||||
|
@ -16475,7 +16436,7 @@ packages:
|
|||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.19.0
|
||||
'@wordpress/element': 4.14.0
|
||||
'@wordpress/element': 4.17.0
|
||||
'@wordpress/primitives': 3.2.1
|
||||
dev: false
|
||||
|
||||
|
@ -16815,7 +16776,7 @@ packages:
|
|||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.19.0
|
||||
'@wordpress/element': 4.14.0
|
||||
'@wordpress/element': 4.17.0
|
||||
classnames: 2.3.1
|
||||
dev: false
|
||||
|
||||
|
@ -16926,7 +16887,7 @@ packages:
|
|||
'@wordpress/a11y': 3.10.0
|
||||
'@wordpress/compose': 5.2.1_react@17.0.2
|
||||
'@wordpress/data': 6.15.0_react@17.0.2
|
||||
'@wordpress/element': 4.14.0
|
||||
'@wordpress/element': 4.17.0
|
||||
'@wordpress/escape-html': 2.15.0
|
||||
'@wordpress/i18n': 4.16.0
|
||||
'@wordpress/keycodes': 3.16.0
|
||||
|
@ -18607,7 +18568,7 @@ packages:
|
|||
'@babel/core': ^7.0.0-0
|
||||
dependencies:
|
||||
'@babel/core': 7.16.12
|
||||
'@babel/helper-define-polyfill-provider': 0.3.0_@babel+core@7.16.12
|
||||
'@babel/helper-define-polyfill-provider': 0.3.3_@babel+core@7.16.12
|
||||
core-js-compat: 3.25.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
@ -18619,7 +18580,7 @@ packages:
|
|||
'@babel/core': ^7.0.0-0
|
||||
dependencies:
|
||||
'@babel/core': 7.17.8
|
||||
'@babel/helper-define-polyfill-provider': 0.3.0_@babel+core@7.17.8
|
||||
'@babel/helper-define-polyfill-provider': 0.3.3_@babel+core@7.17.8
|
||||
core-js-compat: 3.25.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
@ -22612,11 +22573,11 @@ packages:
|
|||
eslint-import-resolver-node: 0.3.6
|
||||
eslint-module-utils: 2.7.3_lkzaig2qiyp6elizstfbgvzhie
|
||||
has: 1.0.3
|
||||
is-core-module: 2.8.0
|
||||
is-core-module: 2.10.0
|
||||
is-glob: 4.0.3
|
||||
minimatch: 3.0.4
|
||||
minimatch: 3.1.2
|
||||
object.values: 1.1.5
|
||||
resolve: 1.20.0
|
||||
resolve: 1.22.1
|
||||
tsconfig-paths: 3.14.0
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-typescript
|
||||
|
@ -22674,11 +22635,11 @@ packages:
|
|||
eslint-import-resolver-node: 0.3.6
|
||||
eslint-module-utils: 2.7.3_54d5qjwnmqnp5634aqlesxatge
|
||||
has: 1.0.3
|
||||
is-core-module: 2.8.0
|
||||
is-core-module: 2.10.0
|
||||
is-glob: 4.0.3
|
||||
minimatch: 3.0.4
|
||||
minimatch: 3.1.2
|
||||
object.values: 1.1.5
|
||||
resolve: 1.20.0
|
||||
resolve: 1.22.1
|
||||
tsconfig-paths: 3.14.0
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-typescript
|
||||
|
@ -31531,6 +31492,7 @@ packages:
|
|||
resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==}
|
||||
dependencies:
|
||||
brace-expansion: 1.1.11
|
||||
dev: true
|
||||
|
||||
/minimatch/3.1.2:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
|
|
Loading…
Reference in New Issue