@wordpress/upload-media
Version:
Core media upload logic.
335 lines (318 loc) • 7.76 kB
JavaScript
/**
* 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