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