Skip to content

Commit

Permalink
Media Utils: add experimental sideloadMedia
Browse files Browse the repository at this point in the history
  • Loading branch information
swissspidy committed Oct 23, 2024
1 parent a775b7c commit d969b0b
Show file tree
Hide file tree
Showing 12 changed files with 210 additions and 3 deletions.
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions packages/media-utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ Undocumented declaration.

Undocumented declaration.

### privateApis

Private @wordpress/media-utils APIs.

### RestAttachment

Undocumented declaration.
Expand Down
3 changes: 2 additions & 1 deletion packages/media-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
"@wordpress/api-fetch": "*",
"@wordpress/blob": "*",
"@wordpress/element": "*",
"@wordpress/i18n": "*"
"@wordpress/i18n": "*",
"@wordpress/private-apis": "*"
},
"publishConfig": {
"access": "public"
Expand Down
2 changes: 2 additions & 0 deletions packages/media-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ export { validateMimeType } from './utils/validate-mime-type';
export { validateMimeTypeForUser } from './utils/validate-mime-type-for-user';

export type { Attachment, RestAttachment } from './utils/types';

export { privateApis } from './private-apis';
10 changes: 10 additions & 0 deletions packages/media-utils/src/lock-unlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* WordPress dependencies
*/
import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis';

export const { lock, unlock } =
__dangerousOptInToUnstableAPIsOnlyForCoreModules(
'I acknowledge private features are not for use in themes or plugins and doing so will break in the next version of WordPress.',
'@wordpress/media-utils'
);
14 changes: 14 additions & 0 deletions packages/media-utils/src/private-apis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Internal dependencies
*/
import { sideloadMedia } from './utils/sideload-media';
import { lock } from './lock-unlock';

/**
* Private @wordpress/media-utils APIs.
*/
export const privateApis = {};

lock( privateApis, {
sideloadMedia,
} );
82 changes: 82 additions & 0 deletions packages/media-utils/src/utils/sideload-media.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import type {
OnChangeHandler,
OnErrorHandler,
CreateSideloadFile,
RestAttachment,
} from './types';
import { sideloadToServer } from './sideload-to-server';
import { UploadError } from './upload-error';

const noop = () => {};

interface SideloadMediaArgs {
// Additional data to include in the request.
additionalData?: CreateSideloadFile;
// File to sideload.
file: File;
// Attachment ID.
attachmentId: RestAttachment[ 'id' ];
// Function called when an error happens.
onError?: OnErrorHandler;
// Function called each time a file or a temporary representation of the file is available.
onFileChange?: OnChangeHandler;
// Abort signal.
signal?: AbortSignal;
}

/**
* Uploads a file to the server without creating an attachment.
*
* @param $0 Parameters object passed to the function.
* @param $0.file Media File to Save.
* @param $0.attachmentId Parent attachment ID.
* @param $0.additionalData Additional data to include in the request.
* @param $0.signal Abort signal.
* @param $0.onFileChange Function called each time a file or a temporary representation of the file is available.
* @param $0.onError Function called when an error happens.
*/
export async function sideloadMedia( {
file,
attachmentId,
additionalData = {},
signal,
onFileChange,
onError = noop,
}: SideloadMediaArgs ) {
try {
const attachment = await sideloadToServer(
file,
attachmentId,
additionalData,
signal
);
onFileChange?.( [ attachment ] );
} catch ( error ) {
let message;
if ( error instanceof Error ) {
message = error.message;
} else {
message = sprintf(
// translators: %s: file name
__( 'Error while sideloading file %s to the server.' ),
file.name
);
}
onError(
new UploadError( {
code: 'GENERAL',
message,
file,
cause: error instanceof Error ? error : undefined,
} )
);
}
}
48 changes: 48 additions & 0 deletions packages/media-utils/src/utils/sideload-to-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* WordPress dependencies
*/
import apiFetch from '@wordpress/api-fetch';

/**
* Internal dependencies
*/
import type { CreateSideloadFile, RestAttachment } from './types';
import { flattenFormData } from './flatten-form-data';
import { transformAttachment } from './transform-attachment';

/**
* Uploads a file to the server without creating an attachment.
*
* @param file Media File to Save.
* @param attachmentId Parent attachment ID.
* @param additionalData Additional data to include in the request.
* @param signal Abort signal.
*
* @return The saved attachment.
*/
export async function sideloadToServer(
file: File,
attachmentId: RestAttachment[ 'id' ],
additionalData: CreateSideloadFile = {},
signal?: AbortSignal
) {
// Create upload payload.
const data = new FormData();
data.append( 'file', file, file.name || file.type.replace( '/', '.' ) );
for ( const [ key, value ] of Object.entries( additionalData ) ) {
flattenFormData(
data,
key,
value as string | Record< string, string > | undefined
);
}

return transformAttachment(
await apiFetch< RestAttachment >( {
path: `/wp/v2/media/${ attachmentId }/sideload`,
body: data,
method: 'POST',
signal,
} )
);
}
33 changes: 33 additions & 0 deletions packages/media-utils/src/utils/test/sideload-media.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Internal dependencies
*/
import { sideloadMedia } from '../sideload-media';
import { sideloadToServer } from '../sideload-to-server';

jest.mock( '../sideload-to-server', () => ( {
sideloadToServer: jest.fn(),
} ) );

const imageFile = new window.File( [ 'fake_file' ], 'test.jpeg', {
type: 'image/jpeg',
} );

describe( 'sideloadMedia', () => {
afterEach( () => {
jest.clearAllMocks();
} );

it( 'should sideload to server', async () => {
const onError = jest.fn();
const onFileChange = jest.fn();
await sideloadMedia( {
file: imageFile,
attachmentId: 1,
onError,
onFileChange,
} );

expect( sideloadToServer ).toHaveBeenCalled();
expect( onFileChange ).toHaveBeenCalled();
} );
} );
10 changes: 10 additions & 0 deletions packages/media-utils/src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,13 @@ export type OnErrorHandler = ( error: Error ) => void;
export type CreateRestAttachment = Partial< RestAttachment >;

export type AdditionalData = BetterOmit< CreateRestAttachment, 'meta' >;

export interface CreateSideloadFile {
image_size?: string;
upload_request?: string;
}

export interface SideloadAdditionalData {
post: RestAttachment[ 'id' ];
image_size?: string;
}
3 changes: 2 additions & 1 deletion packages/media-utils/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
{ "path": "../api-fetch" },
{ "path": "../blob" },
{ "path": "../element" },
{ "path": "../i18n" }
{ "path": "../i18n" },
{ "path": "../private-apis" }
]
}
1 change: 1 addition & 0 deletions packages/private-apis/src/implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const CORE_MODULES_USING_PRIVATE_APIS = [
'@wordpress/router',
'@wordpress/dataviews',
'@wordpress/fields',
'@wordpress/media-utils',
];

/**
Expand Down

0 comments on commit d969b0b

Please sign in to comment.