UNPKG

@wordpress/block-library

Version:
364 lines (363 loc) 11.7 kB
// packages/block-library/src/image/edit.js import clsx from "clsx"; import { isBlobURL, createBlobURL } from "@wordpress/blob"; import { createBlock, getBlockBindingsSource } from "@wordpress/blocks"; import { Placeholder } from "@wordpress/components"; import { useDispatch, useSelect } from "@wordpress/data"; import { BlockIcon, useBlockProps, MediaPlaceholder, store as blockEditorStore, __experimentalUseBorderProps as useBorderProps, __experimentalGetShadowClassesAndStyles as getShadowClassesAndStyles, useBlockEditingMode } from "@wordpress/block-editor"; import { useEffect, useRef, useState } from "@wordpress/element"; import { __, sprintf } from "@wordpress/i18n"; import { image as icon, plugins as pluginsIcon } from "@wordpress/icons"; import { store as noticesStore } from "@wordpress/notices"; import { useResizeObserver } from "@wordpress/compose"; import { useUploadMediaFromBlobURL } from "../utils/hooks"; import Image from "./image"; import { isValidFileType } from "./utils"; import { useMaxWidthObserver } from "./use-max-width-observer"; import { LINK_DESTINATION_ATTACHMENT, LINK_DESTINATION_CUSTOM, LINK_DESTINATION_MEDIA, LINK_DESTINATION_NONE, ALLOWED_MEDIA_TYPES, DEFAULT_MEDIA_SIZE_SLUG } from "./constants"; import { Fragment, jsx, jsxs } from "react/jsx-runtime"; var pickRelevantMediaFiles = (image, size) => { const imageProps = Object.fromEntries( Object.entries(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; }; var isExternalImage = (id, url) => url && !id && !isBlobURL(url); function hasSize(image, size) { return "url" in (image?.sizes?.[size] ?? {}) || "source_url" in (image?.media_details?.sizes?.[size] ?? {}); } function ImageEdit({ attributes, setAttributes, isSelected: isSingleSelected, className, insertBlocksAfter, onReplace, context, clientId, __unstableParentLayout: parentLayout }) { const { url = "", caption, id, width, height, sizeSlug, aspectRatio, scale, align, metadata } = attributes; const [temporaryURL, setTemporaryURL] = useState(attributes.blob); const containerRef = useRef(); const layoutType = parentLayout?.type || parentLayout?.default?.type; const isMaxWidthContainerWidth = !layoutType || layoutType !== "flex" && layoutType !== "grid"; const [maxWidthObserver, maxContentWidth] = useMaxWidthObserver(); const [placeholderResizeListener, { width: placeholderWidth }] = useResizeObserver(); const isSmallContainer = placeholderWidth && placeholderWidth < 160; const captionRef = useRef(); useEffect(() => { captionRef.current = caption; }, [caption]); const { __unstableMarkNextChangeAsNotPersistent, replaceBlock } = useDispatch(blockEditorStore); useEffect(() => { if (["wide", "full"].includes(align)) { __unstableMarkNextChangeAsNotPersistent(); setAttributes({ width: void 0, height: void 0, aspectRatio: void 0, scale: void 0 }); } }, [__unstableMarkNextChangeAsNotPersistent, align, setAttributes]); const { getSettings, getBlockRootClientId, getBlockName, canInsertBlockType } = useSelect(blockEditorStore); const blockEditingMode = useBlockEditingMode(); const { createErrorNotice } = useDispatch(noticesStore); function onUploadError(message) { createErrorNotice(message, { type: "snackbar" }); setAttributes({ src: void 0, id: void 0, url: void 0, blob: void 0 }); } function onSelectImagesList(images) { const win = containerRef.current?.ownerDocument.defaultView; if (images.every((file) => file instanceof win.File)) { const files = images; const rootClientId = getBlockRootClientId(clientId); if (files.some((file) => !isValidFileType(file))) { createErrorNotice( __( "If uploading to a gallery all files need to be image formats" ), { id: "gallery-upload-invalid-file", type: "snackbar" } ); } const imageBlocks = files.filter((file) => isValidFileType(file)).map( (file) => createBlock("core/image", { blob: createBlobURL(file) }) ); if (getBlockName(rootClientId) === "core/gallery") { replaceBlock(clientId, imageBlocks); } else if (canInsertBlockType("core/gallery", rootClientId)) { const galleryBlock = createBlock( "core/gallery", {}, imageBlocks ); replaceBlock(clientId, galleryBlock); } } } function onSelectImage(media) { if (Array.isArray(media)) { onSelectImagesList(media); return; } if (!media || !media.url) { setAttributes({ url: void 0, alt: void 0, id: void 0, title: void 0, caption: void 0, blob: void 0 }); setTemporaryURL(); return; } if (isBlobURL(media.url)) { setTemporaryURL(media.url); return; } const { imageDefaultSize } = getSettings(); let newSize = DEFAULT_MEDIA_SIZE_SLUG; if (sizeSlug && hasSize(media, sizeSlug)) { newSize = sizeSlug; } else if (hasSize(media, imageDefaultSize)) { newSize = imageDefaultSize; } let mediaAttributes = pickRelevantMediaFiles(media, newSize); if (typeof mediaAttributes.caption === "string" && mediaAttributes.caption.includes("\n")) { mediaAttributes.caption = mediaAttributes.caption.replace( /\n/g, "<br>" ); } if (captionRef.current && !mediaAttributes.caption) { const { caption: omittedCaption, ...restMediaAttributes } = mediaAttributes; mediaAttributes = restMediaAttributes; } let additionalAttributes; if (!media.id || media.id !== id) { additionalAttributes = { sizeSlug: newSize }; } let linkDestination = attributes.linkDestination; if (!linkDestination) { switch (window?.wp?.media?.view?.settings?.defaultProps?.link || LINK_DESTINATION_NONE) { case "file": case LINK_DESTINATION_MEDIA: linkDestination = LINK_DESTINATION_MEDIA; break; case "post": case LINK_DESTINATION_ATTACHMENT: linkDestination = LINK_DESTINATION_ATTACHMENT; break; case LINK_DESTINATION_CUSTOM: linkDestination = LINK_DESTINATION_CUSTOM; break; case LINK_DESTINATION_NONE: linkDestination = LINK_DESTINATION_NONE; break; } } let href; switch (linkDestination) { case LINK_DESTINATION_MEDIA: href = media.url; break; case LINK_DESTINATION_ATTACHMENT: href = media.link; break; } mediaAttributes.href = href; setAttributes({ blob: void 0, ...mediaAttributes, ...additionalAttributes, linkDestination }); setTemporaryURL(); } function onSelectURL(newURL) { if (newURL !== url) { setAttributes({ blob: void 0, url: newURL, id: void 0, sizeSlug: getSettings().imageDefaultSize }); setTemporaryURL(); } } useUploadMediaFromBlobURL({ url: temporaryURL, allowedTypes: ALLOWED_MEDIA_TYPES, onChange: onSelectImage, onError: onUploadError }); const isExternal = isExternalImage(id, url); const src = isExternal ? url : void 0; const mediaPreview = !!url && /* @__PURE__ */ jsx( "img", { alt: __("Edit image"), title: __("Edit image"), className: "edit-image-preview", src: url } ); const borderProps = useBorderProps(attributes); const shadowProps = getShadowClassesAndStyles(attributes); const classes = clsx(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 = useBlockProps({ ref: containerRef, className: classes }); const { lockUrlControls = false, lockUrlControlsMessage } = useSelect( (select) => { if (!isSingleSelected) { return {}; } const blockBindingsSource = getBlockBindingsSource( metadata?.bindings?.url?.source ); return { lockUrlControls: !!metadata?.bindings?.url && !blockBindingsSource?.canUserEditValue?.({ select, context, args: metadata?.bindings?.url?.args }), lockUrlControlsMessage: blockBindingsSource?.label ? sprintf( /* translators: %s: Label of the bindings source. */ __("Connected to %s"), blockBindingsSource.label ) : __("Connected to dynamic data") }; }, [context, isSingleSelected, metadata?.bindings?.url] ); const placeholder = (content) => { return /* @__PURE__ */ jsxs( Placeholder, { className: clsx("block-editor-media-placeholder", { [borderProps.className]: !!borderProps.className && !isSingleSelected }), icon: !isSmallContainer && (lockUrlControls ? pluginsIcon : icon), withIllustration: !isSingleSelected || isSmallContainer, label: !isSmallContainer && __("Image"), instructions: !lockUrlControls && !isSmallContainer && __( "Drag and drop an image, upload, or choose from your library." ), style: { aspectRatio: !(width && height) && aspectRatio ? aspectRatio : void 0, 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__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsxs("figure", { ...blockProps, children: [ /* @__PURE__ */ jsx( Image, { temporaryURL, attributes, setAttributes, isSingleSelected, insertBlocksAfter, onReplace, onSelectImage, onSelectURL, onUploadError, context, clientId, blockEditingMode, parentLayoutType: layoutType, maxContentWidth } ), /* @__PURE__ */ jsx( MediaPlaceholder, { icon: /* @__PURE__ */ jsx(BlockIcon, { icon }), onSelect: onSelectImage, onSelectURL, onError: onUploadError, placeholder, allowedTypes: ALLOWED_MEDIA_TYPES, handleUpload: (files) => files.length === 1, value: { id, src }, 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 edit_default = ImageEdit; export { ImageEdit, edit_default as default, isExternalImage, pickRelevantMediaFiles }; //# sourceMappingURL=edit.js.map