Skip to content

Conversation

@octaedro
Copy link
Contributor

@octaedro octaedro commented Sep 21, 2022

All Submissions:

Changes proposed in this Pull Request:

This PR adds the Image section to the product creation/edition.

Screen Capture on 2022-09-22 at 17-42-32

Detail:

  • It adds an Images section.
  • It adds the upload image component and a gallery to see and order the images.

The following functionalities are not included here, but they will be added in a follow-up PR.

  • Clicking anywhere on the card (except for buttons) opens the file picker.
  • The addition of a toolbar from the Gallery block in Gutenberg to manage gallery images.

Closes #34759.

How to test the changes in this Pull Request:

  1. Checkout this branch.
  2. Grab one (or more) image from your computer and drop it in the Drag images here or click to upload area.
  3. Verify that the image has been uploaded correctly and is now being shown on the screen.
  4. Verify that the Drag images here or click to upload area is smaller when there is at least one image loaded.
  5. Upload another image and change the order of the images.
  6. Then save the product. Verify that the new order for the images is saved correctly.
  7. Press Choose images and select an image that has been uploaded before.
  8. Verify that the selected image is added to the image gallery component.
  9. Grab an image from the gallery and verify that the area Drop here to remove is now visible.
  10. Drop the image in that area and verify that the image is removed from the gallery.

Other information:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your changes, as applicable?
  • Have you successfully run tests with your changes locally?
  • Have you created a changelog file for each project being changed, ie pnpm changelog add --filter=<project>?

FOR PR REVIEWER ONLY:

  • I have reviewed that everything is sanitized/escaped appropriately for any SQL or XSS injection possibilities. I made sure Linting is not ignored or disabled.

@github-actions github-actions bot added focus: react admin package: @woocommerce/components issues related to @woocommerce/components plugin: woocommerce Issues related to the WooCommerce Core plugin. release: highlight Issues that have a high user impact and need to be discussed/paid attention to. labels Sep 21, 2022
@github-actions
Copy link
Contributor

github-actions bot commented Sep 21, 2022

Test Results Summary

Commit SHA: d64d689

Test 🧪Passed ✅Failed 🚨Broken 🚧Skipped ⏭️Unknown ❔Total 📊Duration ⏱️
API Tests18800201900m 46s
E2E Tests127428014111m 31s

⚠️ Warning

Please address the following issues prior to merging this pull request:
  • FAILED/BROKEN TESTS. There were failed and/or broken API and E2E tests.
  • INCOMPLETE E2E TEST RUN. We have a total of 189 E2E tests, but only 141 were executed. Note that in CI, E2E tests automatically end when they encounter too many failures.

To view the full API test report, click here.
To view the full E2E test report, click here.
To view all test reports, visit the WooCommerce Test Reports Dashboard.

@octaedro octaedro changed the title Add/39 image section details Images Product management MVP 1.0 Sep 22, 2022
@octaedro octaedro force-pushed the add/39_image_section_details branch from 1cea4c0 to 0052fda Compare September 22, 2022 15:57
@github-actions github-actions bot removed the release: highlight Issues that have a high user impact and need to be discussed/paid attention to. label Sep 22, 2022
@octaedro octaedro requested a review from a team September 22, 2022 21:20
@octaedro octaedro marked this pull request as ready for review September 22, 2022 21:20
Copy link
Contributor

@joshuatf joshuatf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice implementation, @octaedro! Left a few suggestions on where we can move logic.

export const ImageGallery: React.FC< ImageGalleryProps > = ( {
children,
columns = 4,
onDragStart = () => null,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Thanks for adding these in. Should we add onDragOver as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea! I added it in the commit 58c89b2

&__images {
margin-bottom: 0;
box-shadow: unset;
.woocommerce-media-uploader {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should any of these styles be moved to the MediaUploader component? Or are they specific to the product form?

Copy link
Contributor Author

@octaedro octaedro Sep 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion! I moved a few lines to the package in commit a44aaa6. I left the image and the code related to that in the client because it was something specific to this implementation.

}
}
&__images.has-images {
.woocommerce-media-uploader {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would leave this code here, since these styles will be applied depending on whether the Card that contains the component MediaUploader and ImageGallery has a class or not.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Referring to the has-images class? This could be something useful inside that component as well.

But I guess the real question is do we want similar behavior to this in most of our other media uploader components.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, if it's ok with you I'll tackle this in a follow-up PR.

margin-top: 0;
}
}
.woocommerce-sortable {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question here. (under the ImageGallery component I would image)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these styles also depend on whether the parent component has a class or not, I would leave these styles here.

recordEvent( 'prepare_images_help' );
} }
>
{ __( 'How to prepare images?', 'woocommerce' ) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like it might be a sentence fragment. Should we change it to How to prepare images without the ? or How should I prepare images??

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch! I changed the copy in commit 2cf569d.

setShowRemoveZone( ! showRemoveZone );
};

const setImageToRemove = ( image: string ) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of setting the image to remove later, could we just setValue( 'images', newImages ); here after filtering out the removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's a good idea, and it was my first thought too. The problem I found was that it would remove the image before triggering the onDragEnd here.
That method (among other things) removes the class is-draggin in the Sortable component. So it would create a few oddities.

const setImageToRemove = ( image: string ) => {
const tempNode = document.createElement( 'div' );
tempNode.innerHTML = image;
const imageId = tempNode.getElementsByTagName( 'img' )[ 0 ].id;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think instead of retrieving this ID via selectors, we could set the ID of the dragged element on start. For example:

onDragStart={ ( image ) => setDraggedImageId( image.key ) }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if I understood this suggestion correctly. When a user grabs an image, we don't know if the user is wanting to modify the order of the images or remove that image. If the user wants only to change the order of the images, we wouldn't need to save that value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's okay if they don't end up using the draggedImageId; we can set it to null later. But at least this way we can just used the ID stored in state instead of using dom query selectors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it! I like the idea! I changed it in commit ddccb5b

key={ image.id }
alt={ image.alt }
src={ image.url || image.src }
id={ `${ image.id }` }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that we could forego the ID prop here and just opt to use the key to keep props down if we implement my above suggestion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now we're not presenting the key when we render an ImageGalleryItem item. The rendered id is what we use to pair our list when the items are reordered or to delete an image.

};

const orderImages = ( newOrder: JSX.Element[] ) => {
const orderedImages = newOrder.map( ( image ) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to just directly call setValue( 'images', newOrder );? Does the data differ pretty significantly?

I'd consider this a shortcoming in the onOrderChange callback if so. Not a blocker for this PR if that's the case, but would love to open an issue to make this easier.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is not possible now. The onOrderChange method returns the items and we are saving another kind of object under images.
I think that it would be a great addition to make the method onOrderChange return the same type of object we will use later.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea! I've been looking on feedback on this and @louwie17 had a similar request.

@octaedro octaedro force-pushed the add/39_image_section_details branch from 7b05648 to a13f75d Compare September 28, 2022 18:53
@octaedro
Copy link
Contributor Author

Thank you @joshuatf for your review!
I just addressed the changes you suggested and answered the questions you asked.
Could you take another look at this PR?

@octaedro octaedro requested a review from joshuatf September 30, 2022 00:43
Copy link
Contributor

@joshuatf joshuatf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@octaedro I'm having trouble getting this branch started again. I'm seeing the old TS errors that we were hitting before the latest fixes in trunk. Could you give this a rebase?

}
}
&__images.has-images {
.woocommerce-media-uploader {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Referring to the has-images class? This could be something useful inside that component as well.

But I guess the real question is do we want similar behavior to this in most of our other media uploader components.

};

const orderImages = ( newOrder: JSX.Element[] ) => {
const orderedImages = newOrder.map( ( image ) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea! I've been looking on feedback on this and @louwie17 had a similar request.

export const ImagesSection: React.FC = () => {
const { getInputProps, setValue } = useFormContext< Product >();
const images = ( getInputProps( 'images' ).value as Image[] ) || [];
const [ showRemoveZone, setShowRemoveZone ] = useState( false );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpicky but isRemoveZoneVisible to avoid prefixing this with a verb.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea! I renamed the const in the commit 662d29c

const [ showRemoveZone, setShowRemoveZone ] = useState( false );
const [ imageIdToRemove, setImageIdToRemove ] = useState< number >( 0 );

const getUniqueImages = ( files: Image[] ) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to extract this to a utils file so we can both test it and minimize the logic in this component.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this, if it is ok with you I'll move the method in the same follow-up PR to refactor the code.

const setImageToRemove = ( image: string ) => {
const tempNode = document.createElement( 'div' );
tempNode.innerHTML = image;
const imageId = tempNode.getElementsByTagName( 'img' )[ 0 ].id;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's okay if they don't end up using the draggedImageId; we can set it to null later. But at least this way we can just used the ID stored in state instead of using dom query selectors.

images.push( image );
}
} );
setValue( 'images', images );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This getUniqueImages function has a side effect of setValue which feels out of place. We're also calling setValue below again after calling getUniqueImages.

								setValue(
									'images',
									getUniqueImages( [ file ] )
								)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch! I removed the setValue( 'images', images ); inside the function in commit 5ceceaa

files.forEach( ( image: Image ) => {
if (
image.id &&
images.find( ( file ) => file.id === image.id ) === undefined
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm having some trouble understanding what the purpose of the getUniqueImages function really does. Is this just to guarantee the same image is not selected twice?

What happens if someone selects the same image twice? Nothing happens?

If we really need this, we might also want to consider storing a memoized version of images so we don't have nested loops.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think that we really don't need the method getUniqueImages. I removed it in commit 372dea0

@octaedro octaedro force-pushed the add/39_image_section_details branch 2 times, most recently from 46f1585 to cfabfe8 Compare October 6, 2022 16:43
@github-actions github-actions bot added the release: highlight Issues that have a high user impact and need to be discussed/paid attention to. label Oct 6, 2022
@octaedro octaedro force-pushed the add/39_image_section_details branch from 73f73f1 to 215065f Compare October 7, 2022 19:34
@github-actions github-actions bot removed the release: highlight Issues that have a high user impact and need to be discussed/paid attention to. label Oct 7, 2022
@octaedro octaedro requested a review from joshuatf October 10, 2022 14:47
joshuatf
joshuatf previously approved these changes Oct 11, 2022
Copy link
Contributor

@joshuatf joshuatf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fantastic work, @octaedro! The image section is testing really well for me. Love the work on the deletion drop zone.

Left a couple very minor comments, but pre-approving.

I think this PR might have an outdated lock file, but feel free to ping me on Slack for re-approval as soon as that's updated 😄

useState< boolean >( false );
const [ isRemove, setIsRemove ] = useState< boolean >( false );
const [ draggedImageId, setDraggedImageId ] = useState< number | null >(
0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this initially be null?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call! I changed it in the commit 15b342d

onDragStart={ ( event ) => {
const { id: imageId, dataset } = event.target as HTMLElement;
if ( imageId ) {
setDraggedImageId( parseInt( imageId, 10 ) );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nicely done!

const { id: imageId, dataset } = event.target as HTMLElement;
if ( imageId ) {
setDraggedImageId( parseInt( imageId, 10 ) );
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious - what scenario do we not have imageId and end up inside this else?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That happens when the image to move is grabbed by the icon in the toolbar. At that point, we don't have the image id, and this is why we use the image index (in the images list), to get the image id.

Screen Shot 2022-10-12 at 11 49 17

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see. Thanks for the explanation!

# Conflicts:
#	plugins/woocommerce-admin/client/products/add-product-page.tsx
# Conflicts:
#	packages/js/components/src/sortable/sortable-item.tsx
#	packages/js/components/src/sortable/sortable.tsx

# Conflicts:
#	packages/js/components/src/sortable/sortable.tsx
# Conflicts:
#	packages/js/components/src/image-gallery/image-gallery-item.tsx
#	packages/js/components/src/image-gallery/image-gallery.tsx
# 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
Copy link
Contributor

@joshuatf joshuatf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing well! Thanks for minor fix ups and the explanation around the imageId!

@octaedro octaedro merged commit 98162b9 into trunk Oct 12, 2022
@octaedro octaedro deleted the add/39_image_section_details branch October 12, 2022 18:16
@github-actions github-actions bot added this to the 7.1.0 milestone Oct 12, 2022
@github-actions
Copy link
Contributor

Hi @octaedro, thanks for merging this pull request. Please take a look at these follow-up tasks you may need to perform:

  • Add the release: add testing instructions label

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

package: @woocommerce/components issues related to @woocommerce/components plugin: woocommerce Issues related to the WooCommerce Core plugin.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Epic] Images Product management MVP 1.0

4 participants