@wordpress/block-library
Version:
Block library for the WordPress editor.
445 lines (404 loc) • 17.1 kB
JavaScript
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