@wordpress/media-utils 5.41.1-next.v.202603102151.0 → 5.42.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 +2 -0
- package/build/components/media-upload-modal/index.cjs +136 -89
- 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 +129 -90
- 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 +131 -108
- 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.42.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.4.
|
|
54
|
-
"@wordpress/core-data": "^7.
|
|
55
|
-
"@wordpress/data": "^10.
|
|
56
|
-
"@wordpress/dataviews": "^13.1.
|
|
57
|
-
"@wordpress/element": "^6.
|
|
58
|
-
"@wordpress/i18n": "^6.
|
|
59
|
-
"@wordpress/icons": "^12.0.
|
|
60
|
-
"@wordpress/media-fields": "^0.
|
|
61
|
-
"@wordpress/notices": "^5.
|
|
62
|
-
"@wordpress/private-apis": "^1.
|
|
50
|
+
"@wordpress/api-fetch": "^7.42.0",
|
|
51
|
+
"@wordpress/base-styles": "^6.18.0",
|
|
52
|
+
"@wordpress/blob": "^4.42.0",
|
|
53
|
+
"@wordpress/components": "^32.4.0",
|
|
54
|
+
"@wordpress/core-data": "^7.42.0",
|
|
55
|
+
"@wordpress/data": "^10.42.0",
|
|
56
|
+
"@wordpress/dataviews": "^13.1.0",
|
|
57
|
+
"@wordpress/element": "^6.42.0",
|
|
58
|
+
"@wordpress/i18n": "^6.15.0",
|
|
59
|
+
"@wordpress/icons": "^12.0.0",
|
|
60
|
+
"@wordpress/media-fields": "^0.7.0",
|
|
61
|
+
"@wordpress/notices": "^5.42.0",
|
|
62
|
+
"@wordpress/private-apis": "^1.42.0",
|
|
63
|
+
"@wordpress/ui": "^0.9.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": "c20787b1778ae64c2db65643b1c236309d68e6ba"
|
|
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 >( () => ( {
|
|
@@ -186,10 +197,11 @@ export function MediaUploadModal( {
|
|
|
186
197
|
mediaField: 'media_thumbnail',
|
|
187
198
|
search: '',
|
|
188
199
|
page: 1,
|
|
189
|
-
perPage:
|
|
200
|
+
perPage: 50,
|
|
190
201
|
filters: [],
|
|
191
202
|
layout: {
|
|
192
203
|
previewSize: 170,
|
|
204
|
+
density: 'compact',
|
|
193
205
|
},
|
|
194
206
|
} ) );
|
|
195
207
|
|
|
@@ -243,6 +255,51 @@ export function MediaUploadModal( {
|
|
|
243
255
|
};
|
|
244
256
|
}, [ view, allowedTypes ] );
|
|
245
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
|
+
|
|
246
303
|
// Fetch all media attachments using WordPress core data with permissions
|
|
247
304
|
const {
|
|
248
305
|
records: mediaRecords,
|
|
@@ -287,7 +344,7 @@ export function MediaUploadModal( {
|
|
|
287
344
|
() => [
|
|
288
345
|
{
|
|
289
346
|
id: 'select',
|
|
290
|
-
label:
|
|
347
|
+
label: __( 'Select' ),
|
|
291
348
|
isPrimary: true,
|
|
292
349
|
supportsBulk: multiple,
|
|
293
350
|
async callback() {
|
|
@@ -317,42 +374,39 @@ export function MediaUploadModal( {
|
|
|
317
374
|
? transformedPosts
|
|
318
375
|
: transformedPosts?.[ 0 ];
|
|
319
376
|
|
|
377
|
+
removeAllNotices( 'snackbar', NOTICES_CONTEXT );
|
|
320
378
|
onSelect( selectedItems );
|
|
321
379
|
},
|
|
322
380
|
},
|
|
323
381
|
],
|
|
324
|
-
[ multiple, onSelect, selection ]
|
|
382
|
+
[ multiple, onSelect, selection, removeAllNotices ]
|
|
325
383
|
);
|
|
326
384
|
|
|
327
385
|
const handleModalClose = useCallback( () => {
|
|
386
|
+
removeAllNotices( 'snackbar', NOTICES_CONTEXT );
|
|
328
387
|
onClose?.();
|
|
329
|
-
}, [ onClose ] );
|
|
388
|
+
}, [ removeAllNotices, onClose ] );
|
|
330
389
|
|
|
331
390
|
// Use onUpload if provided, otherwise fall back to uploadMedia
|
|
332
391
|
const handleUpload = onUpload || uploadMedia;
|
|
333
392
|
|
|
334
|
-
//
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const
|
|
339
|
-
(
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
! isBlobURL( attachment.url )
|
|
343
|
-
);
|
|
344
|
-
|
|
345
|
-
if ( allComplete && attachments.length > 0 ) {
|
|
346
|
-
// 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 ) {
|
|
347
401
|
createSuccessNotice(
|
|
348
402
|
sprintf(
|
|
349
403
|
// translators: %s: number of files
|
|
350
404
|
_n(
|
|
351
405
|
'Uploaded %s file',
|
|
352
406
|
'Uploaded %s files',
|
|
353
|
-
|
|
407
|
+
completeCount
|
|
354
408
|
),
|
|
355
|
-
|
|
409
|
+
completeCount.toLocaleString()
|
|
356
410
|
),
|
|
357
411
|
{
|
|
358
412
|
type: 'snackbar',
|
|
@@ -360,84 +414,33 @@ export function MediaUploadModal( {
|
|
|
360
414
|
id: NOTICE_ID_UPLOAD_PROGRESS,
|
|
361
415
|
}
|
|
362
416
|
);
|
|
363
|
-
|
|
364
|
-
// Auto-select the newly uploaded items
|
|
365
|
-
const uploadedIds = attachments
|
|
366
|
-
.map( ( attachment ) => String( attachment.id ) )
|
|
367
|
-
.filter( Boolean );
|
|
368
|
-
|
|
369
|
-
if ( multiple ) {
|
|
370
|
-
// In multiple mode, add to existing selection
|
|
371
|
-
setSelection( ( prev ) => [ ...prev, ...uploadedIds ] );
|
|
372
|
-
} else {
|
|
373
|
-
// In single mode, replace selection with the first uploaded item
|
|
374
|
-
setSelection( uploadedIds.slice( 0, 1 ) );
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// Invalidate the entity records resolution to refresh the view
|
|
378
|
-
invalidateResolution( 'getEntityRecords', [
|
|
379
|
-
'postType',
|
|
380
|
-
'attachment',
|
|
381
|
-
queryArgs,
|
|
382
|
-
] );
|
|
383
417
|
}
|
|
384
|
-
},
|
|
385
|
-
[ createSuccessNotice, invalidateResolution, queryArgs, multiple ]
|
|
386
|
-
);
|
|
387
418
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
} );
|
|
397
|
-
},
|
|
398
|
-
[ createErrorNotice ]
|
|
399
|
-
);
|
|
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 ] );
|
|
400
427
|
|
|
401
428
|
const handleFileSelect = useCallback(
|
|
402
429
|
( event: React.ChangeEvent< HTMLInputElement > ) => {
|
|
403
430
|
const files = event.target.files;
|
|
404
431
|
if ( files && files.length > 0 ) {
|
|
405
432
|
const filesArray = Array.from( files );
|
|
406
|
-
|
|
407
|
-
// Show upload start notice
|
|
408
|
-
createInfoNotice(
|
|
409
|
-
sprintf(
|
|
410
|
-
// translators: %s: number of files
|
|
411
|
-
_n(
|
|
412
|
-
'Uploading %s file',
|
|
413
|
-
'Uploading %s files',
|
|
414
|
-
filesArray.length
|
|
415
|
-
),
|
|
416
|
-
filesArray.length.toLocaleString()
|
|
417
|
-
),
|
|
418
|
-
{
|
|
419
|
-
type: 'snackbar',
|
|
420
|
-
context: NOTICES_CONTEXT,
|
|
421
|
-
id: NOTICE_ID_UPLOAD_PROGRESS,
|
|
422
|
-
explicitDismiss: true,
|
|
423
|
-
}
|
|
424
|
-
);
|
|
433
|
+
const { onFileChange, onError } = registerBatch( filesArray );
|
|
425
434
|
|
|
426
435
|
handleUpload( {
|
|
427
436
|
allowedTypes,
|
|
428
437
|
filesList: filesArray,
|
|
429
|
-
onFileChange
|
|
430
|
-
onError
|
|
438
|
+
onFileChange,
|
|
439
|
+
onError,
|
|
431
440
|
} );
|
|
432
441
|
}
|
|
433
442
|
},
|
|
434
|
-
[
|
|
435
|
-
allowedTypes,
|
|
436
|
-
handleUpload,
|
|
437
|
-
createInfoNotice,
|
|
438
|
-
handleUploadComplete,
|
|
439
|
-
handleUploadError,
|
|
440
|
-
]
|
|
443
|
+
[ allowedTypes, handleUpload, registerBatch ]
|
|
441
444
|
);
|
|
442
445
|
|
|
443
446
|
const paginationInfo = useMemo(
|
|
@@ -524,30 +527,14 @@ export function MediaUploadModal( {
|
|
|
524
527
|
);
|
|
525
528
|
}
|
|
526
529
|
if ( filteredFiles.length > 0 ) {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
sprintf(
|
|
530
|
-
// translators: %s: number of files
|
|
531
|
-
_n(
|
|
532
|
-
'Uploading %s file',
|
|
533
|
-
'Uploading %s files',
|
|
534
|
-
filteredFiles.length
|
|
535
|
-
),
|
|
536
|
-
filteredFiles.length.toLocaleString()
|
|
537
|
-
),
|
|
538
|
-
{
|
|
539
|
-
type: 'snackbar',
|
|
540
|
-
context: NOTICES_CONTEXT,
|
|
541
|
-
id: NOTICE_ID_UPLOAD_PROGRESS,
|
|
542
|
-
explicitDismiss: true,
|
|
543
|
-
}
|
|
544
|
-
);
|
|
530
|
+
const { onFileChange, onError } =
|
|
531
|
+
registerBatch( filteredFiles );
|
|
545
532
|
|
|
546
533
|
handleUpload( {
|
|
547
534
|
allowedTypes,
|
|
548
535
|
filesList: filteredFiles,
|
|
549
|
-
onFileChange
|
|
550
|
-
onError
|
|
536
|
+
onFileChange,
|
|
537
|
+
onError,
|
|
551
538
|
} );
|
|
552
539
|
}
|
|
553
540
|
} }
|
|
@@ -565,10 +552,46 @@ export function MediaUploadModal( {
|
|
|
565
552
|
paginationInfo={ paginationInfo }
|
|
566
553
|
defaultLayouts={ defaultLayouts }
|
|
567
554
|
getItemId={ ( item: RestAttachment ) => String( item.id ) }
|
|
568
|
-
search={ search }
|
|
569
|
-
searchLabel={ searchLabel }
|
|
570
555
|
itemListLabel={ __( 'Media items' ) }
|
|
571
|
-
|
|
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>
|
|
572
595
|
{ createPortal(
|
|
573
596
|
<SnackbarNotices
|
|
574
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
|
+
}
|