UNPKG

@wordpress/block-library

Version:
445 lines (404 loc) • 17.1 kB
import _extends from "@babel/runtime/helpers/esm/extends"; import { createElement, Fragment } from "@wordpress/element"; /** * External dependencies */ import classnames from 'classnames'; import { find } from 'lodash'; /** * WordPress dependencies */ import { compose } from '@wordpress/compose'; import { BaseControl, PanelBody, SelectControl, ToggleControl, RangeControl, Spinner } from '@wordpress/components'; import { store as blockEditorStore, MediaPlaceholder, InspectorControls, useBlockProps, BlockControls, MediaReplaceFlow } from '@wordpress/block-editor'; import { Platform, useEffect, useMemo } from '@wordpress/element'; import { __, _x, sprintf } from '@wordpress/i18n'; import { useSelect, useDispatch } from '@wordpress/data'; import { withViewportMatch } from '@wordpress/viewport'; import { View } from '@wordpress/primitives'; import { createBlock } from '@wordpress/blocks'; import { createBlobURL } from '@wordpress/blob'; import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies */ import { sharedIcon } from './shared-icon'; import { defaultColumnsNumber, pickRelevantMediaFiles } from './shared'; import { getHrefAndDestination } from './utils'; import { getUpdatedLinkTargetSettings, getImageSizeAttributes } from '../image/utils'; import Gallery from './gallery'; import { LINK_DESTINATION_ATTACHMENT, LINK_DESTINATION_MEDIA, LINK_DESTINATION_NONE } from './constants'; import useImageSizes from './use-image-sizes'; import useGetNewImages from './use-get-new-images'; import useGetMedia from './use-get-media'; import GapStyles from './gap-styles'; const MAX_COLUMNS = 8; const linkOptions = [{ value: LINK_DESTINATION_ATTACHMENT, label: __('Attachment Page') }, { value: LINK_DESTINATION_MEDIA, label: __('Media File') }, { value: LINK_DESTINATION_NONE, label: _x('None', 'Media item link option') }]; const ALLOWED_MEDIA_TYPES = ['image']; const PLACEHOLDER_TEXT = Platform.isNative ? __('ADD MEDIA') : __('Drag images, upload new ones or select files from your library.'); const MOBILE_CONTROL_PROPS_RANGE_CONTROL = Platform.isNative ? { type: 'stepper' } : {}; function GalleryEdit(props) { var _attributes$style, _attributes$style$spa; const { setAttributes, attributes, className, clientId, isSelected, insertBlocksAfter } = props; const { columns, imageCrop, linkTarget, linkTo, sizeSlug } = attributes; const { __unstableMarkNextChangeAsNotPersistent, replaceInnerBlocks, updateBlockAttributes, selectBlock, clearSelectedBlock } = useDispatch(blockEditorStore); const { createSuccessNotice, createErrorNotice } = useDispatch(noticesStore); const { getBlock, getSettings, preferredStyle } = useSelect(select => { var _preferredStyleVariat; const settings = select(blockEditorStore).getSettings(); const preferredStyleVariations = settings.__experimentalPreferredStyleVariations; return { getBlock: select(blockEditorStore).getBlock, getSettings: select(blockEditorStore).getSettings, preferredStyle: preferredStyleVariations === null || preferredStyleVariations === void 0 ? void 0 : (_preferredStyleVariat = preferredStyleVariations.value) === null || _preferredStyleVariat === void 0 ? void 0 : _preferredStyleVariat['core/image'] }; }, []); const innerBlockImages = useSelect(select => { var _select$getBlock; return (_select$getBlock = select(blockEditorStore).getBlock(clientId)) === null || _select$getBlock === void 0 ? void 0 : _select$getBlock.innerBlocks; }, [clientId]); const wasBlockJustInserted = useSelect(select => { return select(blockEditorStore).wasBlockJustInserted(clientId, 'inserter_menu'); }, [clientId]); const images = useMemo(() => innerBlockImages === null || innerBlockImages === void 0 ? void 0 : innerBlockImages.map(block => ({ clientId: block.clientId, id: block.attributes.id, url: block.attributes.url, attributes: block.attributes, fromSavedContent: Boolean(block.originalContent) })), [innerBlockImages]); const imageData = useGetMedia(innerBlockImages); const newImages = useGetNewImages(images, imageData); useEffect(() => { newImages === null || newImages === void 0 ? void 0 : newImages.forEach(newImage => { // Update the images data without creating new undo levels. __unstableMarkNextChangeAsNotPersistent(); updateBlockAttributes(newImage.clientId, { ...buildImageAttributes(newImage.attributes), id: newImage.id, align: undefined }); }); if ((newImages === null || newImages === void 0 ? void 0 : newImages.length) > 0) { clearSelectedBlock(); } }, [newImages]); const imageSizeOptions = useImageSizes(imageData, isSelected, getSettings); /** * Determines the image attributes that should be applied to an image block * after the gallery updates. * * The gallery will receive the full collection of images when a new image * is added. As a result we need to reapply the image's original settings if * it already existed in the gallery. If the image is in fact new, we need * to apply the gallery's current settings to the image. * * @param {Object} imageAttributes Media object for the actual image. * @return {Object} Attributes to set on the new image block. */ function buildImageAttributes(imageAttributes) { var _image$caption; const image = imageAttributes.id ? find(imageData, { id: imageAttributes.id }) : null; let newClassName; if (imageAttributes.className && imageAttributes.className !== '') { newClassName = imageAttributes.className; } else { newClassName = preferredStyle ? `is-style-${preferredStyle}` : undefined; } let newLinkTarget; if (imageAttributes.linkTarget || imageAttributes.rel) { // When transformed from image blocks, the link destination and rel attributes are inherited. newLinkTarget = { linkTarget: imageAttributes.linkTarget, rel: imageAttributes.rel }; } else { // When an image is added, update the link destination and rel attributes according to the gallery settings newLinkTarget = getUpdatedLinkTargetSettings(linkTarget, attributes); } return { ...pickRelevantMediaFiles(image, sizeSlug), ...getHrefAndDestination(image, linkTo, imageAttributes === null || imageAttributes === void 0 ? void 0 : imageAttributes.linkDestination), ...newLinkTarget, className: newClassName, sizeSlug, caption: imageAttributes.caption || ((_image$caption = image.caption) === null || _image$caption === void 0 ? void 0 : _image$caption.raw), alt: imageAttributes.alt || image.alt_text }; } function isValidFileType(file) { var _file$url; return ALLOWED_MEDIA_TYPES.some(mediaType => { var _file$type; return ((_file$type = file.type) === null || _file$type === void 0 ? void 0 : _file$type.indexOf(mediaType)) === 0; }) || ((_file$url = file.url) === null || _file$url === void 0 ? void 0 : _file$url.indexOf('blob:')) === 0; } function updateImages(selectedImages) { const newFileUploads = Object.prototype.toString.call(selectedImages) === '[object FileList]'; const imageArray = newFileUploads ? Array.from(selectedImages).map(file => { if (!file.url) { return pickRelevantMediaFiles({ url: createBlobURL(file) }); } return file; }) : selectedImages; if (!imageArray.every(isValidFileType)) { createErrorNotice(__('If uploading to a gallery all files need to be image formats'), { id: 'gallery-upload-invalid-file', type: 'snackbar' }); } const processedImages = imageArray.filter(file => file.url || isValidFileType(file)).map(file => { if (!file.url) { return pickRelevantMediaFiles({ url: createBlobURL(file) }); } return file; }); // Because we are reusing existing innerImage blocks any reordering // done in the media library will be lost so we need to reapply that ordering // once the new image blocks are merged in with existing. const newOrderMap = processedImages.reduce((result, image, index) => (result[image.id] = index, result), {}); const existingImageBlocks = !newFileUploads ? innerBlockImages.filter(block => processedImages.find(img => img.id === block.attributes.id)) : innerBlockImages; const newImageList = processedImages.filter(img => !existingImageBlocks.find(existingImg => img.id === existingImg.attributes.id)); const newBlocks = newImageList.map(image => { return createBlock('core/image', { id: image.id, url: image.url, caption: image.caption, alt: image.alt }); }); if ((newBlocks === null || newBlocks === void 0 ? void 0 : newBlocks.length) > 0) { selectBlock(newBlocks[0].clientId); } replaceInnerBlocks(clientId, existingImageBlocks.concat(newBlocks).sort((a, b) => newOrderMap[a.attributes.id] - newOrderMap[b.attributes.id])); } function onUploadError(message) { createErrorNotice(message, { type: 'snackbar' }); } function setLinkTo(value) { setAttributes({ linkTo: value }); const changedAttributes = {}; const blocks = []; getBlock(clientId).innerBlocks.forEach(block => { blocks.push(block.clientId); const image = block.attributes.id ? find(imageData, { id: block.attributes.id }) : null; changedAttributes[block.clientId] = getHrefAndDestination(image, value); }); updateBlockAttributes(blocks, changedAttributes, true); const linkToText = [...linkOptions].find(linkType => linkType.value === value); createSuccessNotice(sprintf( /* translators: %s: image size settings */ __('All gallery image links updated to: %s'), linkToText.label), { id: 'gallery-attributes-linkTo', type: 'snackbar' }); } function setColumnsNumber(value) { setAttributes({ columns: value }); } function toggleImageCrop() { setAttributes({ imageCrop: !imageCrop }); } function getImageCropHelp(checked) { return checked ? __('Thumbnails are cropped to align.') : __('Thumbnails are not cropped.'); } function toggleOpenInNewTab(openInNewTab) { const newLinkTarget = openInNewTab ? '_blank' : undefined; setAttributes({ linkTarget: newLinkTarget }); const changedAttributes = {}; const blocks = []; getBlock(clientId).innerBlocks.forEach(block => { blocks.push(block.clientId); changedAttributes[block.clientId] = getUpdatedLinkTargetSettings(newLinkTarget, block.attributes); }); updateBlockAttributes(blocks, changedAttributes, true); const noticeText = openInNewTab ? __('All gallery images updated to open in new tab') : __('All gallery images updated to not open in new tab'); createSuccessNotice(noticeText, { id: 'gallery-attributes-openInNewTab', type: 'snackbar' }); } function updateImagesSize(newSizeSlug) { setAttributes({ sizeSlug: newSizeSlug }); const changedAttributes = {}; const blocks = []; getBlock(clientId).innerBlocks.forEach(block => { blocks.push(block.clientId); const image = block.attributes.id ? find(imageData, { id: block.attributes.id }) : null; changedAttributes[block.clientId] = getImageSizeAttributes(image, newSizeSlug); }); updateBlockAttributes(blocks, changedAttributes, true); const imageSize = imageSizeOptions.find(size => size.value === newSizeSlug); createSuccessNotice(sprintf( /* translators: %s: image size settings */ __('All gallery image sizes updated to: %s'), imageSize.label), { id: 'gallery-attributes-sizeSlug', type: 'snackbar' }); } useEffect(() => { // linkTo attribute must be saved so blocks don't break when changing image_default_link_type in options.php. if (!linkTo) { var _window, _window$wp, _window$wp$media, _window$wp$media$view, _window$wp$media$view2, _window$wp$media$view3; __unstableMarkNextChangeAsNotPersistent(); setAttributes({ linkTo: ((_window = window) === null || _window === void 0 ? void 0 : (_window$wp = _window.wp) === null || _window$wp === void 0 ? void 0 : (_window$wp$media = _window$wp.media) === null || _window$wp$media === void 0 ? void 0 : (_window$wp$media$view = _window$wp$media.view) === null || _window$wp$media$view === void 0 ? void 0 : (_window$wp$media$view2 = _window$wp$media$view.settings) === null || _window$wp$media$view2 === void 0 ? void 0 : (_window$wp$media$view3 = _window$wp$media$view2.defaultProps) === null || _window$wp$media$view3 === void 0 ? void 0 : _window$wp$media$view3.link) || LINK_DESTINATION_NONE }); } }, [linkTo]); const hasImages = !!images.length; const hasImageIds = hasImages && images.some(image => !!image.id); const imagesUploading = images.some(img => { var _img$url, _img$url2; return !Platform.isNative ? !img.id && ((_img$url = img.url) === null || _img$url === void 0 ? void 0 : _img$url.indexOf('blob:')) === 0 : ((_img$url2 = img.url) === null || _img$url2 === void 0 ? void 0 : _img$url2.indexOf('file:')) === 0; }); // MediaPlaceholder props are different between web and native hence, we provide a platform-specific set. const mediaPlaceholderProps = Platform.select({ web: { addToGallery: false, disableMediaButtons: imagesUploading, value: {} }, native: { addToGallery: hasImageIds, isAppender: hasImages, disableMediaButtons: hasImages && !isSelected || imagesUploading, value: hasImageIds ? images : {}, autoOpenMediaUpload: !hasImages && isSelected && wasBlockJustInserted } }); const mediaPlaceholder = createElement(MediaPlaceholder, _extends({ handleUpload: false, icon: sharedIcon, labels: { title: __('Gallery'), instructions: PLACEHOLDER_TEXT }, onSelect: updateImages, accept: "image/*", allowedTypes: ALLOWED_MEDIA_TYPES, multiple: true, onError: onUploadError }, mediaPlaceholderProps)); const blockProps = useBlockProps({ className: classnames(className, 'has-nested-images') }); if (!hasImages) { return createElement(View, blockProps, mediaPlaceholder); } const hasLinkTo = linkTo && linkTo !== 'none'; return createElement(Fragment, null, createElement(InspectorControls, null, createElement(PanelBody, { title: __('Settings') }, images.length > 1 && createElement(RangeControl, _extends({ label: __('Columns'), value: columns ? columns : defaultColumnsNumber(images.length), onChange: setColumnsNumber, min: 1, max: Math.min(MAX_COLUMNS, images.length) }, MOBILE_CONTROL_PROPS_RANGE_CONTROL, { required: true })), createElement(ToggleControl, { label: __('Crop images'), checked: !!imageCrop, onChange: toggleImageCrop, help: getImageCropHelp }), createElement(SelectControl, { label: __('Link to'), value: linkTo, onChange: setLinkTo, options: linkOptions, hideCancelButton: true }), hasLinkTo && createElement(ToggleControl, { label: __('Open in new tab'), checked: linkTarget === '_blank', onChange: toggleOpenInNewTab }), (imageSizeOptions === null || imageSizeOptions === void 0 ? void 0 : imageSizeOptions.length) > 0 && createElement(SelectControl, { label: __('Image size'), value: sizeSlug, options: imageSizeOptions, onChange: updateImagesSize, hideCancelButton: true }), Platform.isWeb && !imageSizeOptions && hasImageIds && createElement(BaseControl, { className: 'gallery-image-sizes' }, createElement(BaseControl.VisualLabel, null, __('Image size')), createElement(View, { className: 'gallery-image-sizes__loading' }, createElement(Spinner, null), __('Loading options…'))))), createElement(BlockControls, { group: "other" }, createElement(MediaReplaceFlow, { allowedTypes: ALLOWED_MEDIA_TYPES, accept: "image/*", handleUpload: false, onSelect: updateImages, name: __('Add'), multiple: true, mediaIds: images.filter(image => image.id).map(image => image.id), addToGallery: hasImageIds })), Platform.isWeb && createElement(GapStyles, { blockGap: (_attributes$style = attributes.style) === null || _attributes$style === void 0 ? void 0 : (_attributes$style$spa = _attributes$style.spacing) === null || _attributes$style$spa === void 0 ? void 0 : _attributes$style$spa.blockGap, clientId: clientId }), createElement(Gallery, _extends({}, props, { images: images, mediaPlaceholder: !hasImages || Platform.isNative ? mediaPlaceholder : undefined, blockProps: blockProps, insertBlocksAfter: insertBlocksAfter }))); } export default compose([withViewportMatch({ isNarrow: '< small' })])(GalleryEdit); //# sourceMappingURL=edit.js.map