@wordpress/media-utils 5.41.1-next.v.202603161435.0 → 5.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/build/components/media-upload-modal/index.cjs +133 -87
- package/build/components/media-upload-modal/index.cjs.map +3 -3
- package/build/components/media-upload-modal/upload-status-popover.cjs +156 -0
- package/build/components/media-upload-modal/upload-status-popover.cjs.map +7 -0
- package/build/components/media-upload-modal/use-invalidate-attachment-resolutions.cjs +45 -0
- package/build/components/media-upload-modal/use-invalidate-attachment-resolutions.cjs.map +7 -0
- package/build/components/media-upload-modal/use-upload-status.cjs +127 -0
- package/build/components/media-upload-modal/use-upload-status.cjs.map +7 -0
- package/build-module/components/media-upload-modal/index.mjs +126 -88
- package/build-module/components/media-upload-modal/index.mjs.map +2 -2
- package/build-module/components/media-upload-modal/upload-status-popover.mjs +131 -0
- package/build-module/components/media-upload-modal/upload-status-popover.mjs.map +7 -0
- package/build-module/components/media-upload-modal/use-invalidate-attachment-resolutions.mjs +20 -0
- package/build-module/components/media-upload-modal/use-invalidate-attachment-resolutions.mjs.map +7 -0
- package/build-module/components/media-upload-modal/use-upload-status.mjs +102 -0
- package/build-module/components/media-upload-modal/use-upload-status.mjs.map +7 -0
- package/build-style/style-rtl.css +73 -3
- package/build-style/style.css +73 -3
- package/build-types/components/media-upload-modal/index.d.ts.map +1 -1
- package/build-types/components/media-upload-modal/upload-status-popover.d.ts +15 -0
- package/build-types/components/media-upload-modal/upload-status-popover.d.ts.map +1 -0
- package/build-types/components/media-upload-modal/use-invalidate-attachment-resolutions.d.ts +22 -0
- package/build-types/components/media-upload-modal/use-invalidate-attachment-resolutions.d.ts.map +1 -0
- package/build-types/components/media-upload-modal/use-upload-status.d.ts +41 -0
- package/build-types/components/media-upload-modal/use-upload-status.d.ts.map +1 -0
- package/package.json +17 -15
- package/src/components/media-upload-modal/index.tsx +129 -107
- package/src/components/media-upload-modal/style.scss +88 -3
- package/src/components/media-upload-modal/test/use-upload-status.test.ts +501 -0
- package/src/components/media-upload-modal/upload-status-popover.tsx +155 -0
- package/src/components/media-upload-modal/use-invalidate-attachment-resolutions.ts +50 -0
- package/src/components/media-upload-modal/use-upload-status.ts +203 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface UploadingFile {
|
|
2
|
+
id: string;
|
|
3
|
+
batchId: string;
|
|
4
|
+
name: string;
|
|
5
|
+
status: 'uploading' | 'uploaded' | 'error';
|
|
6
|
+
error?: string;
|
|
7
|
+
}
|
|
8
|
+
interface UploadStatusPopoverProps {
|
|
9
|
+
uploadingFiles: UploadingFile[];
|
|
10
|
+
onDismissError?: (fileId: string) => void;
|
|
11
|
+
onOpenChange?: (open: boolean) => void;
|
|
12
|
+
}
|
|
13
|
+
export declare function UploadStatusPopover({ uploadingFiles, onDismissError, onOpenChange, }: UploadStatusPopoverProps): import("react").JSX.Element | null;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=upload-status-popover.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-status-popover.d.ts","sourceRoot":"","sources":["../../../src/components/media-upload-modal/upload-status-popover.tsx"],"names":[],"mappings":"AAQA,MAAM,WAAW,aAAa;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,GAAG,UAAU,GAAG,OAAO,CAAC;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,UAAU,wBAAwB;IACjC,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,cAAc,CAAC,EAAE,CAAE,MAAM,EAAE,MAAM,KAAM,IAAI,CAAC;IAC5C,YAAY,CAAC,EAAE,CAAE,IAAI,EAAE,OAAO,KAAM,IAAI,CAAC;CACzC;AAED,wBAAgB,mBAAmB,CAAE,EACpC,cAAc,EACd,cAAc,EACd,YAAY,GACZ,EAAE,wBAAwB,sCAgI1B"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook for invalidating all cached `getEntityRecords` resolutions for the
|
|
3
|
+
* attachment post type.
|
|
4
|
+
*
|
|
5
|
+
* After a file upload completes the media grid needs to refresh, but
|
|
6
|
+
* `invalidateResolution` only clears the exact query that is passed to it.
|
|
7
|
+
* If the user is on page 2, page 1 (where the new upload would appear) stays
|
|
8
|
+
* stale. Using `invalidateResolutionForStoreSelector` would work but is too
|
|
9
|
+
* broad — it clears every `getEntityRecords` resolution, potentially
|
|
10
|
+
* triggering unnecessary refetches for unrelated entity types.
|
|
11
|
+
*
|
|
12
|
+
* This hook provides a middle ground: it iterates over every cached
|
|
13
|
+
* resolution for `getEntityRecords` and invalidates only the entries where
|
|
14
|
+
* the first two arguments match `['postType', 'attachment']`.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Returns a stable callback that invalidates all cached `getEntityRecords`
|
|
18
|
+
* resolutions for `postType / attachment`, leaving every other entity type
|
|
19
|
+
* untouched.
|
|
20
|
+
*/
|
|
21
|
+
export declare function useInvalidateAttachmentResolutions(): () => void;
|
|
22
|
+
//# sourceMappingURL=use-invalidate-attachment-resolutions.d.ts.map
|
package/build-types/components/media-upload-modal/use-invalidate-attachment-resolutions.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-invalidate-attachment-resolutions.d.ts","sourceRoot":"","sources":["../../../src/components/media-upload-modal/use-invalidate-attachment-resolutions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AASH;;;;GAIG;AACH,wBAAgB,kCAAkC,eAqBjD"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook for tracking media upload status with batch-scoped callbacks.
|
|
3
|
+
*
|
|
4
|
+
* This is a transitional layer that manually tracks upload progress using
|
|
5
|
+
* local state. The @wordpress/upload-media package provides a Redux-based
|
|
6
|
+
* store with richer capabilities (per-file progress, pause/resume, retry,
|
|
7
|
+
* concurrency control, client-side processing). When the media upload modal
|
|
8
|
+
* adopts @wordpress/upload-media, this hook can be replaced by selectors
|
|
9
|
+
* from that store (getItems, isBatchUploaded, getItemProgress, etc.) while
|
|
10
|
+
* keeping the same return interface.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Internal dependencies
|
|
14
|
+
*/
|
|
15
|
+
import type { Attachment } from '../../utils/types';
|
|
16
|
+
import type { UploadingFile } from './upload-status-popover';
|
|
17
|
+
interface UseUploadStatusOptions {
|
|
18
|
+
onBatchComplete?: (attachments: Partial<Attachment>[]) => void;
|
|
19
|
+
}
|
|
20
|
+
interface RegisterBatchResult {
|
|
21
|
+
onFileChange: (attachments: Partial<Attachment>[]) => void;
|
|
22
|
+
onError: (error: Error) => void;
|
|
23
|
+
}
|
|
24
|
+
interface UseUploadStatusReturn {
|
|
25
|
+
/** Current list of all tracked files. */
|
|
26
|
+
uploadingFiles: UploadingFile[];
|
|
27
|
+
/**
|
|
28
|
+
* Register a new batch of files for tracking.
|
|
29
|
+
* Returns batch-scoped onFileChange and onError callbacks.
|
|
30
|
+
*/
|
|
31
|
+
registerBatch: (files: File[]) => RegisterBatchResult;
|
|
32
|
+
/** Remove a single error entry by file id. */
|
|
33
|
+
dismissError: (fileId: string) => void;
|
|
34
|
+
/** Remove all uploaded (completed) entries from the list. */
|
|
35
|
+
clearCompleted: () => void;
|
|
36
|
+
/** True when tracked entries exist but none are still uploading. */
|
|
37
|
+
allComplete: boolean;
|
|
38
|
+
}
|
|
39
|
+
export declare function useUploadStatus({ onBatchComplete, }?: UseUploadStatusOptions): UseUploadStatusReturn;
|
|
40
|
+
export {};
|
|
41
|
+
//# sourceMappingURL=use-upload-status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-upload-status.d.ts","sourceRoot":"","sources":["../../../src/components/media-upload-modal/use-upload-status.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH;;GAEG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAK7D,UAAU,sBAAsB;IAC/B,eAAe,CAAC,EAAE,CAAE,WAAW,EAAE,OAAO,CAAE,UAAU,CAAE,EAAE,KAAM,IAAI,CAAC;CACnE;AAED,UAAU,mBAAmB;IAC5B,YAAY,EAAE,CAAE,WAAW,EAAE,OAAO,CAAE,UAAU,CAAE,EAAE,KAAM,IAAI,CAAC;IAC/D,OAAO,EAAE,CAAE,KAAK,EAAE,KAAK,KAAM,IAAI,CAAC;CAClC;AAED,UAAU,qBAAqB;IAC9B,yCAAyC;IACzC,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC;;;OAGG;IACH,aAAa,EAAE,CAAE,KAAK,EAAE,IAAI,EAAE,KAAM,mBAAmB,CAAC;IACxD,8CAA8C;IAC9C,YAAY,EAAE,CAAE,MAAM,EAAE,MAAM,KAAM,IAAI,CAAC;IACzC,6DAA6D;IAC7D,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,oEAAoE;IACpE,WAAW,EAAE,OAAO,CAAC;CACrB;AAED,wBAAgB,eAAe,CAAE,EAChC,eAAe,GACf,GAAE,sBAA2B,GAAI,qBAAqB,CAmJtD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wordpress/media-utils",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.43.0",
|
|
4
4
|
"description": "WordPress Media Upload Utils.",
|
|
5
5
|
"author": "The WordPress Contributors",
|
|
6
6
|
"license": "GPL-2.0-or-later",
|
|
@@ -47,19 +47,21 @@
|
|
|
47
47
|
"build-style/**"
|
|
48
48
|
],
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@wordpress/api-fetch": "^7.
|
|
51
|
-
"@wordpress/base-styles": "^6.
|
|
52
|
-
"@wordpress/blob": "^4.
|
|
53
|
-
"@wordpress/components": "^32.
|
|
54
|
-
"@wordpress/core-data": "^7.
|
|
55
|
-
"@wordpress/data": "^10.
|
|
56
|
-
"@wordpress/dataviews": "^
|
|
57
|
-
"@wordpress/element": "^6.
|
|
58
|
-
"@wordpress/i18n": "^6.
|
|
59
|
-
"@wordpress/icons": "^12.
|
|
60
|
-
"@wordpress/media-fields": "^0.
|
|
61
|
-
"@wordpress/notices": "^5.
|
|
62
|
-
"@wordpress/private-apis": "^1.
|
|
50
|
+
"@wordpress/api-fetch": "^7.43.0",
|
|
51
|
+
"@wordpress/base-styles": "^6.19.0",
|
|
52
|
+
"@wordpress/blob": "^4.43.0",
|
|
53
|
+
"@wordpress/components": "^32.5.0",
|
|
54
|
+
"@wordpress/core-data": "^7.43.0",
|
|
55
|
+
"@wordpress/data": "^10.43.0",
|
|
56
|
+
"@wordpress/dataviews": "^14.0.0",
|
|
57
|
+
"@wordpress/element": "^6.43.0",
|
|
58
|
+
"@wordpress/i18n": "^6.16.0",
|
|
59
|
+
"@wordpress/icons": "^12.1.0",
|
|
60
|
+
"@wordpress/media-fields": "^0.8.0",
|
|
61
|
+
"@wordpress/notices": "^5.43.0",
|
|
62
|
+
"@wordpress/private-apis": "^1.43.0",
|
|
63
|
+
"@wordpress/ui": "^0.10.0",
|
|
64
|
+
"clsx": "^2.1.1"
|
|
63
65
|
},
|
|
64
66
|
"peerDependencies": {
|
|
65
67
|
"react": "^18.0.0"
|
|
@@ -67,5 +69,5 @@
|
|
|
67
69
|
"publishConfig": {
|
|
68
70
|
"access": "public"
|
|
69
71
|
},
|
|
70
|
-
"gitHead": "
|
|
72
|
+
"gitHead": "2cea90674d11aa521ec3f71652fb3a6a4c383969"
|
|
71
73
|
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import clsx from 'clsx';
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
7
|
* WordPress dependencies
|
|
3
8
|
*/
|
|
@@ -6,6 +11,8 @@ import {
|
|
|
6
11
|
useState,
|
|
7
12
|
useCallback,
|
|
8
13
|
useMemo,
|
|
14
|
+
useRef,
|
|
15
|
+
useEffect,
|
|
9
16
|
} from '@wordpress/element';
|
|
10
17
|
import { __, sprintf, _n } from '@wordpress/i18n';
|
|
11
18
|
import {
|
|
@@ -17,6 +24,7 @@ import { Modal, DropZone, FormFileUpload, Button } from '@wordpress/components';
|
|
|
17
24
|
import { upload as uploadIcon } from '@wordpress/icons';
|
|
18
25
|
import { DataViewsPicker } from '@wordpress/dataviews';
|
|
19
26
|
import type { View, Field, ActionButton } from '@wordpress/dataviews';
|
|
27
|
+
import { Stack } from '@wordpress/ui';
|
|
20
28
|
import {
|
|
21
29
|
altTextField,
|
|
22
30
|
attachedToField,
|
|
@@ -32,7 +40,6 @@ import {
|
|
|
32
40
|
mimeTypeField,
|
|
33
41
|
} from '@wordpress/media-fields';
|
|
34
42
|
import { store as noticesStore, SnackbarNotices } from '@wordpress/notices';
|
|
35
|
-
import { isBlobURL } from '@wordpress/blob';
|
|
36
43
|
|
|
37
44
|
/**
|
|
38
45
|
* Internal dependencies
|
|
@@ -41,6 +48,9 @@ import type { Attachment, RestAttachment } from '../../utils/types';
|
|
|
41
48
|
import { transformAttachment } from '../../utils/transform-attachment';
|
|
42
49
|
import { uploadMedia } from '../../utils/upload-media';
|
|
43
50
|
import { unlock } from '../../lock-unlock';
|
|
51
|
+
import { UploadStatusPopover } from './upload-status-popover';
|
|
52
|
+
import { useInvalidateAttachmentResolutions } from './use-invalidate-attachment-resolutions';
|
|
53
|
+
import { useUploadStatus } from './use-upload-status';
|
|
44
54
|
|
|
45
55
|
const { useEntityRecordsWithPermissions } = unlock( coreDataPrivateApis );
|
|
46
56
|
|
|
@@ -173,9 +183,10 @@ export function MediaUploadModal( {
|
|
|
173
183
|
: [ String( value ) ];
|
|
174
184
|
} );
|
|
175
185
|
|
|
176
|
-
const { createSuccessNotice,
|
|
186
|
+
const { createSuccessNotice, removeAllNotices } =
|
|
177
187
|
useDispatch( noticesStore );
|
|
178
|
-
const
|
|
188
|
+
const invalidateAttachmentResolutions =
|
|
189
|
+
useInvalidateAttachmentResolutions();
|
|
179
190
|
|
|
180
191
|
// DataViews configuration - allow view updates
|
|
181
192
|
const [ view, setView ] = useState< View >( () => ( {
|
|
@@ -244,6 +255,51 @@ export function MediaUploadModal( {
|
|
|
244
255
|
};
|
|
245
256
|
}, [ view, allowedTypes ] );
|
|
246
257
|
|
|
258
|
+
// Per-batch completion handler: auto-select uploaded items and refresh the grid.
|
|
259
|
+
const handleBatchComplete = useCallback(
|
|
260
|
+
( attachments: Partial< Attachment >[] ) => {
|
|
261
|
+
const uploadedIds = attachments
|
|
262
|
+
.map( ( attachment ) => String( attachment.id ) )
|
|
263
|
+
.filter( Boolean );
|
|
264
|
+
|
|
265
|
+
if ( multiple ) {
|
|
266
|
+
setSelection( ( prev ) => {
|
|
267
|
+
const existing = new Set( prev );
|
|
268
|
+
const newIds = uploadedIds.filter(
|
|
269
|
+
( id ) => ! existing.has( id )
|
|
270
|
+
);
|
|
271
|
+
return [ ...prev, ...newIds ];
|
|
272
|
+
} );
|
|
273
|
+
} else {
|
|
274
|
+
setSelection( uploadedIds.slice( 0, 1 ) );
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Invalidate all cached attachment queries so every page of
|
|
278
|
+
// results refreshes — not just the page the user is viewing.
|
|
279
|
+
invalidateAttachmentResolutions();
|
|
280
|
+
},
|
|
281
|
+
[ multiple, invalidateAttachmentResolutions ]
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
const {
|
|
285
|
+
uploadingFiles,
|
|
286
|
+
registerBatch,
|
|
287
|
+
dismissError,
|
|
288
|
+
clearCompleted,
|
|
289
|
+
allComplete,
|
|
290
|
+
} = useUploadStatus( { onBatchComplete: handleBatchComplete } );
|
|
291
|
+
|
|
292
|
+
const isPopoverOpenRef = useRef( false );
|
|
293
|
+
const handlePopoverOpenChange = useCallback(
|
|
294
|
+
( open: boolean ) => {
|
|
295
|
+
isPopoverOpenRef.current = open;
|
|
296
|
+
if ( ! open ) {
|
|
297
|
+
clearCompleted();
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
[ clearCompleted ]
|
|
301
|
+
);
|
|
302
|
+
|
|
247
303
|
// Fetch all media attachments using WordPress core data with permissions
|
|
248
304
|
const {
|
|
249
305
|
records: mediaRecords,
|
|
@@ -288,7 +344,7 @@ export function MediaUploadModal( {
|
|
|
288
344
|
() => [
|
|
289
345
|
{
|
|
290
346
|
id: 'select',
|
|
291
|
-
label:
|
|
347
|
+
label: __( 'Select' ),
|
|
292
348
|
isPrimary: true,
|
|
293
349
|
supportsBulk: multiple,
|
|
294
350
|
async callback() {
|
|
@@ -318,42 +374,39 @@ export function MediaUploadModal( {
|
|
|
318
374
|
? transformedPosts
|
|
319
375
|
: transformedPosts?.[ 0 ];
|
|
320
376
|
|
|
377
|
+
removeAllNotices( 'snackbar', NOTICES_CONTEXT );
|
|
321
378
|
onSelect( selectedItems );
|
|
322
379
|
},
|
|
323
380
|
},
|
|
324
381
|
],
|
|
325
|
-
[ multiple, onSelect, selection ]
|
|
382
|
+
[ multiple, onSelect, selection, removeAllNotices ]
|
|
326
383
|
);
|
|
327
384
|
|
|
328
385
|
const handleModalClose = useCallback( () => {
|
|
386
|
+
removeAllNotices( 'snackbar', NOTICES_CONTEXT );
|
|
329
387
|
onClose?.();
|
|
330
|
-
}, [ onClose ] );
|
|
388
|
+
}, [ removeAllNotices, onClose ] );
|
|
331
389
|
|
|
332
390
|
// Use onUpload if provided, otherwise fall back to uploadMedia
|
|
333
391
|
const handleUpload = onUpload || uploadMedia;
|
|
334
392
|
|
|
335
|
-
//
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
const
|
|
340
|
-
(
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
! isBlobURL( attachment.url )
|
|
344
|
-
);
|
|
345
|
-
|
|
346
|
-
if ( allComplete && attachments.length > 0 ) {
|
|
347
|
-
// Show success notice (replaces progress notice via ID)
|
|
393
|
+
// Show success notice and auto-clear completed entries when all batches finish.
|
|
394
|
+
const prevAllCompleteRef = useRef( false );
|
|
395
|
+
useEffect( () => {
|
|
396
|
+
if ( allComplete && ! prevAllCompleteRef.current ) {
|
|
397
|
+
const completeCount = uploadingFiles.filter(
|
|
398
|
+
( file ) => file.status === 'uploaded'
|
|
399
|
+
).length;
|
|
400
|
+
if ( completeCount > 0 ) {
|
|
348
401
|
createSuccessNotice(
|
|
349
402
|
sprintf(
|
|
350
403
|
// translators: %s: number of files
|
|
351
404
|
_n(
|
|
352
405
|
'Uploaded %s file',
|
|
353
406
|
'Uploaded %s files',
|
|
354
|
-
|
|
407
|
+
completeCount
|
|
355
408
|
),
|
|
356
|
-
|
|
409
|
+
completeCount.toLocaleString()
|
|
357
410
|
),
|
|
358
411
|
{
|
|
359
412
|
type: 'snackbar',
|
|
@@ -361,84 +414,33 @@ export function MediaUploadModal( {
|
|
|
361
414
|
id: NOTICE_ID_UPLOAD_PROGRESS,
|
|
362
415
|
}
|
|
363
416
|
);
|
|
364
|
-
|
|
365
|
-
// Auto-select the newly uploaded items
|
|
366
|
-
const uploadedIds = attachments
|
|
367
|
-
.map( ( attachment ) => String( attachment.id ) )
|
|
368
|
-
.filter( Boolean );
|
|
369
|
-
|
|
370
|
-
if ( multiple ) {
|
|
371
|
-
// In multiple mode, add to existing selection
|
|
372
|
-
setSelection( ( prev ) => [ ...prev, ...uploadedIds ] );
|
|
373
|
-
} else {
|
|
374
|
-
// In single mode, replace selection with the first uploaded item
|
|
375
|
-
setSelection( uploadedIds.slice( 0, 1 ) );
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// Invalidate the entity records resolution to refresh the view
|
|
379
|
-
invalidateResolution( 'getEntityRecords', [
|
|
380
|
-
'postType',
|
|
381
|
-
'attachment',
|
|
382
|
-
queryArgs,
|
|
383
|
-
] );
|
|
384
417
|
}
|
|
385
|
-
},
|
|
386
|
-
[ createSuccessNotice, invalidateResolution, queryArgs, multiple ]
|
|
387
|
-
);
|
|
388
418
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
} );
|
|
398
|
-
},
|
|
399
|
-
[ createErrorNotice ]
|
|
400
|
-
);
|
|
419
|
+
// Auto-clear completed entries, unless the popover is
|
|
420
|
+
// open — in that case, they'll be cleared on close.
|
|
421
|
+
if ( ! isPopoverOpenRef.current ) {
|
|
422
|
+
clearCompleted();
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
prevAllCompleteRef.current = allComplete;
|
|
426
|
+
}, [ allComplete, uploadingFiles, createSuccessNotice, clearCompleted ] );
|
|
401
427
|
|
|
402
428
|
const handleFileSelect = useCallback(
|
|
403
429
|
( event: React.ChangeEvent< HTMLInputElement > ) => {
|
|
404
430
|
const files = event.target.files;
|
|
405
431
|
if ( files && files.length > 0 ) {
|
|
406
432
|
const filesArray = Array.from( files );
|
|
407
|
-
|
|
408
|
-
// Show upload start notice
|
|
409
|
-
createInfoNotice(
|
|
410
|
-
sprintf(
|
|
411
|
-
// translators: %s: number of files
|
|
412
|
-
_n(
|
|
413
|
-
'Uploading %s file',
|
|
414
|
-
'Uploading %s files',
|
|
415
|
-
filesArray.length
|
|
416
|
-
),
|
|
417
|
-
filesArray.length.toLocaleString()
|
|
418
|
-
),
|
|
419
|
-
{
|
|
420
|
-
type: 'snackbar',
|
|
421
|
-
context: NOTICES_CONTEXT,
|
|
422
|
-
id: NOTICE_ID_UPLOAD_PROGRESS,
|
|
423
|
-
explicitDismiss: true,
|
|
424
|
-
}
|
|
425
|
-
);
|
|
433
|
+
const { onFileChange, onError } = registerBatch( filesArray );
|
|
426
434
|
|
|
427
435
|
handleUpload( {
|
|
428
436
|
allowedTypes,
|
|
429
437
|
filesList: filesArray,
|
|
430
|
-
onFileChange
|
|
431
|
-
onError
|
|
438
|
+
onFileChange,
|
|
439
|
+
onError,
|
|
432
440
|
} );
|
|
433
441
|
}
|
|
434
442
|
},
|
|
435
|
-
[
|
|
436
|
-
allowedTypes,
|
|
437
|
-
handleUpload,
|
|
438
|
-
createInfoNotice,
|
|
439
|
-
handleUploadComplete,
|
|
440
|
-
handleUploadError,
|
|
441
|
-
]
|
|
443
|
+
[ allowedTypes, handleUpload, registerBatch ]
|
|
442
444
|
);
|
|
443
445
|
|
|
444
446
|
const paginationInfo = useMemo(
|
|
@@ -525,30 +527,14 @@ export function MediaUploadModal( {
|
|
|
525
527
|
);
|
|
526
528
|
}
|
|
527
529
|
if ( filteredFiles.length > 0 ) {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
sprintf(
|
|
531
|
-
// translators: %s: number of files
|
|
532
|
-
_n(
|
|
533
|
-
'Uploading %s file',
|
|
534
|
-
'Uploading %s files',
|
|
535
|
-
filteredFiles.length
|
|
536
|
-
),
|
|
537
|
-
filteredFiles.length.toLocaleString()
|
|
538
|
-
),
|
|
539
|
-
{
|
|
540
|
-
type: 'snackbar',
|
|
541
|
-
context: NOTICES_CONTEXT,
|
|
542
|
-
id: NOTICE_ID_UPLOAD_PROGRESS,
|
|
543
|
-
explicitDismiss: true,
|
|
544
|
-
}
|
|
545
|
-
);
|
|
530
|
+
const { onFileChange, onError } =
|
|
531
|
+
registerBatch( filteredFiles );
|
|
546
532
|
|
|
547
533
|
handleUpload( {
|
|
548
534
|
allowedTypes,
|
|
549
535
|
filesList: filteredFiles,
|
|
550
|
-
onFileChange
|
|
551
|
-
onError
|
|
536
|
+
onFileChange,
|
|
537
|
+
onError,
|
|
552
538
|
} );
|
|
553
539
|
}
|
|
554
540
|
} }
|
|
@@ -566,10 +552,46 @@ export function MediaUploadModal( {
|
|
|
566
552
|
paginationInfo={ paginationInfo }
|
|
567
553
|
defaultLayouts={ defaultLayouts }
|
|
568
554
|
getItemId={ ( item: RestAttachment ) => String( item.id ) }
|
|
569
|
-
search={ search }
|
|
570
|
-
searchLabel={ searchLabel }
|
|
571
555
|
itemListLabel={ __( 'Media items' ) }
|
|
572
|
-
|
|
556
|
+
>
|
|
557
|
+
<Stack
|
|
558
|
+
direction="row"
|
|
559
|
+
align="top"
|
|
560
|
+
justify="space-between"
|
|
561
|
+
className="dataviews__view-actions"
|
|
562
|
+
gap="xs"
|
|
563
|
+
>
|
|
564
|
+
<Stack
|
|
565
|
+
direction="row"
|
|
566
|
+
gap="sm"
|
|
567
|
+
justify="start"
|
|
568
|
+
className="dataviews__search"
|
|
569
|
+
>
|
|
570
|
+
{ search && (
|
|
571
|
+
<DataViewsPicker.Search label={ searchLabel } />
|
|
572
|
+
) }
|
|
573
|
+
<DataViewsPicker.FiltersToggle />
|
|
574
|
+
</Stack>
|
|
575
|
+
<Stack direction="row" gap="xs" style={ { flexShrink: 0 } }>
|
|
576
|
+
<DataViewsPicker.LayoutSwitcher />
|
|
577
|
+
<DataViewsPicker.ViewConfig />
|
|
578
|
+
</Stack>
|
|
579
|
+
</Stack>
|
|
580
|
+
<DataViewsPicker.FiltersToggled className="dataviews-filters__container" />
|
|
581
|
+
<DataViewsPicker.Layout />
|
|
582
|
+
<div
|
|
583
|
+
className={ clsx( 'media-upload-modal__footer', {
|
|
584
|
+
'is-uploading': uploadingFiles.length > 0,
|
|
585
|
+
} ) }
|
|
586
|
+
>
|
|
587
|
+
<UploadStatusPopover
|
|
588
|
+
uploadingFiles={ uploadingFiles }
|
|
589
|
+
onDismissError={ dismissError }
|
|
590
|
+
onOpenChange={ handlePopoverOpenChange }
|
|
591
|
+
/>
|
|
592
|
+
<DataViewsPicker.BulkActionToolbar />
|
|
593
|
+
</div>
|
|
594
|
+
</DataViewsPicker>
|
|
573
595
|
{ createPortal(
|
|
574
596
|
<SnackbarNotices
|
|
575
597
|
className="media-upload-modal__snackbar"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
@use "@wordpress/base-styles/variables" as *;
|
|
2
|
+
@use "@wordpress/base-styles/colors" as *;
|
|
2
3
|
@use "@wordpress/base-styles/mixins" as *;
|
|
3
4
|
|
|
4
5
|
.media-upload-modal {
|
|
@@ -7,7 +8,7 @@
|
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
.components-modal__frame.is-full-screen .components-modal__content {
|
|
10
|
-
margin-bottom:
|
|
11
|
+
margin-bottom: 0;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
.components-modal__content {
|
|
@@ -18,8 +19,41 @@
|
|
|
18
19
|
padding-top: $grid-unit-10;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
.
|
|
22
|
-
|
|
22
|
+
.media-upload-modal__footer {
|
|
23
|
+
position: sticky;
|
|
24
|
+
bottom: 0;
|
|
25
|
+
background-color: inherit;
|
|
26
|
+
// Match the z-index the footer normally has, since we take over sticky.
|
|
27
|
+
z-index: 2;
|
|
28
|
+
|
|
29
|
+
&.is-uploading .dataviews-picker-footer__bulk-selection {
|
|
30
|
+
visibility: hidden;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// The inner footer no longer needs sticky/z-index since the wrapper handles it.
|
|
34
|
+
.dataviews-footer {
|
|
35
|
+
position: static;
|
|
36
|
+
z-index: auto;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.media-upload-modal__upload-status {
|
|
41
|
+
position: absolute;
|
|
42
|
+
display: flex;
|
|
43
|
+
align-items: center;
|
|
44
|
+
gap: $grid-unit-10;
|
|
45
|
+
background-color: var(--wp-dataviews-color-background, $white);
|
|
46
|
+
// Match the footer's padding so the trigger aligns with the item count.
|
|
47
|
+
left: $grid-unit-30;
|
|
48
|
+
top: 1px;
|
|
49
|
+
bottom: 1px;
|
|
50
|
+
z-index: 1;
|
|
51
|
+
|
|
52
|
+
.components-spinner {
|
|
53
|
+
width: $grid-unit-20;
|
|
54
|
+
height: $grid-unit-20;
|
|
55
|
+
margin: 0;
|
|
56
|
+
}
|
|
23
57
|
}
|
|
24
58
|
}
|
|
25
59
|
|
|
@@ -38,3 +72,54 @@
|
|
|
38
72
|
transform-origin: 0 !important;
|
|
39
73
|
}
|
|
40
74
|
}
|
|
75
|
+
|
|
76
|
+
.media-upload-modal__upload-status__popover {
|
|
77
|
+
.components-popover__content {
|
|
78
|
+
width: 320px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.media-upload-modal__upload-status__header {
|
|
82
|
+
display: flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
justify-content: space-between;
|
|
85
|
+
padding: $grid-unit-15 $grid-unit-20;
|
|
86
|
+
h3 {
|
|
87
|
+
margin: 0;
|
|
88
|
+
font-size: 13px;
|
|
89
|
+
font-weight: $font-weight-medium;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.media-upload-modal__upload-status__list {
|
|
94
|
+
max-height: 200px;
|
|
95
|
+
overflow-y: auto;
|
|
96
|
+
margin: 0;
|
|
97
|
+
padding: 0 0 $grid-unit-05;
|
|
98
|
+
list-style: none;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.media-upload-modal__upload-status__item {
|
|
102
|
+
display: flex;
|
|
103
|
+
align-items: flex-start;
|
|
104
|
+
margin-bottom: 0;
|
|
105
|
+
justify-content: space-between;
|
|
106
|
+
gap: $grid-unit-10;
|
|
107
|
+
padding: $grid-unit-10 $grid-unit-20;
|
|
108
|
+
|
|
109
|
+
.components-spinner {
|
|
110
|
+
flex-shrink: 0;
|
|
111
|
+
width: $grid-unit-20;
|
|
112
|
+
height: $grid-unit-20;
|
|
113
|
+
margin: 0;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.media-upload-modal__upload-status__filename {
|
|
118
|
+
overflow: hidden;
|
|
119
|
+
text-overflow: ellipsis;
|
|
120
|
+
white-space: nowrap;
|
|
121
|
+
min-width: 0;
|
|
122
|
+
flex: 1;
|
|
123
|
+
font-size: 12px;
|
|
124
|
+
}
|
|
125
|
+
}
|