Back

94.73% Statements 36/38
80% Branches 8/10
100% Functions 15/15
93.75% Lines 30/32

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100                      7x 6x                                                 7x     22x       7x   22x     7x 22x   22x 22x 22x 73x   22x     22x 13x 3x   19x 19x 87x   22x     20x   2x 2x                           17x 26x   17x 17x 12x   5x 5x      
import { createBlobURL, isBlobURL, revokeBlobURL } from '@wordpress/blob';
import { useDispatch } from '@wordpress/data';
import { useEffect } from '@wordpress/element';
 
/**
 * Whether a media selection holds files to upload rather than library attachments.
 * Not `instanceof File`: files picked in the editor iframe come from that window's File.
 *
 * @param {Array} selection - What a media placeholder passed to `onSelect`.
 * @return {boolean} Whether the selection holds files.
 */
export const isFileSelection = selection =>
	Object.prototype.toString.call( selection?.[ 0 ] ) === '[object File]';
 
/**
 * Uploads each file on its own into a placeholder item added up front, which keeps the selection order:
 * client-side media processing reports one file per callback. Each result replaces its item, found by
 * URL, and a failure removes it.
 *
 * @param {object}          options              - Options.
 * @param {FileList|File[]} options.files        - The files to upload.
 * @param {Function}        options.mediaUpload  - The block editor's `mediaUpload` setting.
 * @param {string[]}        options.allowedTypes - The media types the block accepts.
 * @param {Function}        options.getItems     - Returns the block's current items, each with a `url`.
 * @param {Function}        options.setItems     - Saves the block's items.
 * @param {Function}        options.toItem       - Turns an attachment, and the item it replaces, into an item.
 * @param {Function}        options.onError      - Reports an upload's error message.
 */
export default function uploadFilesIndividually( {
	files,
	mediaUpload,
	allowedTypes,
	getItems,
	setItems,
	toItem,
	onError,
} ) {
	Iif ( ! mediaUpload ) {
		return;
	}
	const uploads = Array.from( files ).map( file => ( {
		file,
		placeholderUrl: createBlobURL( file ),
	} ) );
	setItems( [
		...getItems(),
		...uploads.map( ( { placeholderUrl } ) => ( { url: placeholderUrl } ) ),
	] );
 
	uploads.forEach( ( { file, placeholderUrl } ) => {
		let currentUrl = placeholderUrl;
		// Without media, the upload failed: drop its item.
		const updateUpload = media => {
			revokeBlobURL( placeholderUrl );
			const items = getItems();
			const index = items.findIndex( ( { url } ) => url === currentUrl );
			// The user removed this item while it was still uploading.
			Iif ( index === -1 ) {
				return;
			}
			if ( ! media ) {
				setItems( items.filter( ( item, i ) => i !== index ) );
				return;
			}
			const item = toItem( media, items[ index ] );
			currentUrl = item.url;
			setItems( items.map( ( existing, i ) => ( i === index ? item : existing ) ) );
		};
		mediaUpload( {
			allowedTypes,
			filesList: [ file ],
			onFileChange: ( [ media ] ) => updateUpload( media ),
			onError: message => {
				updateUpload();
				onError( message );
			},
		} );
	} );
}
 
/**
 * Locks post saving while `items` hold an upload's placeholder, so its blob URL is never saved.
 * Following the rendered items releases the lock however a placeholder goes, the block's removal included.
 *
 * @param {Array}  items    - The block's items, each with a `url`.
 * @param {string} lockName - A lock name unique to the block.
 */
export function usePlaceholderSaveLock( items, lockName ) {
	const { lockPostSaving, unlockPostSaving } = useDispatch( 'core/editor' );
	const hasPlaceholder = items.some( ( { url } ) => isBlobURL( url ) );
 
	useEffect( () => {
		if ( ! hasPlaceholder ) {
			return;
		}
		lockPostSaving( lockName );
		return () => unlockPostSaving( lockName );
	}, [ hasPlaceholder, lockName, lockPostSaving, unlockPostSaving ] );
}