@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,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/components/media-upload-modal/upload-status-popover.tsx"],
|
|
4
|
+
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { useState, useEffect, useCallback, useRef } from '@wordpress/element';\nimport { __, sprintf, _n } from '@wordpress/i18n';\nimport { Button, Icon, Notice, Popover, Spinner } from '@wordpress/components';\nimport { check, chevronDown } from '@wordpress/icons';\n\nexport interface UploadingFile {\n\tid: string;\n\tbatchId: string;\n\tname: string;\n\tstatus: 'uploading' | 'uploaded' | 'error';\n\terror?: string;\n}\n\ninterface UploadStatusPopoverProps {\n\tuploadingFiles: UploadingFile[];\n\tonDismissError?: ( fileId: string ) => void;\n\tonOpenChange?: ( open: boolean ) => void;\n}\n\nexport function UploadStatusPopover( {\n\tuploadingFiles,\n\tonDismissError,\n\tonOpenChange,\n}: UploadStatusPopoverProps ) {\n\tconst [ isOpen, setIsOpen ] = useState( false );\n\tconst [ prevHadErrors, setPrevHadErrors ] = useState( false );\n\tconst triggerRef = useRef< HTMLButtonElement >( null );\n\n\tconst updateIsOpen = useCallback(\n\t\t( open: boolean ) => {\n\t\t\tsetIsOpen( open );\n\t\t\tonOpenChange?.( open );\n\t\t},\n\t\t[ onOpenChange ]\n\t);\n\n\tconst activeFiles = uploadingFiles.filter(\n\t\t( file ) => file.status === 'uploading'\n\t);\n\tconst errorFiles = uploadingFiles.filter(\n\t\t( file ) => file.status === 'error'\n\t);\n\tconst hasErrors = errorFiles.length > 0;\n\tconst isUploading = activeFiles.length > 0;\n\n\t// Auto-expand when an error occurs.\n\tuseEffect( () => {\n\t\tif ( hasErrors && ! prevHadErrors ) {\n\t\t\tupdateIsOpen( true );\n\t\t}\n\t\tsetPrevHadErrors( hasErrors );\n\t}, [ hasErrors, prevHadErrors, updateIsOpen ] );\n\n\tif ( uploadingFiles.length === 0 ) {\n\t\treturn null;\n\t}\n\n\tlet buttonLabel, popoverHeading: string;\n\tif ( isUploading ) {\n\t\tbuttonLabel = sprintf(\n\t\t\t// translators: %s: number of files being uploaded\n\t\t\t_n( 'Uploading %s file', 'Uploading %s files', activeFiles.length ),\n\t\t\tactiveFiles.length.toLocaleString()\n\t\t);\n\t\tpopoverHeading = __( 'Uploading' );\n\t} else if ( hasErrors ) {\n\t\tbuttonLabel = sprintf(\n\t\t\t// translators: %s: number of upload errors\n\t\t\t_n( '%s upload error', '%s upload errors', errorFiles.length ),\n\t\t\terrorFiles.length.toLocaleString()\n\t\t);\n\t\tpopoverHeading = __( 'Upload errors' );\n\t} else {\n\t\tbuttonLabel = __( 'Upload complete' );\n\t\tpopoverHeading = __( 'Upload complete' );\n\t}\n\n\treturn (\n\t\t<div className=\"media-upload-modal__upload-status\">\n\t\t\t{ isUploading && <Spinner /> }\n\t\t\t<Button\n\t\t\t\tclassName=\"media-upload-modal__upload-status__trigger\"\n\t\t\t\tsize=\"compact\"\n\t\t\t\ticon={ chevronDown }\n\t\t\t\ticonPosition=\"right\"\n\t\t\t\tonClick={ () => updateIsOpen( ! isOpen ) }\n\t\t\t\taria-expanded={ isOpen }\n\t\t\t\tref={ triggerRef }\n\t\t\t>\n\t\t\t\t{ buttonLabel }\n\t\t\t</Button>\n\t\t\t{ isOpen && (\n\t\t\t\t<Popover\n\t\t\t\t\tclassName=\"media-upload-modal__upload-status__popover\"\n\t\t\t\t\tplacement=\"top-start\"\n\t\t\t\t\toffset={ 8 }\n\t\t\t\t\tanchor={ triggerRef.current }\n\t\t\t\t\tfocusOnMount\n\t\t\t\t\tonClose={ () => {\n\t\t\t\t\t\t// Let the button's onClick handle toggling when\n\t\t\t\t\t\t// the close was triggered by clicking the trigger.\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\ttriggerRef.current?.contains(\n\t\t\t\t\t\t\t\ttriggerRef.current.ownerDocument.activeElement\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tupdateIsOpen( false );\n\t\t\t\t\t} }\n\t\t\t\t>\n\t\t\t\t\t<div className=\"media-upload-modal__upload-status__header\">\n\t\t\t\t\t\t<h3>{ popoverHeading }</h3>\n\t\t\t\t\t</div>\n\t\t\t\t\t<ul className=\"media-upload-modal__upload-status__list\">\n\t\t\t\t\t\t{ uploadingFiles.map( ( file ) => (\n\t\t\t\t\t\t\t<li\n\t\t\t\t\t\t\t\tkey={ file.id }\n\t\t\t\t\t\t\t\tclassName=\"media-upload-modal__upload-status__item\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{ file.status === 'uploading' && <Spinner /> }\n\t\t\t\t\t\t\t\t{ file.status === 'uploaded' && (\n\t\t\t\t\t\t\t\t\t<Icon icon={ check } size={ 16 } />\n\t\t\t\t\t\t\t\t) }\n\t\t\t\t\t\t\t\t{ ( file.status === 'uploading' ||\n\t\t\t\t\t\t\t\t\tfile.status === 'uploaded' ) && (\n\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\tclassName=\"media-upload-modal__upload-status__filename\"\n\t\t\t\t\t\t\t\t\t\ttitle={ file.name }\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{ file.name }\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t) }\n\t\t\t\t\t\t\t\t{ file.status === 'error' && (\n\t\t\t\t\t\t\t\t\t<Notice\n\t\t\t\t\t\t\t\t\t\tstatus=\"error\"\n\t\t\t\t\t\t\t\t\t\tisDismissible={ !! onDismissError }\n\t\t\t\t\t\t\t\t\t\tonRemove={ () =>\n\t\t\t\t\t\t\t\t\t\t\tonDismissError?.( file.id )\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t{ file.name }: { file.error }\n\t\t\t\t\t\t\t\t\t</Notice>\n\t\t\t\t\t\t\t\t) }\n\t\t\t\t\t\t\t</li>\n\t\t\t\t\t\t) ) }\n\t\t\t\t\t</ul>\n\t\t\t\t</Popover>\n\t\t\t) }\n\t\t</div>\n\t);\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,qBAAyD;AACzD,kBAAgC;AAChC,wBAAuD;AACvD,mBAAmC;AA4Ef;AA5Db,SAAS,oBAAqB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AACD,GAA8B;AAC7B,QAAM,CAAE,QAAQ,SAAU,QAAI,yBAAU,KAAM;AAC9C,QAAM,CAAE,eAAe,gBAAiB,QAAI,yBAAU,KAAM;AAC5D,QAAM,iBAAa,uBAA6B,IAAK;AAErD,QAAM,mBAAe;AAAA,IACpB,CAAE,SAAmB;AACpB,gBAAW,IAAK;AAChB,qBAAgB,IAAK;AAAA,IACtB;AAAA,IACA,CAAE,YAAa;AAAA,EAChB;AAEA,QAAM,cAAc,eAAe;AAAA,IAClC,CAAE,SAAU,KAAK,WAAW;AAAA,EAC7B;AACA,QAAM,aAAa,eAAe;AAAA,IACjC,CAAE,SAAU,KAAK,WAAW;AAAA,EAC7B;AACA,QAAM,YAAY,WAAW,SAAS;AACtC,QAAM,cAAc,YAAY,SAAS;AAGzC,gCAAW,MAAM;AAChB,QAAK,aAAa,CAAE,eAAgB;AACnC,mBAAc,IAAK;AAAA,IACpB;AACA,qBAAkB,SAAU;AAAA,EAC7B,GAAG,CAAE,WAAW,eAAe,YAAa,CAAE;AAE9C,MAAK,eAAe,WAAW,GAAI;AAClC,WAAO;AAAA,EACR;AAEA,MAAI,aAAa;AACjB,MAAK,aAAc;AAClB,sBAAc;AAAA;AAAA,UAEb,gBAAI,qBAAqB,sBAAsB,YAAY,MAAO;AAAA,MAClE,YAAY,OAAO,eAAe;AAAA,IACnC;AACA,yBAAiB,gBAAI,WAAY;AAAA,EAClC,WAAY,WAAY;AACvB,sBAAc;AAAA;AAAA,UAEb,gBAAI,mBAAmB,oBAAoB,WAAW,MAAO;AAAA,MAC7D,WAAW,OAAO,eAAe;AAAA,IAClC;AACA,yBAAiB,gBAAI,eAAgB;AAAA,EACtC,OAAO;AACN,sBAAc,gBAAI,iBAAkB;AACpC,yBAAiB,gBAAI,iBAAkB;AAAA,EACxC;AAEA,SACC,6CAAC,SAAI,WAAU,qCACZ;AAAA,mBAAe,4CAAC,6BAAQ;AAAA,IAC1B;AAAA,MAAC;AAAA;AAAA,QACA,WAAU;AAAA,QACV,MAAK;AAAA,QACL,MAAO;AAAA,QACP,cAAa;AAAA,QACb,SAAU,MAAM,aAAc,CAAE,MAAO;AAAA,QACvC,iBAAgB;AAAA,QAChB,KAAM;AAAA,QAEJ;AAAA;AAAA,IACH;AAAA,IACE,UACD;AAAA,MAAC;AAAA;AAAA,QACA,WAAU;AAAA,QACV,WAAU;AAAA,QACV,QAAS;AAAA,QACT,QAAS,WAAW;AAAA,QACpB,cAAY;AAAA,QACZ,SAAU,MAAM;AAGf,cACC,WAAW,SAAS;AAAA,YACnB,WAAW,QAAQ,cAAc;AAAA,UAClC,GACC;AACD;AAAA,UACD;AACA,uBAAc,KAAM;AAAA,QACrB;AAAA,QAEA;AAAA,sDAAC,SAAI,WAAU,6CACd,sDAAC,QAAK,0BAAgB,GACvB;AAAA,UACA,4CAAC,QAAG,WAAU,2CACX,yBAAe,IAAK,CAAE,SACvB;AAAA,YAAC;AAAA;AAAA,cAEA,WAAU;AAAA,cAER;AAAA,qBAAK,WAAW,eAAe,4CAAC,6BAAQ;AAAA,gBACxC,KAAK,WAAW,cACjB,4CAAC,0BAAK,MAAO,oBAAQ,MAAO,IAAK;AAAA,iBAE9B,KAAK,WAAW,eACnB,KAAK,WAAW,eAChB;AAAA,kBAAC;AAAA;AAAA,oBACA,WAAU;AAAA,oBACV,OAAQ,KAAK;AAAA,oBAEX,eAAK;AAAA;AAAA,gBACR;AAAA,gBAEC,KAAK,WAAW,WACjB;AAAA,kBAAC;AAAA;AAAA,oBACA,QAAO;AAAA,oBACP,eAAgB,CAAC,CAAE;AAAA,oBACnB,UAAW,MACV,iBAAkB,KAAK,EAAG;AAAA,oBAGzB;AAAA,2BAAK;AAAA,sBAAM;AAAA,sBAAI,KAAK;AAAA;AAAA;AAAA,gBACvB;AAAA;AAAA;AAAA,YAzBK,KAAK;AAAA,UA2BZ,CACC,GACH;AAAA;AAAA;AAAA,IACD;AAAA,KAEF;AAEF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// packages/media-utils/src/components/media-upload-modal/use-invalidate-attachment-resolutions.ts
|
|
21
|
+
var use_invalidate_attachment_resolutions_exports = {};
|
|
22
|
+
__export(use_invalidate_attachment_resolutions_exports, {
|
|
23
|
+
useInvalidateAttachmentResolutions: () => useInvalidateAttachmentResolutions
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(use_invalidate_attachment_resolutions_exports);
|
|
26
|
+
var import_element = require("@wordpress/element");
|
|
27
|
+
var import_core_data = require("@wordpress/core-data");
|
|
28
|
+
var import_data = require("@wordpress/data");
|
|
29
|
+
function useInvalidateAttachmentResolutions() {
|
|
30
|
+
const registry = (0, import_data.useRegistry)();
|
|
31
|
+
return (0, import_element.useCallback)(() => {
|
|
32
|
+
const resolvers = registry.select(import_core_data.store).getCachedResolvers();
|
|
33
|
+
const entityRecordResolutions = resolvers.getEntityRecords;
|
|
34
|
+
entityRecordResolutions?.forEach((_value, args) => {
|
|
35
|
+
if (args[0] === "postType" && args[1] === "attachment") {
|
|
36
|
+
registry.dispatch(import_core_data.store).invalidateResolution("getEntityRecords", args);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}, [registry]);
|
|
40
|
+
}
|
|
41
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
42
|
+
0 && (module.exports = {
|
|
43
|
+
useInvalidateAttachmentResolutions
|
|
44
|
+
});
|
|
45
|
+
//# sourceMappingURL=use-invalidate-attachment-resolutions.cjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/components/media-upload-modal/use-invalidate-attachment-resolutions.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Hook for invalidating all cached `getEntityRecords` resolutions for the\n * attachment post type.\n *\n * After a file upload completes the media grid needs to refresh, but\n * `invalidateResolution` only clears the exact query that is passed to it.\n * If the user is on page 2, page 1 (where the new upload would appear) stays\n * stale. Using `invalidateResolutionForStoreSelector` would work but is too\n * broad \u2014 it clears every `getEntityRecords` resolution, potentially\n * triggering unnecessary refetches for unrelated entity types.\n *\n * This hook provides a middle ground: it iterates over every cached\n * resolution for `getEntityRecords` and invalidates only the entries where\n * the first two arguments match `['postType', 'attachment']`.\n */\n\n/**\n * WordPress dependencies\n */\nimport { useCallback } from '@wordpress/element';\nimport { store as coreStore } from '@wordpress/core-data';\nimport { useRegistry } from '@wordpress/data';\n\n/**\n * Returns a stable callback that invalidates all cached `getEntityRecords`\n * resolutions for `postType / attachment`, leaving every other entity type\n * untouched.\n */\nexport function useInvalidateAttachmentResolutions() {\n\tconst registry = useRegistry();\n\n\treturn useCallback( () => {\n\t\tconst resolvers = registry.select( coreStore ).getCachedResolvers();\n\n\t\t// getCachedResolvers() is typed as Record<string, unknown> but the\n\t\t// values are EquivalentKeyMap instances (Map-like). Cast the same\n\t\t// way the resolvers-cache-middleware does internally.\n\t\tconst entityRecordResolutions = resolvers.getEntityRecords as\n\t\t\t| Map< string[], { status: string } >\n\t\t\t| undefined;\n\n\t\tentityRecordResolutions?.forEach( ( _value, args ) => {\n\t\t\tif ( args[ 0 ] === 'postType' && args[ 1 ] === 'attachment' ) {\n\t\t\t\tregistry\n\t\t\t\t\t.dispatch( coreStore )\n\t\t\t\t\t.invalidateResolution( 'getEntityRecords', args );\n\t\t\t}\n\t\t} );\n\t}, [ registry ] );\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBA,qBAA4B;AAC5B,uBAAmC;AACnC,kBAA4B;AAOrB,SAAS,qCAAqC;AACpD,QAAM,eAAW,yBAAY;AAE7B,aAAO,4BAAa,MAAM;AACzB,UAAM,YAAY,SAAS,OAAQ,iBAAAA,KAAU,EAAE,mBAAmB;AAKlE,UAAM,0BAA0B,UAAU;AAI1C,6BAAyB,QAAS,CAAE,QAAQ,SAAU;AACrD,UAAK,KAAM,CAAE,MAAM,cAAc,KAAM,CAAE,MAAM,cAAe;AAC7D,iBACE,SAAU,iBAAAA,KAAU,EACpB,qBAAsB,oBAAoB,IAAK;AAAA,MAClD;AAAA,IACD,CAAE;AAAA,EACH,GAAG,CAAE,QAAS,CAAE;AACjB;",
|
|
6
|
+
"names": ["coreStore"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// packages/media-utils/src/components/media-upload-modal/use-upload-status.ts
|
|
21
|
+
var use_upload_status_exports = {};
|
|
22
|
+
__export(use_upload_status_exports, {
|
|
23
|
+
useUploadStatus: () => useUploadStatus
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(use_upload_status_exports);
|
|
26
|
+
var import_element = require("@wordpress/element");
|
|
27
|
+
var import_blob = require("@wordpress/blob");
|
|
28
|
+
var import_upload_error = require("../../utils/upload-error.cjs");
|
|
29
|
+
var idCounter = 0;
|
|
30
|
+
var batchIdCounter = 0;
|
|
31
|
+
function useUploadStatus({
|
|
32
|
+
onBatchComplete
|
|
33
|
+
} = {}) {
|
|
34
|
+
const [uploadingFiles, setUploadingFiles] = (0, import_element.useState)(
|
|
35
|
+
[]
|
|
36
|
+
);
|
|
37
|
+
const clearCompleted = (0, import_element.useCallback)(() => {
|
|
38
|
+
setUploadingFiles(
|
|
39
|
+
(prev) => prev.filter((item) => item.status !== "uploaded")
|
|
40
|
+
);
|
|
41
|
+
}, []);
|
|
42
|
+
const dismissError = (0, import_element.useCallback)((fileId) => {
|
|
43
|
+
setUploadingFiles(
|
|
44
|
+
(prev) => prev.filter((item) => item.id !== fileId)
|
|
45
|
+
);
|
|
46
|
+
}, []);
|
|
47
|
+
const registerBatch = (0, import_element.useCallback)(
|
|
48
|
+
(files) => {
|
|
49
|
+
const batchId = String(++batchIdCounter);
|
|
50
|
+
const batchSize = files.length;
|
|
51
|
+
const newEntries = files.map((file) => ({
|
|
52
|
+
id: String(++idCounter),
|
|
53
|
+
batchId,
|
|
54
|
+
name: file.name,
|
|
55
|
+
status: "uploading"
|
|
56
|
+
}));
|
|
57
|
+
setUploadingFiles((prev) => [...prev, ...newEntries]);
|
|
58
|
+
let successCount = 0;
|
|
59
|
+
let errorCount = 0;
|
|
60
|
+
let batchDone = false;
|
|
61
|
+
let successAttachments = [];
|
|
62
|
+
const completeBatchIfDone = () => {
|
|
63
|
+
if (batchDone || successCount + errorCount < batchSize) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
batchDone = true;
|
|
67
|
+
setUploadingFiles(
|
|
68
|
+
(prev) => prev.map(
|
|
69
|
+
(item) => item.batchId === batchId && item.status === "uploading" ? {
|
|
70
|
+
...item,
|
|
71
|
+
status: "uploaded"
|
|
72
|
+
} : item
|
|
73
|
+
)
|
|
74
|
+
);
|
|
75
|
+
onBatchComplete?.(successAttachments);
|
|
76
|
+
};
|
|
77
|
+
const onFileChange = (attachments) => {
|
|
78
|
+
if (batchDone) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const allReal = attachments.every(
|
|
82
|
+
(attachment) => attachment.id && attachment.url && !(0, import_blob.isBlobURL)(attachment.url)
|
|
83
|
+
);
|
|
84
|
+
if (!allReal) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
successCount = attachments.length;
|
|
88
|
+
successAttachments = attachments;
|
|
89
|
+
completeBatchIfDone();
|
|
90
|
+
};
|
|
91
|
+
const onError = (error) => {
|
|
92
|
+
const fileName = error instanceof import_upload_error.UploadError ? error.file.name : void 0;
|
|
93
|
+
setUploadingFiles((prev) => {
|
|
94
|
+
let matched = false;
|
|
95
|
+
return prev.map((item) => {
|
|
96
|
+
if (!matched && item.batchId === batchId && item.status === "uploading" && (!fileName || item.name === fileName)) {
|
|
97
|
+
matched = true;
|
|
98
|
+
return {
|
|
99
|
+
...item,
|
|
100
|
+
status: "error",
|
|
101
|
+
error: error.message
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return item;
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
errorCount++;
|
|
108
|
+
completeBatchIfDone();
|
|
109
|
+
};
|
|
110
|
+
return { onFileChange, onError };
|
|
111
|
+
},
|
|
112
|
+
[onBatchComplete]
|
|
113
|
+
);
|
|
114
|
+
const allComplete = uploadingFiles.length > 0 && uploadingFiles.every((item) => item.status !== "uploading");
|
|
115
|
+
return {
|
|
116
|
+
uploadingFiles,
|
|
117
|
+
registerBatch,
|
|
118
|
+
dismissError,
|
|
119
|
+
clearCompleted,
|
|
120
|
+
allComplete
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
124
|
+
0 && (module.exports = {
|
|
125
|
+
useUploadStatus
|
|
126
|
+
});
|
|
127
|
+
//# sourceMappingURL=use-upload-status.cjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/components/media-upload-modal/use-upload-status.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Hook for tracking media upload status with batch-scoped callbacks.\n *\n * This is a transitional layer that manually tracks upload progress using\n * local state. The @wordpress/upload-media package provides a Redux-based\n * store with richer capabilities (per-file progress, pause/resume, retry,\n * concurrency control, client-side processing). When the media upload modal\n * adopts @wordpress/upload-media, this hook can be replaced by selectors\n * from that store (getItems, isBatchUploaded, getItemProgress, etc.) while\n * keeping the same return interface.\n */\n\n/**\n * WordPress dependencies\n */\nimport { useState, useCallback } from '@wordpress/element';\nimport { isBlobURL } from '@wordpress/blob';\n\n/**\n * Internal dependencies\n */\nimport type { Attachment } from '../../utils/types';\nimport { UploadError } from '../../utils/upload-error';\nimport type { UploadingFile } from './upload-status-popover';\n\nlet idCounter = 0;\nlet batchIdCounter = 0;\n\ninterface UseUploadStatusOptions {\n\tonBatchComplete?: ( attachments: Partial< Attachment >[] ) => void;\n}\n\ninterface RegisterBatchResult {\n\tonFileChange: ( attachments: Partial< Attachment >[] ) => void;\n\tonError: ( error: Error ) => void;\n}\n\ninterface UseUploadStatusReturn {\n\t/** Current list of all tracked files. */\n\tuploadingFiles: UploadingFile[];\n\t/**\n\t * Register a new batch of files for tracking.\n\t * Returns batch-scoped onFileChange and onError callbacks.\n\t */\n\tregisterBatch: ( files: File[] ) => RegisterBatchResult;\n\t/** Remove a single error entry by file id. */\n\tdismissError: ( fileId: string ) => void;\n\t/** Remove all uploaded (completed) entries from the list. */\n\tclearCompleted: () => void;\n\t/** True when tracked entries exist but none are still uploading. */\n\tallComplete: boolean;\n}\n\nexport function useUploadStatus( {\n\tonBatchComplete,\n}: UseUploadStatusOptions = {} ): UseUploadStatusReturn {\n\tconst [ uploadingFiles, setUploadingFiles ] = useState< UploadingFile[] >(\n\t\t[]\n\t);\n\n\tconst clearCompleted = useCallback( () => {\n\t\tsetUploadingFiles( ( prev ) =>\n\t\t\tprev.filter( ( item ) => item.status !== 'uploaded' )\n\t\t);\n\t}, [] );\n\n\tconst dismissError = useCallback( ( fileId: string ) => {\n\t\tsetUploadingFiles( ( prev ) =>\n\t\t\tprev.filter( ( item ) => item.id !== fileId )\n\t\t);\n\t}, [] );\n\n\tconst registerBatch = useCallback(\n\t\t( files: File[] ): RegisterBatchResult => {\n\t\t\tconst batchId = String( ++batchIdCounter );\n\t\t\tconst batchSize = files.length;\n\n\t\t\tconst newEntries: UploadingFile[] = files.map( ( file ) => ( {\n\t\t\t\tid: String( ++idCounter ),\n\t\t\t\tbatchId,\n\t\t\t\tname: file.name,\n\t\t\t\tstatus: 'uploading' as const,\n\t\t\t} ) );\n\n\t\t\tsetUploadingFiles( ( prev ) => [ ...prev, ...newEntries ] );\n\n\t\t\t// Track successes and errors separately. onFileChange receives\n\t\t\t// the full current array each time (not incremental), so we\n\t\t\t// store the latest count rather than accumulating. The batch\n\t\t\t// is complete when successCount + errorCount >= batchSize.\n\t\t\tlet successCount = 0;\n\t\t\tlet errorCount = 0;\n\t\t\tlet batchDone = false;\n\t\t\tlet successAttachments: Partial< Attachment >[] = [];\n\n\t\t\tconst completeBatchIfDone = () => {\n\t\t\t\tif ( batchDone || successCount + errorCount < batchSize ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tbatchDone = true;\n\n\t\t\t\t// Mark any remaining 'uploading' entries in this batch\n\t\t\t\t// as 'uploaded' (these are the successful ones).\n\t\t\t\tsetUploadingFiles( ( prev ) =>\n\t\t\t\t\tprev.map( ( item ) =>\n\t\t\t\t\t\titem.batchId === batchId && item.status === 'uploading'\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t...item,\n\t\t\t\t\t\t\t\t\tstatus: 'uploaded' as const,\n\t\t\t\t\t\t\t }\n\t\t\t\t\t\t\t: item\n\t\t\t\t\t)\n\t\t\t\t);\n\n\t\t\t\tonBatchComplete?.( successAttachments );\n\t\t\t};\n\n\t\t\tconst onFileChange = ( attachments: Partial< Attachment >[] ) => {\n\t\t\t\tif ( batchDone ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Ignore intermediate calls where some files still have\n\t\t\t\t// blob URLs (not yet uploaded to the server).\n\t\t\t\tconst allReal = attachments.every(\n\t\t\t\t\t( attachment ) =>\n\t\t\t\t\t\tattachment.id &&\n\t\t\t\t\t\tattachment.url &&\n\t\t\t\t\t\t! isBlobURL( attachment.url )\n\t\t\t\t);\n\n\t\t\t\tif ( ! allReal ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Store the latest success count and attachments.\n\t\t\t\t// onFileChange receives the full array each time, so\n\t\t\t\t// we replace rather than accumulate.\n\t\t\t\tsuccessCount = attachments.length;\n\t\t\t\tsuccessAttachments = attachments;\n\n\t\t\t\tcompleteBatchIfDone();\n\t\t\t};\n\n\t\t\tconst onError = ( error: Error ) => {\n\t\t\t\t// uploadMedia always wraps errors in UploadError, which\n\t\t\t\t// carries the originating File so we can match it back to\n\t\t\t\t// the correct item. A custom onUpload prop could pass a\n\t\t\t\t// plain Error instead \u2014 in that case fileName is undefined\n\t\t\t\t// and no entry will be visually marked as errored, but the\n\t\t\t\t// batch will still complete correctly via errorCount.\n\t\t\t\tconst fileName =\n\t\t\t\t\terror instanceof UploadError ? error.file.name : undefined;\n\n\t\t\t\t// Find the first still-uploading entry in this batch whose\n\t\t\t\t// name matches the failed file and mark it as errored.\n\t\t\t\t// Falls back to the first uploading entry in the batch\n\t\t\t\t// when no filename is available. The `matched` flag\n\t\t\t\t// ensures only one entry is updated even when duplicate\n\t\t\t\t// filenames exist in the same batch.\n\t\t\t\tsetUploadingFiles( ( prev ) => {\n\t\t\t\t\tlet matched = false;\n\t\t\t\t\treturn prev.map( ( item ) => {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t! matched &&\n\t\t\t\t\t\t\titem.batchId === batchId &&\n\t\t\t\t\t\t\titem.status === 'uploading' &&\n\t\t\t\t\t\t\t( ! fileName || item.name === fileName )\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tmatched = true;\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t...item,\n\t\t\t\t\t\t\t\tstatus: 'error' as const,\n\t\t\t\t\t\t\t\terror: error.message,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn item;\n\t\t\t\t\t} );\n\t\t\t\t} );\n\n\t\t\t\terrorCount++;\n\n\t\t\t\tcompleteBatchIfDone();\n\t\t\t};\n\n\t\t\treturn { onFileChange, onError };\n\t\t},\n\t\t[ onBatchComplete ]\n\t);\n\n\tconst allComplete =\n\t\tuploadingFiles.length > 0 &&\n\t\tuploadingFiles.every( ( item ) => item.status !== 'uploading' );\n\n\treturn {\n\t\tuploadingFiles,\n\t\tregisterBatch,\n\t\tdismissError,\n\t\tclearCompleted,\n\t\tallComplete,\n\t};\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAeA,qBAAsC;AACtC,kBAA0B;AAM1B,0BAA4B;AAG5B,IAAI,YAAY;AAChB,IAAI,iBAAiB;AA2Bd,SAAS,gBAAiB;AAAA,EAChC;AACD,IAA4B,CAAC,GAA2B;AACvD,QAAM,CAAE,gBAAgB,iBAAkB,QAAI;AAAA,IAC7C,CAAC;AAAA,EACF;AAEA,QAAM,qBAAiB,4BAAa,MAAM;AACzC;AAAA,MAAmB,CAAE,SACpB,KAAK,OAAQ,CAAE,SAAU,KAAK,WAAW,UAAW;AAAA,IACrD;AAAA,EACD,GAAG,CAAC,CAAE;AAEN,QAAM,mBAAe,4BAAa,CAAE,WAAoB;AACvD;AAAA,MAAmB,CAAE,SACpB,KAAK,OAAQ,CAAE,SAAU,KAAK,OAAO,MAAO;AAAA,IAC7C;AAAA,EACD,GAAG,CAAC,CAAE;AAEN,QAAM,oBAAgB;AAAA,IACrB,CAAE,UAAwC;AACzC,YAAM,UAAU,OAAQ,EAAE,cAAe;AACzC,YAAM,YAAY,MAAM;AAExB,YAAM,aAA8B,MAAM,IAAK,CAAE,UAAY;AAAA,QAC5D,IAAI,OAAQ,EAAE,SAAU;AAAA,QACxB;AAAA,QACA,MAAM,KAAK;AAAA,QACX,QAAQ;AAAA,MACT,EAAI;AAEJ,wBAAmB,CAAE,SAAU,CAAE,GAAG,MAAM,GAAG,UAAW,CAAE;AAM1D,UAAI,eAAe;AACnB,UAAI,aAAa;AACjB,UAAI,YAAY;AAChB,UAAI,qBAA8C,CAAC;AAEnD,YAAM,sBAAsB,MAAM;AACjC,YAAK,aAAa,eAAe,aAAa,WAAY;AACzD;AAAA,QACD;AAEA,oBAAY;AAIZ;AAAA,UAAmB,CAAE,SACpB,KAAK;AAAA,YAAK,CAAE,SACX,KAAK,YAAY,WAAW,KAAK,WAAW,cACzC;AAAA,cACA,GAAG;AAAA,cACH,QAAQ;AAAA,YACR,IACA;AAAA,UACJ;AAAA,QACD;AAEA,0BAAmB,kBAAmB;AAAA,MACvC;AAEA,YAAM,eAAe,CAAE,gBAA0C;AAChE,YAAK,WAAY;AAChB;AAAA,QACD;AAIA,cAAM,UAAU,YAAY;AAAA,UAC3B,CAAE,eACD,WAAW,MACX,WAAW,OACX,KAAE,uBAAW,WAAW,GAAI;AAAA,QAC9B;AAEA,YAAK,CAAE,SAAU;AAChB;AAAA,QACD;AAKA,uBAAe,YAAY;AAC3B,6BAAqB;AAErB,4BAAoB;AAAA,MACrB;AAEA,YAAM,UAAU,CAAE,UAAkB;AAOnC,cAAM,WACL,iBAAiB,kCAAc,MAAM,KAAK,OAAO;AAQlD,0BAAmB,CAAE,SAAU;AAC9B,cAAI,UAAU;AACd,iBAAO,KAAK,IAAK,CAAE,SAAU;AAC5B,gBACC,CAAE,WACF,KAAK,YAAY,WACjB,KAAK,WAAW,gBACd,CAAE,YAAY,KAAK,SAAS,WAC7B;AACD,wBAAU;AACV,qBAAO;AAAA,gBACN,GAAG;AAAA,gBACH,QAAQ;AAAA,gBACR,OAAO,MAAM;AAAA,cACd;AAAA,YACD;AACA,mBAAO;AAAA,UACR,CAAE;AAAA,QACH,CAAE;AAEF;AAEA,4BAAoB;AAAA,MACrB;AAEA,aAAO,EAAE,cAAc,QAAQ;AAAA,IAChC;AAAA,IACA,CAAE,eAAgB;AAAA,EACnB;AAEA,QAAM,cACL,eAAe,SAAS,KACxB,eAAe,MAAO,CAAE,SAAU,KAAK,WAAW,WAAY;AAE/D,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
// packages/media-utils/src/components/media-upload-modal/index.tsx
|
|
2
|
+
import clsx from "clsx";
|
|
2
3
|
import {
|
|
3
4
|
createPortal,
|
|
4
5
|
useState,
|
|
5
6
|
useCallback,
|
|
6
|
-
useMemo
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
useEffect
|
|
7
10
|
} from "@wordpress/element";
|
|
8
11
|
import { __, sprintf, _n } from "@wordpress/i18n";
|
|
9
12
|
import {
|
|
@@ -14,6 +17,7 @@ import { resolveSelect, useDispatch } from "@wordpress/data";
|
|
|
14
17
|
import { Modal, DropZone, FormFileUpload, Button } from "@wordpress/components";
|
|
15
18
|
import { upload as uploadIcon } from "@wordpress/icons";
|
|
16
19
|
import { DataViewsPicker } from "@wordpress/dataviews";
|
|
20
|
+
import { Stack } from "@wordpress/ui";
|
|
17
21
|
import {
|
|
18
22
|
altTextField,
|
|
19
23
|
attachedToField,
|
|
@@ -29,10 +33,12 @@ import {
|
|
|
29
33
|
mimeTypeField
|
|
30
34
|
} from "@wordpress/media-fields";
|
|
31
35
|
import { store as noticesStore, SnackbarNotices } from "@wordpress/notices";
|
|
32
|
-
import { isBlobURL } from "@wordpress/blob";
|
|
33
36
|
import { transformAttachment } from "../../utils/transform-attachment.mjs";
|
|
34
37
|
import { uploadMedia } from "../../utils/upload-media.mjs";
|
|
35
38
|
import { unlock } from "../../lock-unlock.mjs";
|
|
39
|
+
import { UploadStatusPopover } from "./upload-status-popover.mjs";
|
|
40
|
+
import { useInvalidateAttachmentResolutions } from "./use-invalidate-attachment-resolutions.mjs";
|
|
41
|
+
import { useUploadStatus } from "./use-upload-status.mjs";
|
|
36
42
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
37
43
|
var { useEntityRecordsWithPermissions } = unlock(coreDataPrivateApis);
|
|
38
44
|
var LAYOUT_PICKER_GRID = "pickerGrid";
|
|
@@ -59,8 +65,8 @@ function MediaUploadModal({
|
|
|
59
65
|
}
|
|
60
66
|
return Array.isArray(value) ? value.map(String) : [String(value)];
|
|
61
67
|
});
|
|
62
|
-
const { createSuccessNotice,
|
|
63
|
-
const
|
|
68
|
+
const { createSuccessNotice, removeAllNotices } = useDispatch(noticesStore);
|
|
69
|
+
const invalidateAttachmentResolutions = useInvalidateAttachmentResolutions();
|
|
64
70
|
const [view, setView] = useState(() => ({
|
|
65
71
|
type: LAYOUT_PICKER_GRID,
|
|
66
72
|
fields: [],
|
|
@@ -69,10 +75,11 @@ function MediaUploadModal({
|
|
|
69
75
|
mediaField: "media_thumbnail",
|
|
70
76
|
search: "",
|
|
71
77
|
page: 1,
|
|
72
|
-
perPage:
|
|
78
|
+
perPage: 50,
|
|
73
79
|
filters: [],
|
|
74
80
|
layout: {
|
|
75
|
-
previewSize: 170
|
|
81
|
+
previewSize: 170,
|
|
82
|
+
density: "compact"
|
|
76
83
|
}
|
|
77
84
|
}));
|
|
78
85
|
const queryArgs = useMemo(() => {
|
|
@@ -113,6 +120,41 @@ function MediaUploadModal({
|
|
|
113
120
|
...filters
|
|
114
121
|
};
|
|
115
122
|
}, [view, allowedTypes]);
|
|
123
|
+
const handleBatchComplete = useCallback(
|
|
124
|
+
(attachments) => {
|
|
125
|
+
const uploadedIds = attachments.map((attachment) => String(attachment.id)).filter(Boolean);
|
|
126
|
+
if (multiple) {
|
|
127
|
+
setSelection((prev) => {
|
|
128
|
+
const existing = new Set(prev);
|
|
129
|
+
const newIds = uploadedIds.filter(
|
|
130
|
+
(id) => !existing.has(id)
|
|
131
|
+
);
|
|
132
|
+
return [...prev, ...newIds];
|
|
133
|
+
});
|
|
134
|
+
} else {
|
|
135
|
+
setSelection(uploadedIds.slice(0, 1));
|
|
136
|
+
}
|
|
137
|
+
invalidateAttachmentResolutions();
|
|
138
|
+
},
|
|
139
|
+
[multiple, invalidateAttachmentResolutions]
|
|
140
|
+
);
|
|
141
|
+
const {
|
|
142
|
+
uploadingFiles,
|
|
143
|
+
registerBatch,
|
|
144
|
+
dismissError,
|
|
145
|
+
clearCompleted,
|
|
146
|
+
allComplete
|
|
147
|
+
} = useUploadStatus({ onBatchComplete: handleBatchComplete });
|
|
148
|
+
const isPopoverOpenRef = useRef(false);
|
|
149
|
+
const handlePopoverOpenChange = useCallback(
|
|
150
|
+
(open) => {
|
|
151
|
+
isPopoverOpenRef.current = open;
|
|
152
|
+
if (!open) {
|
|
153
|
+
clearCompleted();
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
[clearCompleted]
|
|
157
|
+
);
|
|
116
158
|
const {
|
|
117
159
|
records: mediaRecords,
|
|
118
160
|
isResolving: isLoading,
|
|
@@ -155,7 +197,7 @@ function MediaUploadModal({
|
|
|
155
197
|
() => [
|
|
156
198
|
{
|
|
157
199
|
id: "select",
|
|
158
|
-
label:
|
|
200
|
+
label: __("Select"),
|
|
159
201
|
isPrimary: true,
|
|
160
202
|
supportsBulk: multiple,
|
|
161
203
|
async callback() {
|
|
@@ -175,31 +217,34 @@ function MediaUploadModal({
|
|
|
175
217
|
);
|
|
176
218
|
const transformedPosts = (selectedPosts ?? []).map(transformAttachment).filter(Boolean);
|
|
177
219
|
const selectedItems = multiple ? transformedPosts : transformedPosts?.[0];
|
|
220
|
+
removeAllNotices("snackbar", NOTICES_CONTEXT);
|
|
178
221
|
onSelect(selectedItems);
|
|
179
222
|
}
|
|
180
223
|
}
|
|
181
224
|
],
|
|
182
|
-
[multiple, onSelect, selection]
|
|
225
|
+
[multiple, onSelect, selection, removeAllNotices]
|
|
183
226
|
);
|
|
184
227
|
const handleModalClose = useCallback(() => {
|
|
228
|
+
removeAllNotices("snackbar", NOTICES_CONTEXT);
|
|
185
229
|
onClose?.();
|
|
186
|
-
}, [onClose]);
|
|
230
|
+
}, [removeAllNotices, onClose]);
|
|
187
231
|
const handleUpload = onUpload || uploadMedia;
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
232
|
+
const prevAllCompleteRef = useRef(false);
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
if (allComplete && !prevAllCompleteRef.current) {
|
|
235
|
+
const completeCount = uploadingFiles.filter(
|
|
236
|
+
(file) => file.status === "uploaded"
|
|
237
|
+
).length;
|
|
238
|
+
if (completeCount > 0) {
|
|
194
239
|
createSuccessNotice(
|
|
195
240
|
sprintf(
|
|
196
241
|
// translators: %s: number of files
|
|
197
242
|
_n(
|
|
198
243
|
"Uploaded %s file",
|
|
199
244
|
"Uploaded %s files",
|
|
200
|
-
|
|
245
|
+
completeCount
|
|
201
246
|
),
|
|
202
|
-
|
|
247
|
+
completeCount.toLocaleString()
|
|
203
248
|
),
|
|
204
249
|
{
|
|
205
250
|
type: "snackbar",
|
|
@@ -207,68 +252,28 @@ function MediaUploadModal({
|
|
|
207
252
|
id: NOTICE_ID_UPLOAD_PROGRESS
|
|
208
253
|
}
|
|
209
254
|
);
|
|
210
|
-
const uploadedIds = attachments.map((attachment) => String(attachment.id)).filter(Boolean);
|
|
211
|
-
if (multiple) {
|
|
212
|
-
setSelection((prev) => [...prev, ...uploadedIds]);
|
|
213
|
-
} else {
|
|
214
|
-
setSelection(uploadedIds.slice(0, 1));
|
|
215
|
-
}
|
|
216
|
-
invalidateResolution("getEntityRecords", [
|
|
217
|
-
"postType",
|
|
218
|
-
"attachment",
|
|
219
|
-
queryArgs
|
|
220
|
-
]);
|
|
221
255
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
type: "snackbar",
|
|
229
|
-
context: NOTICES_CONTEXT,
|
|
230
|
-
id: NOTICE_ID_UPLOAD_PROGRESS
|
|
231
|
-
});
|
|
232
|
-
},
|
|
233
|
-
[createErrorNotice]
|
|
234
|
-
);
|
|
256
|
+
if (!isPopoverOpenRef.current) {
|
|
257
|
+
clearCompleted();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
prevAllCompleteRef.current = allComplete;
|
|
261
|
+
}, [allComplete, uploadingFiles, createSuccessNotice, clearCompleted]);
|
|
235
262
|
const handleFileSelect = useCallback(
|
|
236
263
|
(event) => {
|
|
237
264
|
const files = event.target.files;
|
|
238
265
|
if (files && files.length > 0) {
|
|
239
266
|
const filesArray = Array.from(files);
|
|
240
|
-
|
|
241
|
-
sprintf(
|
|
242
|
-
// translators: %s: number of files
|
|
243
|
-
_n(
|
|
244
|
-
"Uploading %s file",
|
|
245
|
-
"Uploading %s files",
|
|
246
|
-
filesArray.length
|
|
247
|
-
),
|
|
248
|
-
filesArray.length.toLocaleString()
|
|
249
|
-
),
|
|
250
|
-
{
|
|
251
|
-
type: "snackbar",
|
|
252
|
-
context: NOTICES_CONTEXT,
|
|
253
|
-
id: NOTICE_ID_UPLOAD_PROGRESS,
|
|
254
|
-
explicitDismiss: true
|
|
255
|
-
}
|
|
256
|
-
);
|
|
267
|
+
const { onFileChange, onError } = registerBatch(filesArray);
|
|
257
268
|
handleUpload({
|
|
258
269
|
allowedTypes,
|
|
259
270
|
filesList: filesArray,
|
|
260
|
-
onFileChange
|
|
261
|
-
onError
|
|
271
|
+
onFileChange,
|
|
272
|
+
onError
|
|
262
273
|
});
|
|
263
274
|
}
|
|
264
275
|
},
|
|
265
|
-
[
|
|
266
|
-
allowedTypes,
|
|
267
|
-
handleUpload,
|
|
268
|
-
createInfoNotice,
|
|
269
|
-
handleUploadComplete,
|
|
270
|
-
handleUploadError
|
|
271
|
-
]
|
|
276
|
+
[allowedTypes, handleUpload, registerBatch]
|
|
272
277
|
);
|
|
273
278
|
const paginationInfo = useMemo(
|
|
274
279
|
() => ({
|
|
@@ -348,35 +353,19 @@ function MediaUploadModal({
|
|
|
348
353
|
);
|
|
349
354
|
}
|
|
350
355
|
if (filteredFiles.length > 0) {
|
|
351
|
-
|
|
352
|
-
sprintf(
|
|
353
|
-
// translators: %s: number of files
|
|
354
|
-
_n(
|
|
355
|
-
"Uploading %s file",
|
|
356
|
-
"Uploading %s files",
|
|
357
|
-
filteredFiles.length
|
|
358
|
-
),
|
|
359
|
-
filteredFiles.length.toLocaleString()
|
|
360
|
-
),
|
|
361
|
-
{
|
|
362
|
-
type: "snackbar",
|
|
363
|
-
context: NOTICES_CONTEXT,
|
|
364
|
-
id: NOTICE_ID_UPLOAD_PROGRESS,
|
|
365
|
-
explicitDismiss: true
|
|
366
|
-
}
|
|
367
|
-
);
|
|
356
|
+
const { onFileChange, onError } = registerBatch(filteredFiles);
|
|
368
357
|
handleUpload({
|
|
369
358
|
allowedTypes,
|
|
370
359
|
filesList: filteredFiles,
|
|
371
|
-
onFileChange
|
|
372
|
-
onError
|
|
360
|
+
onFileChange,
|
|
361
|
+
onError
|
|
373
362
|
});
|
|
374
363
|
}
|
|
375
364
|
},
|
|
376
365
|
label: __("Drop files to upload")
|
|
377
366
|
}
|
|
378
367
|
),
|
|
379
|
-
/* @__PURE__ */
|
|
368
|
+
/* @__PURE__ */ jsxs(
|
|
380
369
|
DataViewsPicker,
|
|
381
370
|
{
|
|
382
371
|
data: mediaRecords || [],
|
|
@@ -390,9 +379,59 @@ function MediaUploadModal({
|
|
|
390
379
|
paginationInfo,
|
|
391
380
|
defaultLayouts,
|
|
392
381
|
getItemId: (item) => String(item.id),
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
382
|
+
itemListLabel: __("Media items"),
|
|
383
|
+
children: [
|
|
384
|
+
/* @__PURE__ */ jsxs(
|
|
385
|
+
Stack,
|
|
386
|
+
{
|
|
387
|
+
direction: "row",
|
|
388
|
+
align: "top",
|
|
389
|
+
justify: "space-between",
|
|
390
|
+
className: "dataviews__view-actions",
|
|
391
|
+
gap: "xs",
|
|
392
|
+
children: [
|
|
393
|
+
/* @__PURE__ */ jsxs(
|
|
394
|
+
Stack,
|
|
395
|
+
{
|
|
396
|
+
direction: "row",
|
|
397
|
+
gap: "sm",
|
|
398
|
+
justify: "start",
|
|
399
|
+
className: "dataviews__search",
|
|
400
|
+
children: [
|
|
401
|
+
search && /* @__PURE__ */ jsx(DataViewsPicker.Search, { label: searchLabel }),
|
|
402
|
+
/* @__PURE__ */ jsx(DataViewsPicker.FiltersToggle, {})
|
|
403
|
+
]
|
|
404
|
+
}
|
|
405
|
+
),
|
|
406
|
+
/* @__PURE__ */ jsxs(Stack, { direction: "row", gap: "xs", style: { flexShrink: 0 }, children: [
|
|
407
|
+
/* @__PURE__ */ jsx(DataViewsPicker.LayoutSwitcher, {}),
|
|
408
|
+
/* @__PURE__ */ jsx(DataViewsPicker.ViewConfig, {})
|
|
409
|
+
] })
|
|
410
|
+
]
|
|
411
|
+
}
|
|
412
|
+
),
|
|
413
|
+
/* @__PURE__ */ jsx(DataViewsPicker.FiltersToggled, { className: "dataviews-filters__container" }),
|
|
414
|
+
/* @__PURE__ */ jsx(DataViewsPicker.Layout, {}),
|
|
415
|
+
/* @__PURE__ */ jsxs(
|
|
416
|
+
"div",
|
|
417
|
+
{
|
|
418
|
+
className: clsx("media-upload-modal__footer", {
|
|
419
|
+
"is-uploading": uploadingFiles.length > 0
|
|
420
|
+
}),
|
|
421
|
+
children: [
|
|
422
|
+
/* @__PURE__ */ jsx(
|
|
423
|
+
UploadStatusPopover,
|
|
424
|
+
{
|
|
425
|
+
uploadingFiles,
|
|
426
|
+
onDismissError: dismissError,
|
|
427
|
+
onOpenChange: handlePopoverOpenChange
|
|
428
|
+
}
|
|
429
|
+
),
|
|
430
|
+
/* @__PURE__ */ jsx(DataViewsPicker.BulkActionToolbar, {})
|
|
431
|
+
]
|
|
432
|
+
}
|
|
433
|
+
)
|
|
434
|
+
]
|
|
396
435
|
}
|
|
397
436
|
),
|
|
398
437
|
createPortal(
|