Skip to content

Commit

Permalink
Adds ImagePicker Gutenberg component
Browse files Browse the repository at this point in the history
  • Loading branch information
nikkifurls committed Jun 27, 2024
1 parent 656cb9c commit 7b5c4fd
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 0 deletions.
11 changes: 11 additions & 0 deletions components/imagePicker/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.image-picker {
&__button-group {
display: flex;
gap: 5px;
margin-bottom: 5px;
}

&__preview {
max-height: 200px;
}
}
163 changes: 163 additions & 0 deletions components/imagePicker/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/* eslint-disable camelcase */
import { __ } from '@wordpress/i18n';
import { BaseControl, Button, ButtonGroup } from '@wordpress/components';
import { useEffect, useRef, useState } from 'react';
import apiFetch from '@wordpress/api-fetch';
import type { WP_REST_API_Attachment } from 'wp-types';
import './index.scss';

declare global {
interface Window {
wp: {
media: (options: MediaLibraryOptions) => any;

Check failure on line 12 in components/imagePicker/index.tsx

View workflow job for this annotation

GitHub Actions / node-tests / Install, build, and test

'MediaLibraryOptions' was used before it was defined
};
}
}

type ImagePickerProps = {
label: string;
onChange: (value: number) => void;
value: number;
};

type MediaLibraryOptions = {
library: {
type: string;
};
};

type MediaLibrarySelection = {
id: number;
url: string;
};

export default function ImagePicker({
label,
onChange,
value,
}: ImagePickerProps) {
const imagePreviewRef = useRef<HTMLImageElement>(null);
const imagePreview = imagePreviewRef.current as HTMLImageElement;
const [imageUrl, setImageUrl] = useState('');

/**
* Handle the Media Library modal logic.
* @returns Promise
*/
const openMediaLibraryModal = () => {

Check failure on line 47 in components/imagePicker/index.tsx

View workflow job for this annotation

GitHub Actions / node-tests / Install, build, and test

Unexpected block statement surrounding arrow body; move the returned value immediately after the `=>`
return new Promise((resolve, reject) => {

Check failure on line 48 in components/imagePicker/index.tsx

View workflow job for this annotation

GitHub Actions / node-tests / Install, build, and test

Trailing spaces not allowed
// Create the Media Library object. Restrict to images only.
const mediaLibrary = window?.wp?.media({
library: {
type: 'image'

Check failure on line 52 in components/imagePicker/index.tsx

View workflow job for this annotation

GitHub Actions / node-tests / Install, build, and test

Missing trailing comma

Check failure on line 52 in components/imagePicker/index.tsx

View workflow job for this annotation

GitHub Actions / node-tests / Install, build, and test

Missing trailing comma
}

Check failure on line 53 in components/imagePicker/index.tsx

View workflow job for this annotation

GitHub Actions / node-tests / Install, build, and test

Missing trailing comma

Check failure on line 53 in components/imagePicker/index.tsx

View workflow job for this annotation

GitHub Actions / node-tests / Install, build, and test

Missing trailing comma
});

Check failure on line 55 in components/imagePicker/index.tsx

View workflow job for this annotation

GitHub Actions / node-tests / Install, build, and test

Trailing spaces not allowed
if (!mediaLibrary) {
reject();
}

Check failure on line 59 in components/imagePicker/index.tsx

View workflow job for this annotation

GitHub Actions / node-tests / Install, build, and test

Trailing spaces not allowed
// Set up the select event listener. On success, returns a promise with the image ID and URL.
mediaLibrary?.on('select', () => {
const selectedImage = mediaLibrary?.state()?.get('selection')?.first();

if (!selectedImage) {
reject();
}

Check failure on line 67 in components/imagePicker/index.tsx

View workflow job for this annotation

GitHub Actions / node-tests / Install, build, and test

Trailing spaces not allowed
const {
attributes: {
id = 0,
url = '',
} = {},
} = selectedImage;

resolve({ id, url });
});

// Open the Media Library modal.
mediaLibrary?.open();
});
};

/**
* Select an image.
*/
const selectImage = async () => {
const imageData = await openMediaLibraryModal() as MediaLibrarySelection;

const {
id = 0,
url = '',
} = imageData;

// Pass the selected attachment ID to the onChange event.
onChange(id);

// Update the image URL state.
setImageUrl(url);
}

/**
* Clear the selected image.
*/
const clearImage = () => {
onChange(0);
setImageUrl('');
};

/**
* Fetch the image URL from the REST API when the image ID changes.
*/
useEffect(() => {
if (!value) {
return;
}

// Get the image url from the REST API and update the image preview.
apiFetch({ path: `/wp/v2/media/${value}` })
.then((response) => {
const { source_url: url = '' } = response as WP_REST_API_Attachment;
setImageUrl(url);
})
.catch(() => {
setImageUrl('');
});
}, [value]);

/**
* Update the image preview when the image URL changes.
*/
useEffect(() => {
if (!imageUrl || !imagePreview) {
return;
}

imagePreview.src = imageUrl;
}, [imageUrl]);

return (
<BaseControl label={label}>
<ButtonGroup className="image-picker__button-group">
<Button
onClick={selectImage}
variant="secondary"
>
{__('Select an Image', 'wp-newsletter-builder')}
</Button>
<Button
disabled={!imageUrl}
onClick={clearImage}
variant="secondary"
>
{__('Clear Image', 'wp-newsletter-builder')}
</Button>
</ButtonGroup>
<img
className="image-picker__preview"
ref={imagePreviewRef}
src={imageUrl}
/>
</BaseControl>
);
}

0 comments on commit 7b5c4fd

Please sign in to comment.