UNPKG

@wordpress/editor

Version:
254 lines (244 loc) 7.48 kB
/** * WordPress dependencies */ import { PanelBody, Button, Spinner, __unstableMotion as motion, __unstableAnimatePresence as AnimatePresence } from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { store as blockEditorStore } from '@wordpress/block-editor'; import { useState } from '@wordpress/element'; import { isBlobURL } from '@wordpress/blob'; /** * Internal dependencies */ import { fetchMedia } from './media-util'; import { jsx as _jsx } from "react/jsx-runtime"; import { jsxs as _jsxs } from "react/jsx-runtime"; function flattenBlocks(blocks) { const result = []; blocks.forEach(block => { result.push(block); result.push(...flattenBlocks(block.innerBlocks)); }); return result; } /** * Determine whether a block has external media. * * Different blocks use different attribute names (and potentially * different logic as well) in determining whether the media is * present, and whether it's external. * * @param {{name: string, attributes: Object}} block The block. * @return {boolean?} Whether the block has external media */ function hasExternalMedia(block) { if (block.name === 'core/image' || block.name === 'core/cover') { return block.attributes.url && !block.attributes.id; } if (block.name === 'core/media-text') { return block.attributes.mediaUrl && !block.attributes.mediaId; } return undefined; } /** * Retrieve media info from a block. * * Different blocks use different attribute names, so we need this * function to normalize things into a consistent naming scheme. * * @param {{name: string, attributes: Object}} block The block. * @return {{url: ?string, alt: ?string, id: ?number}} The media info for the block. */ function getMediaInfo(block) { if (block.name === 'core/image' || block.name === 'core/cover') { const { url, alt, id } = block.attributes; return { url, alt, id }; } if (block.name === 'core/media-text') { const { mediaUrl: url, mediaAlt: alt, mediaId: id } = block.attributes; return { url, alt, id }; } return {}; } // Image component to represent a single image in the upload dialog. function Image({ clientId, alt, url }) { const { selectBlock } = useDispatch(blockEditorStore); return /*#__PURE__*/_jsx(motion.img, { tabIndex: 0, role: "button", "aria-label": __('Select image block.'), onClick: () => { selectBlock(clientId); }, onKeyDown: event => { if (event.key === 'Enter' || event.key === ' ') { selectBlock(clientId); event.preventDefault(); } }, alt: alt, src: url, animate: { opacity: 1 }, exit: { opacity: 0, scale: 0 }, style: { width: '36px', height: '36px', objectFit: 'cover', borderRadius: '2px', cursor: 'pointer' }, whileHover: { scale: 1.08 } }, clientId); } export default function MaybeUploadMediaPanel() { const [isUploading, setIsUploading] = useState(false); const [isAnimating, setIsAnimating] = useState(false); const [hadUploadError, setHadUploadError] = useState(false); const { editorBlocks, mediaUpload } = useSelect(select => ({ editorBlocks: select(blockEditorStore).getBlocks(), mediaUpload: select(blockEditorStore).getSettings().mediaUpload }), []); // Get a list of blocks with external media. const blocksWithExternalMedia = flattenBlocks(editorBlocks).filter(block => hasExternalMedia(block)); const { updateBlockAttributes } = useDispatch(blockEditorStore); if (!mediaUpload || !blocksWithExternalMedia.length) { return null; } const panelBodyTitle = [__('Suggestion:'), /*#__PURE__*/_jsx("span", { className: "editor-post-publish-panel__link", children: __('External media') }, "label")]; /** * Update an individual block to point to newly-added library media. * * Different blocks use different attribute names, so we need this * function to ensure we modify the correct attributes for each type. * * @param {{name: string, attributes: Object}} block The block. * @param {{id: number, url: string}} media Media library file info. */ function updateBlockWithUploadedMedia(block, media) { if (block.name === 'core/image' || block.name === 'core/cover') { updateBlockAttributes(block.clientId, { id: media.id, url: media.url }); } if (block.name === 'core/media-text') { updateBlockAttributes(block.clientId, { mediaId: media.id, mediaUrl: media.url }); } } // Handle fetching and uploading all external media in the post. function uploadImages() { setIsUploading(true); setHadUploadError(false); // Multiple blocks can be using the same URL, so we // should ensure we only fetch and upload each of them once. const mediaUrls = new Set(blocksWithExternalMedia.map(block => { const { url } = getMediaInfo(block); return url; })); // Create an upload promise for each URL, that we can wait for in all // blocks that make use of that media. const uploadPromises = Object.fromEntries(Object.entries(fetchMedia([...mediaUrls])).map(([url, filePromise]) => { const uploadPromise = filePromise.then(blob => new Promise((resolve, reject) => { mediaUpload({ filesList: [blob], onFileChange: ([media]) => { if (isBlobURL(media.url)) { return; } resolve(media); }, onError() { reject(); } }); })); return [url, uploadPromise]; })); // Wait for all blocks to be updated with library media. Promise.allSettled(blocksWithExternalMedia.map(block => { const { url } = getMediaInfo(block); return uploadPromises[url].then(media => updateBlockWithUploadedMedia(block, media)).then(() => setIsAnimating(true)).catch(() => setHadUploadError(true)); })).finally(() => { setIsUploading(false); }); } return /*#__PURE__*/_jsxs(PanelBody, { initialOpen: true, title: panelBodyTitle, children: [/*#__PURE__*/_jsx("p", { children: __('Upload external images to the Media Library. Images from different domains may load slowly, display incorrectly, or be removed unexpectedly.') }), /*#__PURE__*/_jsxs("div", { style: { display: 'inline-flex', flexWrap: 'wrap', gap: '8px' }, children: [/*#__PURE__*/_jsx(AnimatePresence, { onExitComplete: () => setIsAnimating(false), children: blocksWithExternalMedia.map(block => { const { url, alt } = getMediaInfo(block); return /*#__PURE__*/_jsx(Image, { clientId: block.clientId, url: url, alt: alt }, block.clientId); }) }), isUploading || isAnimating ? /*#__PURE__*/_jsx(Spinner, {}) : /*#__PURE__*/_jsx(Button, { __next40pxDefaultSize: true, variant: "primary", onClick: uploadImages, children: __('Upload') })] }), hadUploadError && /*#__PURE__*/_jsx("p", { children: __('Upload failed, try again.') })] }); } //# sourceMappingURL=maybe-upload-media.js.map