UNPKG

@wordpress/block-library

Version:
393 lines (381 loc) 14.2 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.ImageEdit = ImageEdit; exports.pickRelevantMediaFiles = exports.isExternalImage = exports.default = void 0; var _clsx = _interopRequireDefault(require("clsx")); var _blob = require("@wordpress/blob"); var _blocks = require("@wordpress/blocks"); var _components = require("@wordpress/components"); var _data = require("@wordpress/data"); var _blockEditor = require("@wordpress/block-editor"); var _element = require("@wordpress/element"); var _i18n = require("@wordpress/i18n"); var _icons = require("@wordpress/icons"); var _notices = require("@wordpress/notices"); var _compose = require("@wordpress/compose"); var _hooks = require("../utils/hooks"); var _image = _interopRequireDefault(require("./image")); var _utils = require("./utils"); var _useMaxWidthObserver = require("./use-max-width-observer"); var _constants = require("./constants"); var _jsxRuntime = require("react/jsx-runtime"); /** * External dependencies */ /** * WordPress dependencies */ /** * Internal dependencies */ /** * Module constants */ const pickRelevantMediaFiles = (image, size) => { const imageProps = Object.fromEntries(Object.entries(image !== null && image !== void 0 ? image : {}).filter(([key]) => ['alt', 'id', 'link', 'caption'].includes(key))); imageProps.url = image?.sizes?.[size]?.url || image?.media_details?.sizes?.[size]?.source_url || image.url; return imageProps; }; /** * Is the url for the image hosted externally. An externally hosted image has no * id and is not a blob url. * * @param {number=} id The id of the image. * @param {string=} url The url of the image. * * @return {boolean} Is the url an externally hosted url? */ exports.pickRelevantMediaFiles = pickRelevantMediaFiles; const isExternalImage = (id, url) => url && !id && !(0, _blob.isBlobURL)(url); /** * Checks if WP generated the specified image size. Size generation is skipped * when the image is smaller than the said size. * * @param {Object} image * @param {string} size * * @return {boolean} Whether or not it has default image size. */ exports.isExternalImage = isExternalImage; function hasSize(image, size) { var _image$sizes$size, _image$media_details$; return 'url' in ((_image$sizes$size = image?.sizes?.[size]) !== null && _image$sizes$size !== void 0 ? _image$sizes$size : {}) || 'source_url' in ((_image$media_details$ = image?.media_details?.sizes?.[size]) !== null && _image$media_details$ !== void 0 ? _image$media_details$ : {}); } function ImageEdit({ attributes, setAttributes, isSelected: isSingleSelected, className, insertBlocksAfter, onReplace, context, clientId, __unstableParentLayout: parentLayout }) { const { url = '', alt, caption, id, width, height, sizeSlug, aspectRatio, scale, align, metadata } = attributes; const [temporaryURL, setTemporaryURL] = (0, _element.useState)(attributes.blob); const containerRef = (0, _element.useRef)(); // Only observe the max width from the parent container when the parent layout is not flex nor grid. // This won't work for them because the container width changes with the image. // TODO: Find a way to observe the container width for flex and grid layouts. const layoutType = parentLayout?.type || parentLayout?.default?.type; const isMaxWidthContainerWidth = !layoutType || layoutType !== 'flex' && layoutType !== 'grid'; const [maxWidthObserver, maxContentWidth] = (0, _useMaxWidthObserver.useMaxWidthObserver)(); const [placeholderResizeListener, { width: placeholderWidth }] = (0, _compose.useResizeObserver)(); const isSmallContainer = placeholderWidth && placeholderWidth < 160; const altRef = (0, _element.useRef)(); (0, _element.useEffect)(() => { altRef.current = alt; }, [alt]); const captionRef = (0, _element.useRef)(); (0, _element.useEffect)(() => { captionRef.current = caption; }, [caption]); const { __unstableMarkNextChangeAsNotPersistent, replaceBlock } = (0, _data.useDispatch)(_blockEditor.store); (0, _element.useEffect)(() => { if (['wide', 'full'].includes(align)) { __unstableMarkNextChangeAsNotPersistent(); setAttributes({ width: undefined, height: undefined, aspectRatio: undefined, scale: undefined }); } }, [__unstableMarkNextChangeAsNotPersistent, align, setAttributes]); const { getSettings, getBlockRootClientId, getBlockName, canInsertBlockType } = (0, _data.useSelect)(_blockEditor.store); const blockEditingMode = (0, _blockEditor.useBlockEditingMode)(); const { createErrorNotice } = (0, _data.useDispatch)(_notices.store); function onUploadError(message) { createErrorNotice(message, { type: 'snackbar' }); setAttributes({ src: undefined, id: undefined, url: undefined, blob: undefined }); } function onSelectImagesList(images) { const win = containerRef.current?.ownerDocument.defaultView; if (images.every(file => file instanceof win.File)) { /** @type {File[]} */ const files = images; const rootClientId = getBlockRootClientId(clientId); if (files.some(file => !(0, _utils.isValidFileType)(file))) { // Copied from the same notice in the gallery block. createErrorNotice((0, _i18n.__)('If uploading to a gallery all files need to be image formats'), { id: 'gallery-upload-invalid-file', type: 'snackbar' }); } const imageBlocks = files.filter(file => (0, _utils.isValidFileType)(file)).map(file => (0, _blocks.createBlock)('core/image', { blob: (0, _blob.createBlobURL)(file) })); if (getBlockName(rootClientId) === 'core/gallery') { replaceBlock(clientId, imageBlocks); } else if (canInsertBlockType('core/gallery', rootClientId)) { const galleryBlock = (0, _blocks.createBlock)('core/gallery', {}, imageBlocks); replaceBlock(clientId, galleryBlock); } } } function onSelectImage(media) { if (Array.isArray(media)) { onSelectImagesList(media); return; } if (!media || !media.url) { setAttributes({ url: undefined, alt: undefined, id: undefined, title: undefined, caption: undefined, blob: undefined }); setTemporaryURL(); return; } if ((0, _blob.isBlobURL)(media.url)) { setTemporaryURL(media.url); return; } const { imageDefaultSize } = getSettings(); // Try to use the previous selected image size if its available // otherwise try the default image size or fallback to "full" let newSize = _constants.DEFAULT_MEDIA_SIZE_SLUG; if (sizeSlug && hasSize(media, sizeSlug)) { newSize = sizeSlug; } else if (hasSize(media, imageDefaultSize)) { newSize = imageDefaultSize; } let mediaAttributes = pickRelevantMediaFiles(media, newSize); // If a caption text was meanwhile written by the user, // make sure the text is not overwritten by empty captions. if (captionRef.current && !mediaAttributes.caption) { const { caption: omittedCaption, ...restMediaAttributes } = mediaAttributes; mediaAttributes = restMediaAttributes; } let additionalAttributes; // Reset the dimension attributes if changing to a different image. if (!media.id || media.id !== id) { additionalAttributes = { sizeSlug: newSize }; } // Check if default link setting should be used. let linkDestination = attributes.linkDestination; if (!linkDestination) { // Use the WordPress option to determine the proper default. // The constants used in Gutenberg do not match WP options so a little more complicated than ideal. // TODO: fix this in a follow up PR, requires updating media-text and ui component. switch (window?.wp?.media?.view?.settings?.defaultProps?.link || _constants.LINK_DESTINATION_NONE) { case 'file': case _constants.LINK_DESTINATION_MEDIA: linkDestination = _constants.LINK_DESTINATION_MEDIA; break; case 'post': case _constants.LINK_DESTINATION_ATTACHMENT: linkDestination = _constants.LINK_DESTINATION_ATTACHMENT; break; case _constants.LINK_DESTINATION_CUSTOM: linkDestination = _constants.LINK_DESTINATION_CUSTOM; break; case _constants.LINK_DESTINATION_NONE: linkDestination = _constants.LINK_DESTINATION_NONE; break; } } // Check if the image is linked to it's media. let href; switch (linkDestination) { case _constants.LINK_DESTINATION_MEDIA: href = media.url; break; case _constants.LINK_DESTINATION_ATTACHMENT: href = media.link; break; } mediaAttributes.href = href; setAttributes({ blob: undefined, ...mediaAttributes, ...additionalAttributes, linkDestination }); setTemporaryURL(); } function onSelectURL(newURL) { if (newURL !== url) { setAttributes({ blob: undefined, url: newURL, id: undefined, sizeSlug: getSettings().imageDefaultSize }); setTemporaryURL(); } } (0, _hooks.useUploadMediaFromBlobURL)({ url: temporaryURL, allowedTypes: _constants.ALLOWED_MEDIA_TYPES, onChange: onSelectImage, onError: onUploadError }); const isExternal = isExternalImage(id, url); const src = isExternal ? url : undefined; const mediaPreview = !!url && /*#__PURE__*/(0, _jsxRuntime.jsx)("img", { alt: (0, _i18n.__)('Edit image'), title: (0, _i18n.__)('Edit image'), className: "edit-image-preview", src: url }); const borderProps = (0, _blockEditor.__experimentalUseBorderProps)(attributes); const shadowProps = (0, _blockEditor.__experimentalGetShadowClassesAndStyles)(attributes); const classes = (0, _clsx.default)(className, { 'is-transient': !!temporaryURL, 'is-resized': !!width || !!height, [`size-${sizeSlug}`]: sizeSlug, 'has-custom-border': !!borderProps.className || borderProps.style && Object.keys(borderProps.style).length > 0 }); const blockProps = (0, _blockEditor.useBlockProps)({ ref: containerRef, className: classes }); // Much of this description is duplicated from MediaPlaceholder. const { lockUrlControls = false, lockUrlControlsMessage } = (0, _data.useSelect)(select => { if (!isSingleSelected) { return {}; } const blockBindingsSource = (0, _blocks.getBlockBindingsSource)(metadata?.bindings?.url?.source); return { lockUrlControls: !!metadata?.bindings?.url && !blockBindingsSource?.canUserEditValue?.({ select, context, args: metadata?.bindings?.url?.args }), lockUrlControlsMessage: blockBindingsSource?.label ? (0, _i18n.sprintf)(/* translators: %s: Label of the bindings source. */ (0, _i18n.__)('Connected to %s'), blockBindingsSource.label) : (0, _i18n.__)('Connected to dynamic data') }; }, [context, isSingleSelected, metadata?.bindings?.url]); const placeholder = content => { return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_components.Placeholder, { className: (0, _clsx.default)('block-editor-media-placeholder', { [borderProps.className]: !!borderProps.className && !isSingleSelected }), icon: !isSmallContainer && (lockUrlControls ? _icons.plugins : _icons.image), withIllustration: !isSingleSelected || isSmallContainer, label: !isSmallContainer && (0, _i18n.__)('Image'), instructions: !lockUrlControls && !isSmallContainer && (0, _i18n.__)('Drag and drop an image, upload, or choose from your library.'), style: { aspectRatio: !(width && height) && aspectRatio ? aspectRatio : undefined, width: height && aspectRatio ? '100%' : width, height: width && aspectRatio ? '100%' : height, objectFit: scale, ...borderProps.style, ...shadowProps.style }, children: [lockUrlControls && !isSmallContainer && lockUrlControlsMessage, !lockUrlControls && !isSmallContainer && content, placeholderResizeListener] }); }; return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, { children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)("figure", { ...blockProps, children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_image.default, { temporaryURL: temporaryURL, attributes: attributes, setAttributes: setAttributes, isSingleSelected: isSingleSelected, insertBlocksAfter: insertBlocksAfter, onReplace: onReplace, onSelectImage: onSelectImage, onSelectURL: onSelectURL, onUploadError: onUploadError, context: context, clientId: clientId, blockEditingMode: blockEditingMode, parentLayoutType: layoutType, maxContentWidth: maxContentWidth }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_blockEditor.MediaPlaceholder, { icon: /*#__PURE__*/(0, _jsxRuntime.jsx)(_blockEditor.BlockIcon, { icon: _icons.image }), onSelect: onSelectImage, onSelectURL: onSelectURL, onError: onUploadError, placeholder: placeholder, accept: "image/*", allowedTypes: _constants.ALLOWED_MEDIA_TYPES, handleUpload: files => files.length === 1, value: { id, src }, mediaPreview: mediaPreview, disableMediaButtons: temporaryURL || url })] }), // The listener cannot be placed as the first element as it will break the in-between inserter. // See https://github.com/WordPress/gutenberg/blob/71134165868298fc15e22896d0c28b41b3755ff7/packages/block-editor/src/components/block-list/use-in-between-inserter.js#L120 isSingleSelected && isMaxWidthContainerWidth && maxWidthObserver] }); } var _default = exports.default = ImageEdit; //# sourceMappingURL=edit.js.map