UNPKG

@wordpress/upload-media

Version:
335 lines (318 loc) 7.76 kB
/** * External dependencies */ import { v4 as uuidv4 } from 'uuid'; /** * WordPress dependencies */ import { createBlobURL, isBlobURL, revokeBlobURL } from '@wordpress/blob'; /** * Internal dependencies */ import { cloneFile, convertBlobToFile } from '../utils'; import { StubFile } from '../stub-file'; import { ItemStatus, OperationType, Type } from './types'; /** * Adds a new item to the upload queue. * * @param $0 * @param $0.file File * @param [$0.batchId] Batch ID. * @param [$0.onChange] Function called each time a file or a temporary representation of the file is available. * @param [$0.onSuccess] Function called after the file is uploaded. * @param [$0.onBatchSuccess] Function called after a batch of files is uploaded. * @param [$0.onError] Function called when an error happens. * @param [$0.additionalData] Additional data to include in the request. * @param [$0.sourceUrl] Source URL. Used when importing a file from a URL or optimizing an existing file. * @param [$0.sourceAttachmentId] Source attachment ID. Used when optimizing an existing file for example. * @param [$0.abortController] Abort controller for upload cancellation. * @param [$0.operations] List of operations to perform. Defaults to automatically determined list, based on the file. */ export function addItem({ file: fileOrBlob, batchId, onChange, onSuccess, onBatchSuccess, onError, additionalData = {}, sourceUrl, sourceAttachmentId, abortController, operations }) { return async ({ dispatch }) => { const itemId = uuidv4(); // Hardening in case a Blob is passed instead of a File. // See https://github.com/WordPress/gutenberg/pull/65693 for an example. const file = convertBlobToFile(fileOrBlob); let blobUrl; // StubFile could be coming from addItemFromUrl(). if (!(file instanceof StubFile)) { blobUrl = createBlobURL(file); dispatch({ type: Type.CacheBlobUrl, id: itemId, blobUrl }); } dispatch({ type: Type.Add, item: { id: itemId, batchId, status: ItemStatus.Processing, sourceFile: cloneFile(file), file, attachment: { url: blobUrl }, additionalData: { convert_format: false, ...additionalData }, onChange, onSuccess, onBatchSuccess, onError, sourceUrl, sourceAttachmentId, abortController: abortController || new AbortController(), operations: Array.isArray(operations) ? operations : [OperationType.Prepare] } }); dispatch.processItem(itemId); }; } /** * Processes a single item in the queue. * * Runs the next operation in line and invokes any callbacks. * * @param id Item ID. */ export function processItem(id) { return async ({ select, dispatch }) => { if (select.isPaused()) { return; } const item = select.getItem(id); const { attachment, onChange, onSuccess, onBatchSuccess, batchId } = item; const operation = Array.isArray(item.operations?.[0]) ? item.operations[0][0] : item.operations?.[0]; if (attachment) { onChange?.([attachment]); } /* If there are no more operations, the item can be removed from the queue, but only if there are no thumbnails still being side-loaded, or if itself is a side-loaded item. */ if (!operation) { if (attachment) { onSuccess?.([attachment]); } // dispatch.removeItem( id ); dispatch.revokeBlobUrls(id); if (batchId && select.isBatchUploaded(batchId)) { onBatchSuccess?.(); } /* At this point we are dealing with a parent whose children haven't fully uploaded yet. Do nothing and let the removal happen once the last side-loaded item finishes. */ return; } if (!operation) { // This shouldn't really happen. return; } dispatch({ type: Type.OperationStart, id, operation }); switch (operation) { case OperationType.Prepare: dispatch.prepareItem(item.id); break; case OperationType.Upload: dispatch.uploadItem(id); break; } }; } /** * Returns an action object that pauses all processing in the queue. * * Useful for testing purposes. * * @return Action object. */ export function pauseQueue() { return { type: Type.PauseQueue }; } /** * Resumes all processing in the queue. * * Dispatches an action object for resuming the queue itself, * and triggers processing for each remaining item in the queue individually. */ export function resumeQueue() { return async ({ select, dispatch }) => { dispatch({ type: Type.ResumeQueue }); for (const item of select.getAllItems()) { dispatch.processItem(item.id); } }; } /** * Removes a specific item from the queue. * * @param id Item ID. */ export function removeItem(id) { return async ({ select, dispatch }) => { const item = select.getItem(id); if (!item) { return; } dispatch({ type: Type.Remove, id }); }; } /** * Finishes an operation for a given item ID and immediately triggers processing the next one. * * @param id Item ID. * @param updates Updated item data. */ export function finishOperation(id, updates) { return async ({ dispatch }) => { dispatch({ type: Type.OperationFinish, id, item: updates }); dispatch.processItem(id); }; } /** * Prepares an item for initial processing. * * Determines the list of operations to perform for a given image, * depending on its media type. * * For example, HEIF images first need to be converted, resized, * compressed, and then uploaded. * * Or videos need to be compressed, and then need poster generation * before upload. * * @param id Item ID. */ export function prepareItem(id) { return async ({ dispatch }) => { const operations = [OperationType.Upload]; dispatch({ type: Type.AddOperations, id, operations }); dispatch.finishOperation(id, {}); }; } /** * Uploads an item to the server. * * @param id Item ID. */ export function uploadItem(id) { return async ({ select, dispatch }) => { const item = select.getItem(id); select.getSettings().mediaUpload({ filesList: [item.file], additionalData: item.additionalData, signal: item.abortController?.signal, onFileChange: ([attachment]) => { if (!isBlobURL(attachment.url)) { dispatch.finishOperation(id, { attachment }); } }, onSuccess: ([attachment]) => { dispatch.finishOperation(id, { attachment }); }, onError: error => { dispatch.cancelItem(id, error); } }); }; } /** * Revokes all blob URLs for a given item, freeing up memory. * * @param id Item ID. */ export function revokeBlobUrls(id) { return async ({ select, dispatch }) => { const blobUrls = select.getBlobUrls(id); for (const blobUrl of blobUrls) { revokeBlobURL(blobUrl); } dispatch({ type: Type.RevokeBlobUrls, id }); }; } /** * Returns an action object that pauses all processing in the queue. * * Useful for testing purposes. * * @param settings * @return Action object. */ export function updateSettings(settings) { return { type: Type.UpdateSettings, settings }; } //# sourceMappingURL=private-actions.js.map