UNPKG

@wordpress/block-library

Version:
504 lines (496 loc) • 20 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = GalleryEdit; var _clsx = _interopRequireDefault(require("clsx")); var _components = require("@wordpress/components"); var _blockEditor = require("@wordpress/block-editor"); var _element = require("@wordpress/element"); var _i18n = require("@wordpress/i18n"); var _data = require("@wordpress/data"); var _primitives = require("@wordpress/primitives"); var _blocks = require("@wordpress/blocks"); var _blob = require("@wordpress/blob"); var _notices = require("@wordpress/notices"); var _icons = require("@wordpress/icons"); var _sharedIcon = require("./shared-icon"); var _shared = require("./shared"); var _utils = require("./utils"); var _utils2 = require("../image/utils"); var _gallery = _interopRequireDefault(require("./gallery")); var _constants = require("./constants"); var _useImageSizes = _interopRequireDefault(require("./use-image-sizes")); var _useGetNewImages = _interopRequireDefault(require("./use-get-new-images")); var _useGetMedia = _interopRequireDefault(require("./use-get-media")); var _gapStyles = _interopRequireDefault(require("./gap-styles")); var _jsxRuntime = require("react/jsx-runtime"); /** * External dependencies */ /** * WordPress dependencies */ /** * Internal dependencies */ const MAX_COLUMNS = 8; const LINK_OPTIONS = [{ icon: _icons.customLink, label: (0, _i18n.__)('Link images to attachment pages'), value: _constants.LINK_DESTINATION_ATTACHMENT, noticeText: (0, _i18n.__)('Attachment Pages') }, { icon: _icons.image, label: (0, _i18n.__)('Link images to media files'), value: _constants.LINK_DESTINATION_MEDIA, noticeText: (0, _i18n.__)('Media Files') }, { icon: _icons.fullscreen, label: (0, _i18n.__)('Enlarge on click'), value: _constants.LINK_DESTINATION_LIGHTBOX, noticeText: (0, _i18n.__)('Lightbox effect'), infoText: (0, _i18n.__)('Scale images with a lightbox effect') }, { icon: _icons.linkOff, label: (0, _i18n._x)('None', 'Media item link option'), value: _constants.LINK_DESTINATION_NONE, noticeText: (0, _i18n.__)('None') }]; const ALLOWED_MEDIA_TYPES = ['image']; const PLACEHOLDER_TEXT = _element.Platform.isNative ? (0, _i18n.__)('Add media') : (0, _i18n.__)('Drag and drop images, upload, or choose from your library.'); const MOBILE_CONTROL_PROPS_RANGE_CONTROL = _element.Platform.isNative ? { type: 'stepper' } : {}; const DEFAULT_BLOCK = { name: 'core/image' }; const EMPTY_ARRAY = []; function GalleryEdit(props) { const { setAttributes, attributes, className, clientId, isSelected, insertBlocksAfter, isContentLocked, onFocus } = props; const [lightboxSetting] = (0, _blockEditor.useSettings)('blocks.core/image.lightbox'); const linkOptions = !lightboxSetting?.allowEditing ? LINK_OPTIONS.filter(option => option.value !== _constants.LINK_DESTINATION_LIGHTBOX) : LINK_OPTIONS; const { columns, imageCrop, randomOrder, linkTarget, linkTo, sizeSlug } = attributes; const { __unstableMarkNextChangeAsNotPersistent, replaceInnerBlocks, updateBlockAttributes, selectBlock } = (0, _data.useDispatch)(_blockEditor.store); const { createSuccessNotice, createErrorNotice } = (0, _data.useDispatch)(_notices.store); const { getBlock, getSettings, innerBlockImages, blockWasJustInserted, multiGallerySelection } = (0, _data.useSelect)(select => { var _getBlock$innerBlocks; const { getBlockName, getMultiSelectedBlockClientIds, getSettings: _getSettings, getBlock: _getBlock, wasBlockJustInserted } = select(_blockEditor.store); const multiSelectedClientIds = getMultiSelectedBlockClientIds(); return { getBlock: _getBlock, getSettings: _getSettings, innerBlockImages: (_getBlock$innerBlocks = _getBlock(clientId)?.innerBlocks) !== null && _getBlock$innerBlocks !== void 0 ? _getBlock$innerBlocks : EMPTY_ARRAY, blockWasJustInserted: wasBlockJustInserted(clientId, 'inserter_menu'), multiGallerySelection: multiSelectedClientIds.length && multiSelectedClientIds.every(_clientId => getBlockName(_clientId) === 'core/gallery') }; }, [clientId]); const images = (0, _element.useMemo)(() => innerBlockImages?.map(block => ({ clientId: block.clientId, id: block.attributes.id, url: block.attributes.url, attributes: block.attributes, fromSavedContent: Boolean(block.originalContent) })), [innerBlockImages]); const imageData = (0, _useGetMedia.default)(innerBlockImages); const newImages = (0, _useGetNewImages.default)(images, imageData); (0, _element.useEffect)(() => { newImages?.forEach(newImage => { // Update the images data without creating new undo levels. __unstableMarkNextChangeAsNotPersistent(); updateBlockAttributes(newImage.clientId, { ...buildImageAttributes(newImage.attributes), id: newImage.id, align: undefined }); }); }, [newImages]); const imageSizeOptions = (0, _useImageSizes.default)(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) { const image = imageAttributes.id ? imageData.find(({ id }) => id === imageAttributes.id) : null; let newClassName; if (imageAttributes.className && imageAttributes.className !== '') { newClassName = imageAttributes.className; } 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 = (0, _utils2.getUpdatedLinkTargetSettings)(linkTarget, attributes); } return { ...(0, _shared.pickRelevantMediaFiles)(image, sizeSlug), ...(0, _utils.getHrefAndDestination)(image, linkTo, imageAttributes?.linkDestination), ...newLinkTarget, className: newClassName, sizeSlug, caption: imageAttributes.caption || image.caption?.raw, alt: imageAttributes.alt || image.alt_text }; } function isValidFileType(file) { // It's necessary to retrieve the media type from the raw image data for already-uploaded images on native. const nativeFileData = _element.Platform.isNative && file.id ? imageData.find(({ id }) => id === file.id) : null; const mediaTypeSelector = nativeFileData ? nativeFileData?.media_type : file.type; return ALLOWED_MEDIA_TYPES.some(mediaType => mediaTypeSelector?.indexOf(mediaType) === 0) || file.blob; } function updateImages(selectedImages) { const newFileUploads = Object.prototype.toString.call(selectedImages) === '[object FileList]'; const imageArray = newFileUploads ? Array.from(selectedImages).map(file => { if (!file.url) { return { blob: (0, _blob.createBlobURL)(file) }; } return file; }) : selectedImages; if (!imageArray.every(isValidFileType)) { createErrorNotice((0, _i18n.__)('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 { blob: file.blob || (0, _blob.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 (0, _blocks.createBlock)('core/image', { id: image.id, blob: image.blob, url: image.url, caption: image.caption, alt: image.alt }); }); replaceInnerBlocks(clientId, existingImageBlocks.concat(newBlocks).sort((a, b) => newOrderMap[a.attributes.id] - newOrderMap[b.attributes.id])); // Select the first block to scroll into view when new blocks are added. if (newBlocks?.length > 0) { selectBlock(newBlocks[0].clientId); } } 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 ? imageData.find(({ id }) => id === block.attributes.id) : null; changedAttributes[block.clientId] = (0, _utils.getHrefAndDestination)(image, value, false, block.attributes, lightboxSetting); }); updateBlockAttributes(blocks, changedAttributes, true); const linkToText = [...linkOptions].find(linkType => linkType.value === value); createSuccessNotice((0, _i18n.sprintf)(/* translators: %s: image size settings */ (0, _i18n.__)('All gallery image links updated to: %s'), linkToText.noticeText), { id: 'gallery-attributes-linkTo', type: 'snackbar' }); } function setColumnsNumber(value) { setAttributes({ columns: value }); } function toggleImageCrop() { setAttributes({ imageCrop: !imageCrop }); } function toggleRandomOrder() { setAttributes({ randomOrder: !randomOrder }); } 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] = (0, _utils2.getUpdatedLinkTargetSettings)(newLinkTarget, block.attributes); }); updateBlockAttributes(blocks, changedAttributes, true); const noticeText = openInNewTab ? (0, _i18n.__)('All gallery images updated to open in new tab') : (0, _i18n.__)('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 ? imageData.find(({ id }) => id === block.attributes.id) : null; changedAttributes[block.clientId] = (0, _utils2.getImageSizeAttributes)(image, newSizeSlug); }); updateBlockAttributes(blocks, changedAttributes, true); const imageSize = imageSizeOptions.find(size => size.value === newSizeSlug); createSuccessNotice((0, _i18n.sprintf)(/* translators: %s: image size settings */ (0, _i18n.__)('All gallery image sizes updated to: %s'), imageSize.label), { id: 'gallery-attributes-sizeSlug', type: 'snackbar' }); } (0, _element.useEffect)(() => { // linkTo attribute must be saved so blocks don't break when changing image_default_link_type in options.php. if (!linkTo) { __unstableMarkNextChangeAsNotPersistent(); setAttributes({ linkTo: window?.wp?.media?.view?.settings?.defaultProps?.link || _constants.LINK_DESTINATION_NONE }); } }, [linkTo]); const hasImages = !!images.length; const hasImageIds = hasImages && images.some(image => !!image.id); const imagesUploading = images.some(img => !_element.Platform.isNative ? !img.id && img.url?.indexOf('blob:') === 0 : img.url?.indexOf('file:') === 0); // MediaPlaceholder props are different between web and native hence, we provide a platform-specific set. const mediaPlaceholderProps = _element.Platform.select({ web: { addToGallery: false, disableMediaButtons: imagesUploading, value: {} }, native: { addToGallery: hasImageIds, isAppender: hasImages, disableMediaButtons: hasImages && !isSelected || imagesUploading, value: hasImageIds ? images : {}, autoOpenMediaUpload: !hasImages && isSelected && blockWasJustInserted, onFocus } }); const mediaPlaceholder = /*#__PURE__*/(0, _jsxRuntime.jsx)(_blockEditor.MediaPlaceholder, { handleUpload: false, icon: _sharedIcon.sharedIcon, labels: { title: (0, _i18n.__)('Gallery'), instructions: PLACEHOLDER_TEXT }, onSelect: updateImages, accept: "image/*", allowedTypes: ALLOWED_MEDIA_TYPES, multiple: true, onError: onUploadError, ...mediaPlaceholderProps }); const blockProps = (0, _blockEditor.useBlockProps)({ className: (0, _clsx.default)(className, 'has-nested-images') }); const nativeInnerBlockProps = _element.Platform.isNative && { marginHorizontal: 0, marginVertical: 0 }; const innerBlocksProps = (0, _blockEditor.useInnerBlocksProps)(blockProps, { defaultBlock: DEFAULT_BLOCK, directInsert: true, orientation: 'horizontal', renderAppender: false, ...nativeInnerBlockProps }); if (!hasImages) { return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_primitives.View, { ...innerBlocksProps, children: [innerBlocksProps.children, mediaPlaceholder] }); } const hasLinkTo = linkTo && linkTo !== 'none'; return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, { children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_blockEditor.InspectorControls, { children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_components.PanelBody, { title: (0, _i18n.__)('Settings'), children: [images.length > 1 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.RangeControl, { __nextHasNoMarginBottom: true, label: (0, _i18n.__)('Columns'), value: columns ? columns : (0, _shared.defaultColumnsNumber)(images.length), onChange: setColumnsNumber, min: 1, max: Math.min(MAX_COLUMNS, images.length), ...MOBILE_CONTROL_PROPS_RANGE_CONTROL, required: true, __next40pxDefaultSize: true }), imageSizeOptions?.length > 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.SelectControl, { __nextHasNoMarginBottom: true, label: (0, _i18n.__)('Resolution'), help: (0, _i18n.__)('Select the size of the source images.'), value: sizeSlug, options: imageSizeOptions, onChange: updateImagesSize, hideCancelButton: true, size: "__unstable-large" }), _element.Platform.isNative ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.SelectControl, { __nextHasNoMarginBottom: true, label: (0, _i18n.__)('Link'), value: linkTo, onChange: setLinkTo, options: linkOptions, hideCancelButton: true, size: "__unstable-large" }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.ToggleControl, { __nextHasNoMarginBottom: true, label: (0, _i18n.__)('Crop images to fit'), checked: !!imageCrop, onChange: toggleImageCrop }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.ToggleControl, { __nextHasNoMarginBottom: true, label: (0, _i18n.__)('Randomize order'), checked: !!randomOrder, onChange: toggleRandomOrder }), hasLinkTo && /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.ToggleControl, { __nextHasNoMarginBottom: true, label: (0, _i18n.__)('Open images in new tab'), checked: linkTarget === '_blank', onChange: toggleOpenInNewTab }), _element.Platform.isWeb && !imageSizeOptions && hasImageIds && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_components.BaseControl, { className: "gallery-image-sizes", __nextHasNoMarginBottom: true, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.BaseControl.VisualLabel, { children: (0, _i18n.__)('Resolution') }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_primitives.View, { className: "gallery-image-sizes__loading", children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Spinner, {}), (0, _i18n.__)('Loading options…')] })] })] }) }), _element.Platform.isWeb ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_blockEditor.BlockControls, { group: "block", children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.ToolbarDropdownMenu, { icon: _icons.link, label: (0, _i18n.__)('Link'), children: ({ onClose }) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.MenuGroup, { children: linkOptions.map(linkItem => { const isOptionSelected = linkTo === linkItem.value; return /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.MenuItem, { isSelected: isOptionSelected, className: (0, _clsx.default)('components-dropdown-menu__menu-item', { 'is-active': isOptionSelected }), iconPosition: "left", icon: linkItem.icon, onClick: () => { setLinkTo(linkItem.value); onClose(); }, role: "menuitemradio", info: linkItem.infoText, children: linkItem.label }, linkItem.value); }) }) }) }) : null, _element.Platform.isWeb && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, { children: [!multiGallerySelection && /*#__PURE__*/(0, _jsxRuntime.jsx)(_blockEditor.BlockControls, { group: "other", children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_blockEditor.MediaReplaceFlow, { allowedTypes: ALLOWED_MEDIA_TYPES, accept: "image/*", handleUpload: false, onSelect: updateImages, name: (0, _i18n.__)('Add'), multiple: true, mediaIds: images.filter(image => image.id).map(image => image.id), addToGallery: hasImageIds }) }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_gapStyles.default, { blockGap: attributes.style?.spacing?.blockGap, clientId: clientId })] }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_gallery.default, { ...props, isContentLocked: isContentLocked, images: images, mediaPlaceholder: !hasImages || _element.Platform.isNative ? mediaPlaceholder : undefined, blockProps: innerBlocksProps, insertBlocksAfter: insertBlocksAfter, multiGallerySelection: multiGallerySelection })] }); } //# sourceMappingURL=edit.js.map