@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,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: [],
|
|
@@ -114,6 +120,41 @@ function MediaUploadModal({
|
|
|
114
120
|
...filters
|
|
115
121
|
};
|
|
116
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
|
+
);
|
|
117
158
|
const {
|
|
118
159
|
records: mediaRecords,
|
|
119
160
|
isResolving: isLoading,
|
|
@@ -156,7 +197,7 @@ function MediaUploadModal({
|
|
|
156
197
|
() => [
|
|
157
198
|
{
|
|
158
199
|
id: "select",
|
|
159
|
-
label:
|
|
200
|
+
label: __("Select"),
|
|
160
201
|
isPrimary: true,
|
|
161
202
|
supportsBulk: multiple,
|
|
162
203
|
async callback() {
|
|
@@ -176,31 +217,34 @@ function MediaUploadModal({
|
|
|
176
217
|
);
|
|
177
218
|
const transformedPosts = (selectedPosts ?? []).map(transformAttachment).filter(Boolean);
|
|
178
219
|
const selectedItems = multiple ? transformedPosts : transformedPosts?.[0];
|
|
220
|
+
removeAllNotices("snackbar", NOTICES_CONTEXT);
|
|
179
221
|
onSelect(selectedItems);
|
|
180
222
|
}
|
|
181
223
|
}
|
|
182
224
|
],
|
|
183
|
-
[multiple, onSelect, selection]
|
|
225
|
+
[multiple, onSelect, selection, removeAllNotices]
|
|
184
226
|
);
|
|
185
227
|
const handleModalClose = useCallback(() => {
|
|
228
|
+
removeAllNotices("snackbar", NOTICES_CONTEXT);
|
|
186
229
|
onClose?.();
|
|
187
|
-
}, [onClose]);
|
|
230
|
+
}, [removeAllNotices, onClose]);
|
|
188
231
|
const handleUpload = onUpload || uploadMedia;
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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) {
|
|
195
239
|
createSuccessNotice(
|
|
196
240
|
sprintf(
|
|
197
241
|
// translators: %s: number of files
|
|
198
242
|
_n(
|
|
199
243
|
"Uploaded %s file",
|
|
200
244
|
"Uploaded %s files",
|
|
201
|
-
|
|
245
|
+
completeCount
|
|
202
246
|
),
|
|
203
|
-
|
|
247
|
+
completeCount.toLocaleString()
|
|
204
248
|
),
|
|
205
249
|
{
|
|
206
250
|
type: "snackbar",
|
|
@@ -208,68 +252,28 @@ function MediaUploadModal({
|
|
|
208
252
|
id: NOTICE_ID_UPLOAD_PROGRESS
|
|
209
253
|
}
|
|
210
254
|
);
|
|
211
|
-
const uploadedIds = attachments.map((attachment) => String(attachment.id)).filter(Boolean);
|
|
212
|
-
if (multiple) {
|
|
213
|
-
setSelection((prev) => [...prev, ...uploadedIds]);
|
|
214
|
-
} else {
|
|
215
|
-
setSelection(uploadedIds.slice(0, 1));
|
|
216
|
-
}
|
|
217
|
-
invalidateResolution("getEntityRecords", [
|
|
218
|
-
"postType",
|
|
219
|
-
"attachment",
|
|
220
|
-
queryArgs
|
|
221
|
-
]);
|
|
222
255
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
type: "snackbar",
|
|
230
|
-
context: NOTICES_CONTEXT,
|
|
231
|
-
id: NOTICE_ID_UPLOAD_PROGRESS
|
|
232
|
-
});
|
|
233
|
-
},
|
|
234
|
-
[createErrorNotice]
|
|
235
|
-
);
|
|
256
|
+
if (!isPopoverOpenRef.current) {
|
|
257
|
+
clearCompleted();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
prevAllCompleteRef.current = allComplete;
|
|
261
|
+
}, [allComplete, uploadingFiles, createSuccessNotice, clearCompleted]);
|
|
236
262
|
const handleFileSelect = useCallback(
|
|
237
263
|
(event) => {
|
|
238
264
|
const files = event.target.files;
|
|
239
265
|
if (files && files.length > 0) {
|
|
240
266
|
const filesArray = Array.from(files);
|
|
241
|
-
|
|
242
|
-
sprintf(
|
|
243
|
-
// translators: %s: number of files
|
|
244
|
-
_n(
|
|
245
|
-
"Uploading %s file",
|
|
246
|
-
"Uploading %s files",
|
|
247
|
-
filesArray.length
|
|
248
|
-
),
|
|
249
|
-
filesArray.length.toLocaleString()
|
|
250
|
-
),
|
|
251
|
-
{
|
|
252
|
-
type: "snackbar",
|
|
253
|
-
context: NOTICES_CONTEXT,
|
|
254
|
-
id: NOTICE_ID_UPLOAD_PROGRESS,
|
|
255
|
-
explicitDismiss: true
|
|
256
|
-
}
|
|
257
|
-
);
|
|
267
|
+
const { onFileChange, onError } = registerBatch(filesArray);
|
|
258
268
|
handleUpload({
|
|
259
269
|
allowedTypes,
|
|
260
270
|
filesList: filesArray,
|
|
261
|
-
onFileChange
|
|
262
|
-
onError
|
|
271
|
+
onFileChange,
|
|
272
|
+
onError
|
|
263
273
|
});
|
|
264
274
|
}
|
|
265
275
|
},
|
|
266
|
-
[
|
|
267
|
-
allowedTypes,
|
|
268
|
-
handleUpload,
|
|
269
|
-
createInfoNotice,
|
|
270
|
-
handleUploadComplete,
|
|
271
|
-
handleUploadError
|
|
272
|
-
]
|
|
276
|
+
[allowedTypes, handleUpload, registerBatch]
|
|
273
277
|
);
|
|
274
278
|
const paginationInfo = useMemo(
|
|
275
279
|
() => ({
|
|
@@ -349,35 +353,19 @@ function MediaUploadModal({
|
|
|
349
353
|
);
|
|
350
354
|
}
|
|
351
355
|
if (filteredFiles.length > 0) {
|
|
352
|
-
|
|
353
|
-
sprintf(
|
|
354
|
-
// translators: %s: number of files
|
|
355
|
-
_n(
|
|
356
|
-
"Uploading %s file",
|
|
357
|
-
"Uploading %s files",
|
|
358
|
-
filteredFiles.length
|
|
359
|
-
),
|
|
360
|
-
filteredFiles.length.toLocaleString()
|
|
361
|
-
),
|
|
362
|
-
{
|
|
363
|
-
type: "snackbar",
|
|
364
|
-
context: NOTICES_CONTEXT,
|
|
365
|
-
id: NOTICE_ID_UPLOAD_PROGRESS,
|
|
366
|
-
explicitDismiss: true
|
|
367
|
-
}
|
|
368
|
-
);
|
|
356
|
+
const { onFileChange, onError } = registerBatch(filteredFiles);
|
|
369
357
|
handleUpload({
|
|
370
358
|
allowedTypes,
|
|
371
359
|
filesList: filteredFiles,
|
|
372
|
-
onFileChange
|
|
373
|
-
onError
|
|
360
|
+
onFileChange,
|
|
361
|
+
onError
|
|
374
362
|
});
|
|
375
363
|
}
|
|
376
364
|
},
|
|
377
365
|
label: __("Drop files to upload")
|
|
378
366
|
}
|
|
379
367
|
),
|
|
380
|
-
/* @__PURE__ */
|
|
368
|
+
/* @__PURE__ */ jsxs(
|
|
381
369
|
DataViewsPicker,
|
|
382
370
|
{
|
|
383
371
|
data: mediaRecords || [],
|
|
@@ -391,9 +379,59 @@ function MediaUploadModal({
|
|
|
391
379
|
paginationInfo,
|
|
392
380
|
defaultLayouts,
|
|
393
381
|
getItemId: (item) => String(item.id),
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
+
]
|
|
397
435
|
}
|
|
398
436
|
),
|
|
399
437
|
createPortal(
|