Add, Upload image trong Gutenberg Block Development

Bài viết này sẽ hướng dẫn các bạn tạo 1 block có thể upload hoặc thêm hình ảnh để sử dụng. Ví dụ như để làm background-image cho block, gallery, custom image.

Create a Gutenberg Background Image
Create a Gutenberg Background Image

Trong block này sẽ bao gồm các attributes sau:

  • bgColor: Set background color cho block
  • bgUrl: Background image của block
  • bgID: Đây là id của image select từ media
  • focalPoint: Attribute này mình dùng để làm background position cho block
attributes: {
	focalPoint: {
		type: 'object',
		default: {x: 0.5, y: 0.5}
	},
	bgID: {
		type: 'number'
	},
	bgUrl: {
		type: 'string',
		default: ''
	},
	bgColor: {
		type: 'string',
		default: '#948c8c'
	},
}

Để add, upload image trong gutenberg block thì chúng ta sử dụng component MediaUpload. Để đảm bảo người dùng hiện tại có quyền Upload, bạn cần phải bọc component MediaUpload vào MediaUploadCheck.


import { Button } from '@wordpress/components';
import { MediaUpload, MediaUploadCheck } from '@wordpress/block-editor';

const ALLOWED_MEDIA_TYPES = ['audio'];

function MyMediaUploader() {
	return (
		<MediaUploadCheck>
			<MediaUpload
				onSelect={(media) =>
					console.log('selected ' + media.length)
				}
				allowedTypes={ALLOWED_MEDIA_TYPES}
				value={mediaId}
				render={({open}) => (
					<Button onClick={open}>Open Media Library</Button>
				)}
			/>
		</MediaUploadCheck>
	);
}

Props

Component này chấp nhận các props sau:

  • allowedTypes: Mảng với các loại phương tiện để upload/select từ thư viện phương tiện. Mỗi loại là một chuỗi có thể chứa mime type, ví dụ: ‘hình ảnh’, ‘âm thanh’, ‘văn bản’ hoặc mime type hoàn chỉnh, ví dụ: ‘audio/mpeg’, ‘image/gif’. Nếu allowedTypes không được đặt, tất cả các mime type phải được cho phép.
  • multiple: Có cho phép nhiều lựa chọn hay không.
  • value: Media ID (hoặc media IDs nếu multiple là true) được chọn theo mặc định khi mở media library.
  • onClose: Callback function được gọi khi media modal bị đóng.
  • onSelect: Callback function được gọi khi media modal bị đóng sau khi media được chọn.
  • title: Tiêu đề được hiển thị trong media modal.
  • modalClass: Class CSS được thêm vào media modal.
  • addToGallery: Nếu true, gallery media modal sẽ mở trực tiếp trong thư viện phương tiện nơi người dùng có thể thêm hình ảnh bổ sung. Nếu false, gallery media modal sẽ mở trong chế độ chỉnh sửa, nơi người dùng có thể chỉnh sửa các hình ảnh hiện có, bằng cách sắp xếp lại chúng, xóa chúng hoặc thay đổi thuộc tính của chúng. Chỉ áp dụng nếu gallery === true.
  • autoOpen: Nếu true, bộ chọn của nền tảng tương ứng sẽ tự động mở.
  • gallery: Nếu true, thành phần sẽ khởi tạo tất cả các trạng thái cần thiết để đại diện cho một thư viện. Theo mặc định, media modal sẽ mở trong khung chỉnh sửa thư viện, nhưng điều đó có thể được thay đổi bằng cách sử dụng addToGallery.
  • render: Một callback function được gọi để hiển thị Button mở thư viện phương tiện.

Demo Block

Code

Dưới đây là toàn bộ code của block demo.

index.js

//  Import CSS.
import './editor.scss';
import './style.scss';

import { __ } from '@wordpress/i18n'
import { registerBlockType } from '@wordpress/blocks'
import { Fragment } from '@wordpress/element'
import {
	MediaUpload,
	MediaUploadCheck,
	BlockControls,
	InspectorControls,
	InnerBlocks,
} from '@wordpress/block-editor'
import {
	PanelBody,
	Toolbar,
	ToolbarGroup,
	ToolbarButton,
	FocalPointPicker,
	Spinner,
	ResponsiveWrapper,
	Button,
	ColorPalette
} from '@wordpress/components'
import { withSelect } from '@wordpress/data';

const ALLOWED_MEDIA_TYPES = ['image'];
const TEMPLATE = [
	[
		'core/heading',
		{
			level: 1,
			placeholder: __('Heading...'),
			className: 'heading',
			content: __('Heading...')
		},
	],
	[
		'core/paragraph',
		{
			placeholder: __('Content...'),
			content: __('Content...')
		},
	],
	[
		'core/paragraph',
		{
			placeholder: __('Content...'),
			content: __('Content...')
		},
	],
]

const Edit = (props) => {
	const {attributes, setAttributes, className, mediaBG} = props;
	const {bgID, bgUrl, bgColor, focalPoint} = attributes;
	const instructions = <p>{__('To edit the image, you need permission to upload media.')}</p>;

	console.log(props)
	return (
		<Fragment>
			<BlockControls>
				<ToolbarGroup>
					<MediaUploadCheck>
						<MediaUpload
							title={__('Background image')}
							onSelect={(media) => (
								setAttributes({
									bgID: media.id,
									bgUrl: media.url,
								})
							)}
							allowedTypes={ALLOWED_MEDIA_TYPES}
							value={bgID}
							render={({open}) => (
								<ToolbarButton
									icon={<span className="dashicons dashicons-format-image" />}
									label={__('Add background')}
									onClick={open}
								/>
							)}
						/>
					</MediaUploadCheck>
				</ToolbarGroup>
			</BlockControls>
			<InspectorControls>
				<PanelBody title={__('Background Color')}>
					<ColorPalette
						colors={[
							{name: 'red', color: '#f00'},
							{name: 'white', color: '#fff'},
							{name: 'blue', color: '#00f'},
						]}
						value={bgColor}
						onChange={(bgColor) => setAttributes({bgColor})}
					/>
				</PanelBody>

				<PanelBody title={__('Background Image')}>
					<div className="components-placeholder__fieldset">
						<MediaUploadCheck fallback={instructions}>
							<MediaUpload
								title={__('Background Image')}
								onSelect={(media) => (
									setAttributes({
										bgID: media.id,
										bgUrl: media.url,
									})
								)}
								allowedTypes={ALLOWED_MEDIA_TYPES}
								value={bgID}
								render={({open}) => (
									<Button
										className={!bgID ? 'editor-post-featured-image__toggle' : 'editor-post-featured-image__preview'}
										onClick={open}>
										{!bgID && (__('Change Background Image'))}
										{!!bgID && !mediaBG && <Spinner />}
										{!!bgID && mediaBG &&
										<img src={mediaBG.source_url} alt={__('Background image')} />
										}
									</Button>
								)}
							/>
						</MediaUploadCheck>
						{!!bgID && mediaBG &&
						<MediaUploadCheck>
							<MediaUpload
								title={__('Icon')}
								onSelect={(media) => (
									setAttributes({
										bgID: media.id,
										bgUrl: media.url,
									})
								)}
								allowedTypes={ALLOWED_MEDIA_TYPES}
								value={bgID}
								render={({open}) => (
									<Button onClick={open} isSecondary>
										{__('Replace Image')}
									</Button>
								)}
							/>
						</MediaUploadCheck>
						}
						{!!bgID &&
						<MediaUploadCheck>
							<Button onClick={() => (
								setAttributes({
									bgID: undefined,
									bgUrl: '',
									focalPoint: {x: 0.5, y: 0.5}
								})
							)} isDestructive>
								{__('Remove Image')}
							</Button>
						</MediaUploadCheck>
						}
					</div>
				</PanelBody>
				{bgUrl && (<PanelBody title={__('Background Position')}>
					<FocalPointPicker
						label={__('Focal Point Picker')}
						url={bgUrl}
						value={focalPoint}
						onChange={(focalPoint) => setAttributes({focalPoint})}
					/>
				</PanelBody>)}
			</InspectorControls>

			<div style={{
				backgroundImage: `url(${bgUrl})`,
				backgroundPosition: focalPoint ? `${focalPoint.x * 100}% ${focalPoint.y * 100}%` : 'auto',
				backgroundColor: bgColor,
			}} className={['my-block', 'block-hero-section block-editor', className].join(' ')}>
				<div className={["inner-wrap"].join(' ')}>
					<div className="content-wrap">
						<InnerBlocks
							templateLock="inser"
							template={TEMPLATE}
						/>
					</div>
				</div>
			</div>
		</Fragment>
	);
}
const Save = (props) => {
	const {attributes, className} = props;
	const {bgID, bgUrl, bgColor, focalPoint,} = attributes;

	return (
		<div style={{
			backgroundImage: `url(${bgUrl})`,
			backgroundPosition: focalPoint ? `${focalPoint.x * 100}% ${focalPoint.y * 100}%` : 'auto',
			backgroundColor: bgColor,
		}} className={['my-block', 'block-hero-section', className].join(' ')}>
			<div className={["inner-wrap"].join(' ')}>
				<div className="content-wrap">
					<InnerBlocks.Content />
				</div>
			</div>
		</div>
	)
}

registerBlockType('cgb/my-block', {
	title: __('my-block - CGB Block'),
	icon: 'shield',
	category: 'common',
	keywords: [
		__('my-block — CGB Block'),
		__('CGB Example'),
		__('create-guten-block'),
	],
	attributes: {
		focalPoint: {
			type: 'object',
			default: {x: 0.5, y: 0.5}
		},
		bgID: {
			type: 'number'
		},
		bgUrl: {
			type: 'string',
			default: ''
		},
		bgColor: {
			type: 'string',
			default: '#948c8c'
		},
	},
	supports: {
		align: true,
		anchor: true
	},
	example: {
		attributes: {
			focalPoint: {x: 0.5, y: 0.5},
			bgUrl: 'https://picsum.photos/800',
			align: 'full',
			bgColor: '#948c8c',
		},
	},

	edit: withSelect((select, props) => {
		const {getMedia} = select('core');
		const {bgID} = props.attributes;

		return {
			mediaBG: bgID ? getMedia(bgID) : null,
		};
	})(Edit),

	save: Save,
});

style.scss


.block-hero-section {
	padding-top: 80px;
	padding-bottom: 80px;
	background-position: center;
	background-repeat: no-repeat;
	background-size: cover;
	text-align: center;
}

Tài liệu tham khảo

https://github.com/WordPress/gutenberg/tree/trunk/packages/block-editor/src/components/media-upload

https://github.com/WordPress/gutenberg/tree/trunk/packages/components/src/focal-point-picker

https://github.com/WordPress/gutenberg/tree/trunk/packages/components/src/color-palette

Oh My Zsh: Nâng cao trải nghiệm terminal với giao diện đẹp và các plugin tăng hiệu suất! Git cherry-pick là gì? Cách sử dụng và ví dụ Git Rebase: Gộp nhiều commit thành một để tối ưu hóa lịch sử commit 11 tính năng JavaScript mới tuyệt vời trong ES13 (ES2022) CSS diệu kỳ: Các thuộc tính CSS mà bạn có thể chưa biết Auto deploy projects với GitHub Actions – sử dụng ssh-action WordPress Gutenberg Block Server Side Render Tạo Block Controls – Block Toolbar và Settings Sidebar trong WordPress Gutenberg Làm quen với các components thường dùng khi tạo Gutenberg Block Gutenberg Block Attributes

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.